cargo agents hook
Entry point invoked by the agent’s hook system on session events.
Flow
The payload is parsed first, and the agent’s parser can declare it not an occurrence of the symposium event (AgentHookInput::should_dispatch; false for an Antigravity PreInvocation after the first of a turn). Such a payload is answered with the agent’s neutral output and skips every step below.
-
Auto-sync (if enabled) — when
auto-sync = truein the user config, runscargo agents syncto ensure skills are current. The workspace root is resolved from the payload’scwdfield; if the payload does not include a working directory, the process’s current working directory is used as a fallback. Runs quietly and non-fatally — failures are logged but don’t block hook dispatch.SessionStartis the refresh point. Because it fires once per agent session, it does the expensive work that other events skip: it bypasses theCargo.lockfreshness gate (so skills re-sync even when the workspace’s dependencies are unchanged) and passesUpdateLevel::Checkso git registries andsource.gitskill groups are re-fetched if their upstream moved. Every other event keeps the cheap,Cargo.lock-gated path withUpdateLevel::None(debounced) to avoid per-event network andcargo metadatacost. The registry refresh onSessionStart(ensure_registrieswithCheck, decided in the binary entry point from the event) still honors each registry’sauto-updatetoggle.SessionStartalso runsprewarm_hook_sources, which refreshes already-installed hook binaries/scripts (thecargo/githubsources backing plugin hooks) — refresh-only, so it never eagerly installs a tool a hook may never use; first install still happens lazily at dispatch. -
Built-in dispatch — symposium’s own handling, before plugin hooks.
PreToolUseproduces nothing. OnSessionStartthree fragments are computed independently and, when present, joined into oneadditionalContext:- Discovery hint — when the active workspace exposes plugin-vended subcommands (the same workspace-filtered set listed by
cargo agents --help), a line suggesting the agent runcargo agents --helpto find them. Computed independently of the update-check throttle, so it fires whenever there is something to discover. - Consent hint: the dependency plugins awaiting consent, telling the agent to point the user at
cargo agents syncorcargo agents use. - Update nudge — when
auto-update = "warn", the 24-hour check throttle has elapsed, and the registry reports a newer version: a line suggestingcargo agents self-update.
On
PostToolUseandUserPromptSubmit, when auto-sync just re-synced becauseCargo.lockchanged, a generic notice says there may be new plugins and points atcargo agents sync. That is right after a tool call that changed the dependencies, or the user’s next prompt when they changed them.run_auto_syncreturns that as a bool, so an event with an unchangedCargo.lockdoes no extra work. The notice is lost when the agent ignores context on that event (Antigravity after a tool call), or when the change is first seen onPreToolUseorStop, which sync but carry no notice.Agents without hook registration (OpenCode, Goose) never receive this; for them the only discovery surface is
cargo agents --helpitself. - Discovery hint — when the active workspace exposes plugin-vended subcommands (the same workspace-filtered set listed by
-
Dispatch to plugin hooks — for each enabled plugin that defines a hook handler for the incoming event:
- Select format: for each plugin, pick the best hook to deliver (see Hooks for priority rules). If the plugin has a hook matching the current agent’s format, deliver the input unmodified. Otherwise deliver in symposium canonical format (or convert to the declared format if only one non-symposium hook exists).
- Acquire and run:
- Ensure any
requirementsfor the hook are acquired (on-demand, best-effort). - Resolve the hook’s
command(a named installation reference or inline declaration) into a runnable form:- If the installation declares a
source, acquire it (install / cache / clone) and resolve theexecutable/scriptagainst the cached location. Dispatch acquires withUpdateLevel::None— it serves the cache (git checks debounced) rather than hitting the network on every event. Freshness comes from theSessionStartprewarm (step 1), which re-acquires every applicable hook’s source withCheckonce per session. - If no source, the
executable/scriptis taken as a path on disk (relative paths resolve against the plugin directory, so a refreshed plugin-source repo updates these for free). - Run the installation’s
install_commands(post-source) before invoking the runnable. - Spawn
path args…directly forExec, orsh path args…forScript.
- If the installation declares a
- Pass the event JSON (in the selected format) on stdin to the plugin’s hook.
- Collect output from each handler.
- Convert output back to the agent’s wire format.
- Merge results (e.g., allow/block decisions, output text) across all handlers.
mergelets a later value replace an earlier one, so onSessionStart,UserPromptSubmitandPostToolUseaJoinedContextkeeps every source’s context (the builtin fragments first, then each hook) and writes the joined text back after each merge.PreToolUseandStopare left out: there some agents keep context in the field that holds the decision. - Return the merged result to the agent.
- Ensure any
- A hook that exits 2 blocks: dispatch stops, and
blocked_responseturns its stderr into the reason. OnPreToolUsethat is a canonical deny, translated like any other so each agent receives it in the form it honors (Antigravity ignores exit codes, Copilot drops the reason of a failing hook). On other events the process exits 2 with the reason on stderr.
-
Respond: the agent’s handler renders the final output as a
HookResponse(stdout, stderr, exit code). Most agents read everything from stdout; Kiro, whose stdout is plain context, is told to deny a tool through exit 2.
Plugin hooks can respond to agent-specific events (e.g., pre-tool-use, post-tool-use, user-prompt-submit for Claude Code). The available events depend on which agent is in use.