Hook System¶
code-context-agent uses Strands HookProviders to guide agent behavior without modifying tool implementations. Hooks intercept events (tool calls, model invocations, Swarm node transitions) and can log, warn, enrich, or halt execution.
All 9 hook providers live in src/code_context_agent/agent/hooks.py. See Coordinator Pipeline for how hooks integrate with the multi-agent pipeline.
Hook overview¶
| Hook | Level | Event | Purpose |
|---|---|---|---|
ConversationCompactionHook | Agent | BeforeInvocationEvent | Strips large tool payloads from older messages to prevent context window overflow |
OutputQualityHook | Agent | AfterToolCallEvent | Warns when tool outputs exceed 100K chars |
ToolEfficiencyHook | Agent | BeforeToolCallEvent | Warns when shell is used for tasks that have dedicated tools |
ReasoningCheckpointHook | Agent | AfterToolCallEvent | Injects reasoning prompts after key analysis tools to force LLM interpretation |
FailFastHook | Agent | AfterToolCallEvent | Raises FullModeToolError when non-exempt tools return errors in --full mode |
ToolDisplayHook | Agent | Before/AfterToolCallEvent | Updates AgentDisplayState with active tool info for Rich TUI |
JsonLogHook | Agent | Before/AfterToolCallEvent | Emits structured JSON log lines for --quiet mode |
SwarmDisplayHook | Swarm | Before/AfterNodeCallEvent | Tracks Swarm node transitions for multi-agent TUI display |
JsonLogSwarmHook | Swarm | Before/AfterNodeCallEvent | Emits JSON log lines for Swarm agent transitions |
Hook creation¶
create_all_hooks() is the single entry point for assembling all hooks:
agent_hooks, swarm_hooks = create_all_hooks(
full_mode=True, # Include FailFastHook
state=display_state, # AgentDisplayState for TUI (None if quiet)
quiet=False, # Use JsonLogHook instead of display hooks
)
- Agent hooks are registered on each Swarm node agent. They handle tool-level events (
BeforeToolCallEvent,AfterToolCallEvent,BeforeInvocationEvent). - Swarm hooks are registered on the Swarm itself. They handle node-level events (
BeforeNodeCallEvent,AfterNodeCallEvent).
The split ensures agent-level events (tool calls) and swarm-level events (node transitions) are handled by the right providers. The four core agent hooks (ConversationCompactionHook, OutputQualityHook, ToolEfficiencyHook, ReasoningCheckpointHook) are always active. Display and fail-fast hooks are conditional on CLI flags.
Detailed hook descriptions¶
ConversationCompactionHook¶
Fires before every model invocation via BeforeInvocationEvent. Walks the conversation history, skips the last 4 messages (current turn), and replaces tool payloads exceeding 2000 chars with stubs:
Both toolResult content and large toolUse input blocks are compacted. The model's reasoning about tool results is preserved -- only raw data is replaced.
Why this matters
A typical analysis produces 50+ tool calls. Without compaction, accumulated tool results can exhaust the 1M context window before the synthesizer runs.
OutputQualityHook¶
Fires after every tool call. Logs a warning via loguru when the result string exceeds 100,000 characters. This is a diagnostic signal -- it does not block execution, but flags tools that may need pagination or filtering.
ToolEfficiencyHook¶
Fires before every tool call. When the agent calls shell, inspects the command for patterns that have dedicated tools:
| Shell pattern | Recommended tool |
|---|---|
grep / rg | rg_search |
cat | read_file_bounded |
tree | create_file_manifest (never on repo root) |
find | create_file_manifest or rg_search |
The warning is logged but does not block the call. The prompt's steering directives reinforce these preferences so the LLM self-corrects over time.
ReasoningCheckpointHook¶
Fires after key analysis tools complete successfully. Appends a reasoning prompt to the tool result content, forcing the LLM to interpret data before collecting more.
Monitored tools and prompts:
gitnexus_query-- "Which search results are most relevant? Symbols appearing in multiple processes are likely core business logic."gitnexus_context-- "What does this symbol's relationship map reveal? High incoming = foundational. High outgoing = orchestrator."gitnexus_impact-- "How wide is the blast radius? Cross-reference with git_hotspots -- high churn + wide blast radius = fragile bottleneck."git_hotspots-- "Which high-churn files overlap with structurally important symbols from GitNexus?"git_files_changed_together-- "Do these co-change patterns match the structural dependencies from GitNexus? Mismatches indicate implicit coupling."git_blame_summary-- "Single-author files with high centrality = bus factor risk. Many-author files with complex logic = coordination risk."read_file_bounded-- "Compare what you see against what GitNexus context/impact data predicted. What domain invariants does this file maintain?"write_bundle-- "Does every claim have a file:line reference? Did you cross-reference with findings from other teams?"
Prompts are skipped if the tool returned an error.
FailFastHook¶
Only active in --full mode. Fires after every tool call, parses the result as JSON, and raises FullModeToolError if status is "error" and the tool is not exempt.
Exempt tools:
rg_search-- search tools may legitimately return no resultscontext7_resolve-library-id,context7_query-docs-- external MCP tools may be unavailablegitnexus_*-- external MCP tools, best-effortshell-- user-controlled
See Full Mode for the broader --full pipeline.
Display hooks (SwarmDisplayHook, ToolDisplayHook)¶
These hooks update AgentDisplayState, which the Rich Live renderer polls at 2 fps. They replaced AG-UI event streaming in v8.
SwarmDisplayHooktracks which specialist agent is currently active by responding toBeforeNodeCallEvent/AfterNodeCallEvent. It callsstate.set_active_agent()andstate.complete_agent().ToolDisplayHooktracks active tool calls, completed tools, and error counts by responding toBeforeToolCallEvent/AfterToolCallEvent. It createsToolCallStateentries and appends them tostate.completed_tools.
See TUI & Phases for the full display architecture.
JSON log hooks (JsonLogHook, JsonLogSwarmHook)¶
Used in --quiet mode (CI/CD pipelines). Output structured JSON lines via loguru with logger.bind(output="json").
JsonLogHookemitstool_startandtool_endevents with tool name and status (okorerror).JsonLogSwarmHookemitsagent_startandagent_endevents with the Swarm node ID.
Adding a new hook¶
- Create a class inheriting from
HookProviderinagent/hooks.py. - Implement
register_hooks(self, registry: HookRegistry, **kwargs)to register callbacks for typed events. - Callbacks receive typed events (
BeforeToolCallEvent,AfterToolCallEvent,BeforeInvocationEvent,BeforeNodeCallEvent,AfterNodeCallEvent). - Add the hook to
create_all_hooks()in the appropriate list --agent_hooksfor tool/invocation events,swarm_hooksfor node transition events.
class MyCustomHook(HookProvider):
"""One-line description of what this hook does."""
def register_hooks(self, registry: HookRegistry, **kwargs: Any) -> None:
registry.add_callback(AfterToolCallEvent, self._on_tool_end)
def _on_tool_end(self, event: AfterToolCallEvent, **kwargs: Any) -> None:
tool_name = event.tool_use.get("name", "")
# ... custom logic ...
Tip
Keep hooks stateless when possible. If state is needed (e.g., display hooks), accept it via __init__ and store as self._state.