Hooks are the primary runtime extension point in QitOS. They let you observe and react to every phase transition inside the Engine without modifying the agent itself. This tutorial covers the full hook lifecycle, the context objects hooks receive, and how to write both step-level and tool-level hooks.Documentation Index
Fetch the complete documentation index at: https://qitor.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
Hooks vs. Critics
QitOS has two extension mechanisms that operate at different levels:| Mechanism | Role | Can modify flow? |
|---|---|---|
| Hook | Observation and side effects | No — hooks are notified, they do not control |
| Critic | Control and gating | Yes — critics can veto or revise decisions |
Step 1: The EngineHook Base Class
Every hook inherits fromEngineHook. The base class defines no-op methods for every lifecycle callback, so you only override the ones you need.
| Callback | When it fires |
|---|---|
on_run_start(task, state, engine) | Before the first step |
on_before_step(ctx, engine) | Before a step begins |
on_before_decide(ctx, engine) | Before the LLM is called |
on_after_decide(ctx, engine) | After the LLM returns a decision |
on_before_act(ctx, engine) | Before actions are executed |
on_after_act(ctx, engine) | After actions finish executing |
on_before_critic(ctx, engine) | Before the critic runs |
on_after_critic(ctx, engine) | After the critic finishes |
on_before_reduce(ctx, engine) | Before reduce() folds results into state |
on_after_reduce(ctx, engine) | After reduce() completes |
on_before_check_stop(ctx, engine) | Before the stop condition is evaluated |
on_after_check_stop(ctx, engine) | After the stop condition is evaluated |
on_after_step(ctx, engine) | After the step is fully complete |
on_recover(ctx, engine) | When the engine recovers from an error |
on_run_end(result, engine) | After the final step completes |
| Callback | When it fires |
|---|---|
on_session_start(ctx, engine) | When an interactive session begins |
on_session_end(ctx, engine) | When an interactive session ends |
on_before_compact(ctx, engine) | Before context compaction runs |
on_after_compact(ctx, engine) | After context compaction completes |
on_event(event, state, record, engine) | On every RuntimeEvent emission |
on_step_end(record, state, engine) | When a StepRecord is finalized |
None. Override only the callbacks you care about.
Step 2: HookContext and ToolHookContext
Every step-level callback receives aHookContext dataclass that carries everything a hook might need:
ToolHookContext, which extends HookContext with tool-specific fields:
phase field comes from the RuntimePhase enum:
Step 3: Writing a Custom Logging Hook
A common use case is recording every phase transition for later analysis. Here is aLifecycleRecorderHook that logs each callback with a timestamp and step ID:
LifecycleRecorderHook is safe to add to any run without side effects.
Step 4: Tool-Level Hooks
Tool-level hooks fire around individual tool invocations, giving you fine-grained visibility into which tools the agent calls and what they return.| Callback | Context | Purpose |
|---|---|---|
on_before_tool_use | ToolHookContext (no tool_result yet) | Log or validate before execution |
on_after_tool_use | ToolHookContext (with tool_result) | Inspect or record the result |
on_permission_denied | ToolHookContext (with permission_decision) | Track rejected tool calls |
on_permission_denied to monitor security boundaries without modifying the permission system itself.
Step 5: Registering Hooks with the Engine
Hooks are registered on theEngine instance before calling run():
Full Lifecycle Diagram
on_recover fires instead of the remaining step callbacks for that step, and the engine may retry or abort depending on configuration.
Related guide: Critics
Learn how critics differ from hooks and how to use them for control flow and decision gating.
Next tutorial: Multi-Agent Systems
Build systems with coordinator and worker agents that dispatch tasks in parallel.
