crates/hooks and is wired by crates/agent and crates/gateway.
The design goal is simple: the agent loop should know lifecycle phases and typed contributions. It should not need memory-specific, skills-specific, or future domain-specific branches for every side behavior.
Why hooks exist
Some work must happen around a turn but should not be part of the main model/tool loop:- classify turn policy before prompt assembly;
- add compact context before prompt compilation;
- contribute model-visible tool bundles;
- add prompt sections;
- run post-turn extraction or cleanup;
- persist hook-run diagnostics for recovery and audit.
Core types
Handlers declare capabilities. A hook that contributes tool bundles must declare the tool-bundle capability. A hook that only reads context and writes internal domain state should not be able to emit prompt or tool contributions by accident.
Turn phases
The phase names are stable because they are also useful in diagnostics, hook-run persistence, and future operator tooling.Contributions
Hooks communicate through typed contributions instead of raw JSON blobs.
The agent loop aggregates these sets at phase boundaries. Domain hooks interpret their own policy and context; the loop stays generic.
Execution policy
Each subscription has execution behavior:
Failure policy is separate. Best-effort hooks can fail without failing the turn; required hooks can fail closed. The runtime records safe diagnostics and avoids putting raw sensitive payloads in summaries.
Dependencies and ordering
Subscriptions are sorted by priority and id, while explicit dependencies determine required ordering. A hook can require another subscription on the same phase to run first. The runtime detects missing dependencies, disabled dependencies, and cycles. Memory uses phase ordering for recall: deterministic memory runs atturn.pre_prompt_context, then active memory runs at turn.post_preflight_prompt_context after the internal preflight plan has been built. The active hook receives typed prompt context plus the preflight-owned active recall plan or fallback.
Thread episodic context follows the same rule. The hook/runtime boundary carries typed prompt context and recall contributions; the agent loop should not know how memvid capsules, thread chunks, or artifact refs are searched. It only dispatches the phase and later consumes the compiled prompt context.
Persistence and recovery
Gateway hook-run persistence records lifecycle state for subscribed work:- started;
- completed;
- skipped;
- failed;
- timed out;
- backgrounded;
- latency and attempt metadata;
- safe diagnostics and contribution hashes.
Memory as a hooked domain
Agent memory is the first heavy domain built on this runtime:MemoryPolicyClassifierHookruns atturn.pre_policy.MemoryDeterministicRecallHookruns atturn.pre_prompt_context.ActiveMemoryRecallHookruns atturn.post_preflight_prompt_context.MemoryToolBundleHookruns atturn.pre_tool_materialization.MemoryPromptContractHookruns atturn.pre_prompt_compile.MemoryPostTurnExtractorHookruns atturn.post_turn.
Adding a hook
When adding a hook:- Define typed input/output using existing hook request and contribution types where possible.
- Give the handler a stable id and kind.
- Declare only the capabilities it needs.
- Register it with a phase, priority, execution policy, failure policy, and visibility.
- Add tests for descriptor stability, capabilities, ordering/dependencies, policy behavior, and diagnostics.
- Keep domain decisions inside the hook or its domain service, not in the agent loop.
Related pages
- Agent Loop shows where turn phases are dispatched.
- Memory Architecture shows a full domain built on hooks.
- Prompt And Context explains how prompt sections and context reach the provider.