-
Notifications
You must be signed in to change notification settings - Fork 0
Observer Example
A minimal reference observer. Demonstrates the canonical shape of an observer hook that records tool-call activity without affecting the turn. Reference only — normative shape and rules live in Hooks (contract).
An observer hook fires and returns. It cannot vote, it cannot mutate payload. Its job is to see what happened and write that observation to its own state slot, emit an event, or route to a logger sink.
This example counts tool invocations per tool name across the session. The count is useful for analytics ("how often does the user reach for Bash vs Edit?") without changing any behavior. A hook that did the same counting as a guard or transform would pollute a decision path that should be pure.
| Field | Value |
|---|---|
| Kind |
Hook. |
subKind |
observer. |
| Attachment |
TOOL_CALL/post. |
| Firing |
sync or async. This example declares async because the counter write is I/O-bound and the turn should not wait for it. |
asyncTimeoutMs |
1000. Required for async observers per Hooks § Firing mode. |
| Validation severity | Optional by default per Hooks § Validation severity. |
| State slot | Used for per-tool counters. See Extension State. |
flowchart LR
Post[TOOL_CALL/post fires] --> Dispatch[core dispatches observer<br/>on detached task]
Dispatch -->|turn continues| NextTurn[turn stage moves on]
Dispatch --> Read[read result.toolName + success flag]
Read --> Bump[increment state-slot counter]
Bump --> Emit[publish ToolInvocationObserved event]
Emit --> Done[observer returns]
Observer rules visible in the flow:
- Async dispatch does not gate the turn. The turn stage continues immediately. If the counter write is slow, the session is not slowed.
- The counter lives in this observer's own state slot. An observer may not read or write another extension's slot per Hooks § Interaction with core.
-
Failures do not affect the turn. A write error emits a
HookAsyncFailedaudit record but the turn is already past. -
Timeout is enforced by core. If the counter write takes longer than
asyncTimeoutMs, core cancels the task and auditsHookAsyncTimedOut. See Hooks § Firing mode — Cancellation.
| Field | Meaning | Default |
|---|---|---|
enabled |
Whether the hook participates. |
true. |
events[] |
Event names to record. |
["ToolInvocationCompleted", "ToolInvocationFailed"]. |
retentionTurns |
How many turns back to keep per-tool counters. |
100. Counters older than this are pruned on the state-slot write path. |
- It does not decide anything. A result arrives, the observer records, the turn proceeds. That is the entire role. A hook that tries to "observe and then block on certain conditions" is two hooks: an observer plus a guard.
- It does not read env. Observers participate in LLM Context Isolation the same way transforms do — they cannot splice env into LLM-visible payload. This observer doesn't touch LLM payload at all.
- It does not call tools. Only the message loop calls tools.
- It does not drive the Interaction Protocol. An observer that wanted to ask the user something is the wrong pattern — use a tool that prompts, or an SM stage.
Async observers carry the same capability set as sync observers. Non-blocking is not more permissive — the detached task inherits the observer's constraints.
- You need to record something about the turn without affecting it.
- The record can tolerate best-effort semantics — drop on overflow, timeout on stall.
- The record's value does not gate any decision in the current turn.
Do not use an observer for:
- Anything that affects the turn — use a transform (reshape) or a guard (block).
- Durable analytics that must never be dropped — write a Logger sink instead; loggers have bounded queues and drop policy, but they also carry audit records which are never dropped.
- Long-running background processing — that is a Command or a separate process, not a hook.
This observer declares async. A sync observer would work too, with trade-offs:
| Aspect |
sync observer |
async observer |
|---|---|---|
| Blocks turn | Yes, the turn stage awaits. | No, the turn stage continues. |
| Ordering | Participates in the slot's ordering manifest. | Does not — fires in registration order. |
| Cancellation | Coupled to the turn. | Timeout-bounded via asyncTimeoutMs. |
| Typical use | Cheap work (bump a counter in memory). | I/O-bound work (write to disk, emit an event). |
Choose sync when the observer is cheap and deterministic ordering matters; choose async when the observer does I/O the turn should not await.
- Execution Model
- Message Loop
- Concurrency and Cancellation
- Error Model
- Event and Command Ordering
- Event Bus
- Command Model
- Interaction Protocol
- Hook Taxonomy
- Host API
- Extension Lifecycle
- Env Provider
- Prompt Registry
- Resource Registry
- Session Lifecycle
- Session Manifest
- Persistence and Recovery
- Stage Executions
- Subagent Sessions
- Contract Pattern
- Versioning and Compatibility
- Deprecation Policy
- Capability Negotiation
- Dependency Resolution
- Validation Pipeline
- Cardinality and Activation
- Extension State
- Conformance and Testing
- Providers
- Provider Params
- Tools
- Hooks
- UI
- Loggers
- State Machines
- SM Stage Lifecycle
- Stage Definitions
- Commands
- Session Store
- Context Providers
- Settings Shape
- Trust Model
- Project Trust
- Extension Isolation
- Extension Integrity
- LLM Context Isolation
- Secrets Hygiene
- Security Modes
- Tool Approvals
- MCP Trust
- Sandboxing
- Configuration Scopes
- Project Root
- Extension Discovery
- Extension Installation
- Extension Reloading
- Headless and Interactor
- Determinism and Ordering
- Launch Arguments
- Network Policy
- Platform Integration
Tools
UI
Session Stores
Loggers
Providers
Hooks
Context Providers
Commands
- First Run
- Default Chat
- Tool Call Cycle
- Hook Interception
- Guard Deny Reproposal
- State Machine Workflow
- SM Stage Retry
- Hot Model Switch
- Capability Mismatch Switch
- Session Resume
- Session Resume Drift
- Approval and Auth
- Interaction Timeout
- Headless Run
- Parallel Tool Approvals
- Subagent Delegation
- Scope Layering
- Project First-Run Trust
- Reload Mid-Turn
- Compaction Warning
- MCP Remote Tool Call
- MCP Prompt Consume
- MCP Resource Bind
- MCP Reconnect