Documentation
¶
Overview ¶
Package hooks provides lifecycle hooks for agent tool execution. Hooks allow users to run shell commands or prompts at various points during the agent's execution lifecycle, providing deterministic control over agent behavior.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultRegistry = NewRegistry()
DefaultRegistry is the process-wide registry used by NewExecutor. It is a fully-loaded NewRegistry (HookTypeCommand and HookTypeBuiltin both wired in) with no named builtins registered. Callers that need runtime-owned builtins should construct a private registry rather than mutating the default one.
Functions ¶
This section is empty.
Types ¶
type BuiltinFunc ¶ added in v1.52.0
BuiltinFunc is the signature of an in-process hook handler. It receives the parsed Input (no JSON unmarshaling on the user's side), any per-hook arguments declared as [Hook.Args] in the YAML, and returns a parsed Output, short-circuiting the stdout-as-JSON protocol that command hooks rely on.
Returning a nil Output is fine — it produces a successful no-op result and is useful for fire-and-forget handlers (logging, telemetry, ...).
type Config ¶
type Config struct {
// PreToolUse hooks run before tool execution
PreToolUse []MatcherConfig `json:"pre_tool_use,omitempty" yaml:"pre_tool_use,omitempty"`
// PostToolUse hooks run after tool execution
PostToolUse []MatcherConfig `json:"post_tool_use,omitempty" yaml:"post_tool_use,omitempty"`
// SessionStart hooks run when a session begins
SessionStart []Hook `json:"session_start,omitempty" yaml:"session_start,omitempty"`
// TurnStart hooks run at the start of every agent turn (each model
// call). AdditionalContext is injected transiently and never
// persisted to the session.
TurnStart []Hook `json:"turn_start,omitempty" yaml:"turn_start,omitempty"`
// BeforeLLMCall hooks run just before each model call (after
// turn_start). Output is informational; use turn_start to
// contribute system messages.
BeforeLLMCall []Hook `json:"before_llm_call,omitempty" yaml:"before_llm_call,omitempty"`
// AfterLLMCall hooks run just after each successful model call,
// before the response is recorded and tool calls dispatched.
AfterLLMCall []Hook `json:"after_llm_call,omitempty" yaml:"after_llm_call,omitempty"`
// SessionEnd hooks run when a session ends
SessionEnd []Hook `json:"session_end,omitempty" yaml:"session_end,omitempty"`
// OnUserInput hooks run when the agent needs user input
OnUserInput []Hook `json:"on_user_input,omitempty" yaml:"on_user_input,omitempty"`
// Stop hooks run when the model finishes responding
Stop []Hook `json:"stop,omitempty" yaml:"stop,omitempty"`
// Notification hooks run when the agent sends a notification (error, warning) to the user
Notification []Hook `json:"notification,omitempty" yaml:"notification,omitempty"`
// OnError hooks run when the runtime hits an error during a turn
// (model failures, repetitive tool-call loops). Fires alongside
// Notification with level="error".
OnError []Hook `json:"on_error,omitempty" yaml:"on_error,omitempty"`
// OnMaxIterations hooks run when the runtime reaches its configured
// max_iterations limit. Fires alongside Notification with
// level="warning".
OnMaxIterations []Hook `json:"on_max_iterations,omitempty" yaml:"on_max_iterations,omitempty"`
}
Config represents the hooks configuration for an agent
func FromConfig ¶
func FromConfig(cfg *latest.HooksConfig) *Config
FromConfig converts a latest.HooksConfig to a hooks.Config
type EventType ¶
type EventType string
EventType represents the type of hook event
const ( // EventPreToolUse is triggered before a tool call executes. // Can allow/deny/modify tool calls; can block with feedback. EventPreToolUse EventType = "pre_tool_use" // EventPostToolUse is triggered after a tool completes successfully. // Can provide validation, feedback, or additional processing. EventPostToolUse EventType = "post_tool_use" // EventSessionStart is triggered when a session begins or resumes. // Can load context, setup environment, install dependencies. EventSessionStart EventType = "session_start" // EventTurnStart is triggered at the start of every agent turn (each // model call), AFTER the persisted messages are read but BEFORE the // model is invoked. Its AdditionalContext is appended as transient // system messages for that turn only — it is NOT persisted to the // session, so per-turn signals (date, environment, prompt files) are // recomputed every turn instead of bloating the message history on // every resume. EventTurnStart EventType = "turn_start" // EventBeforeLLMCall is triggered immediately before each model call, // AFTER turn_start has assembled the messages slice. Use this for // observability, cost guardrails, or auditing without contributing // system messages — turn_start is the right event for the latter. // The hook output is currently informational; a future extension may // honor a deny verdict to short-circuit the call. EventBeforeLLMCall EventType = "before_llm_call" // EventAfterLLMCall is triggered immediately after a successful model // call, BEFORE the response is recorded into the session and before // any tool calls are dispatched. Receives the assistant text content // in stop_response (matching the stop event). Failed model calls // fire EventOnError instead. EventAfterLLMCall EventType = "after_llm_call" // EventSessionEnd is triggered when a session terminates. // Can perform cleanup, logging, persist session state. EventSessionEnd EventType = "session_end" // EventOnUserInput is triggered when the agent needs input from the user. // Can log, notify, or perform actions before user interaction. EventOnUserInput EventType = "on_user_input" // EventStop is triggered when the model finishes its response and is about // to hand control back to the user. Can perform post-response validation, // logging, or cleanup. EventStop EventType = "stop" // EventNotification is triggered when the agent emits a notification to the user, // such as errors or warnings. Can send external notifications or log events. EventNotification EventType = "notification" // EventOnError is triggered specifically when the runtime hits an // error during a turn (model failures, repetitive tool-call loops). // Fires alongside EventNotification (level="error") so existing // notification hooks keep working; on_error gives a structured entry // point for users who want to react only to errors. EventOnError EventType = "on_error" // EventOnMaxIterations is triggered when the runtime reaches its // configured max_iterations limit. Fires alongside EventNotification // (level="warning") so existing notification hooks keep working; // on_max_iterations gives a structured entry point for users who // want to react only to that condition (e.g. log to a metrics // pipeline rather than a chat channel). EventOnMaxIterations EventType = "on_max_iterations" )
type Executor ¶
type Executor struct {
// contains filtered or unexported fields
}
Executor handles the execution of hooks.
An Executor resolves each Hook's HookType against a Registry of [HandlerFactory]s. The default executor uses DefaultRegistry, which only knows about the built-in "command" handler; tests and embedders can supply their own registry via NewExecutorWithRegistry to add new handler kinds (in-process Go callbacks, HTTP webhooks, MCP tools, ...) without touching the executor itself.
Public entry points:
- Executor.Dispatch runs the hooks registered for one EventType and aggregates their verdicts into a single Result.
- Executor.Has reports whether any hooks are configured for an event, so callers can avoid building the Input when nothing would run.
func NewExecutor ¶
NewExecutor creates a new hook executor backed by DefaultRegistry.
func NewExecutorWithRegistry ¶ added in v1.52.0
func NewExecutorWithRegistry(config *Config, workingDir string, env []string, registry *Registry) *Executor
NewExecutorWithRegistry creates a new hook executor that resolves hook types against the supplied registry.
This is the seam used to extend the hook system: callers register additional [HandlerFactory]s for new HookType values on a private Registry and pass it here.
func (*Executor) Dispatch ¶ added in v1.52.0
Dispatch runs the hooks registered for event and aggregates their verdicts into a single Result.
For tool-matched events (pre/post_tool_use), only hooks whose matcher matches input.ToolName are run. For all other events the hook list is flat. When no hooks would run, a permissive Result{Allowed: true} is returned without serializing the input.
Dispatch sets input.HookEventName to event so handlers can rely on it without the caller having to remember.
type Handler ¶ added in v1.52.0
type Handler interface {
Run(ctx context.Context, input []byte) (HandlerResult, error)
}
Handler executes a single hook invocation.
A Handler is built by a HandlerFactory for a single Hook and is invoked at most once. The factory is responsible for digesting the Hook (validating it, copying out the fields the handler needs); Run only receives the JSON-encoded Input on stdin and the timeout-bounded context produced by the Executor.
A Handler MUST NOT apply Hook.GetTimeout itself: the executor wraps ctx with the timeout before calling Run. A Handler should respect ctx cancellation and return promptly.
type HandlerEnv ¶ added in v1.52.0
type HandlerEnv struct {
// WorkingDir is the directory in which the handler should run.
WorkingDir string
// Env is the environment to expose to the handler, in os.Environ() form.
Env []string
}
HandlerEnv is the per-executor context passed to handler factories. It carries everything a handler may need that isn't part of the Hook definition itself. New fields can be added in the future without breaking factory signatures.
type HandlerFactory ¶ added in v1.52.0
type HandlerFactory func(env HandlerEnv, hook Hook) (Handler, error)
HandlerFactory builds a Handler for a single hook invocation.
Factories are expected to validate the hook (e.g. that a command handler has a non-empty [Hook.Command]) and return an error if the hook is not runnable. The returned Handler is used for exactly one [Handler.Run] call and may be discarded afterwards.
type HandlerResult ¶ added in v1.52.0
type HandlerResult struct {
// Stdout is the raw standard output produced by the handler.
Stdout string
// Stderr is the raw standard error produced by the handler.
Stderr string
// ExitCode is the handler's exit code. 0 means success; 2 is a
// blocking error; any other non-zero value is treated as a
// non-blocking error by the executor.
ExitCode int
// Output, when non-nil, short-circuits Stdout JSON parsing. In-process
// handlers populate this directly.
Output *Output
}
HandlerResult is the raw result of a single [Handler.Run] call.
A handler can speak to the executor in either of two ways:
The classic process protocol: leave Output nil, write JSON (or plain text) to Stdout, signal blocking with ExitCode == 2, etc. The executor parses Stdout into an Output when ExitCode == 0 and Stdout begins with '{'. This is what command hooks do.
The direct protocol: set Output to a pre-parsed Output. In-process handlers use this to skip the JSON round-trip; ExitCode should stay 0 and Stdout/Stderr can be left empty.
type Hook ¶
type Hook struct {
// Type specifies whether this is a command or prompt hook
Type HookType `json:"type" yaml:"type"`
// Command is the shell command to execute (for command hooks)
Command string `json:"command,omitempty" yaml:"command,omitempty"`
// Args are arbitrary string arguments passed to the hook handler.
// They are interpreted by the handler kind: builtin hooks receive
// them as the args parameter of [BuiltinFunc]; future handler kinds
// (http, mcp, ...) can adopt the same field.
Args []string `json:"args,omitempty" yaml:"args,omitempty"`
// Timeout is the execution timeout in seconds (default: 60)
Timeout int `json:"timeout,omitempty" yaml:"timeout,omitempty"`
}
Hook represents a single hook configuration
func (*Hook) GetTimeout ¶
GetTimeout returns the timeout duration, defaulting to 60 seconds
type HookSpecificOutput ¶
type HookSpecificOutput struct {
// HookEventName identifies which event this output is for
HookEventName EventType `json:"hook_event_name,omitempty"`
// PreToolUse fields
PermissionDecision Decision `json:"permission_decision,omitempty"`
PermissionDecisionReason string `json:"permission_decision_reason,omitempty"`
UpdatedInput map[string]any `json:"updated_input,omitempty"`
// PostToolUse/SessionStart fields
AdditionalContext string `json:"additional_context,omitempty"`
}
HookSpecificOutput contains event-specific output fields
type HookType ¶
type HookType string
HookType represents the type of hook action
const HookTypeBuiltin HookType = "builtin"
HookTypeBuiltin dispatches to a named, in-process Go function registered via Registry.RegisterBuiltin. The function name is stored in [Hook.Command], so we don't need a new YAML key — a builtin hook is written as `{type: builtin, command: "<name>"}`.
const ( // HookTypeCommand executes a shell command HookTypeCommand HookType = "command" )
type Input ¶
type Input struct {
// Common fields for all hooks
SessionID string `json:"session_id"`
Cwd string `json:"cwd"`
HookEventName EventType `json:"hook_event_name"`
// Tool-related fields (for PreToolUse and PostToolUse)
ToolName string `json:"tool_name,omitempty"`
ToolUseID string `json:"tool_use_id,omitempty"`
ToolInput map[string]any `json:"tool_input,omitempty"`
// PostToolUse specific
ToolResponse any `json:"tool_response,omitempty"`
// SessionStart specific
Source string `json:"source,omitempty"` // "startup", "resume", "clear", "compact"
// SessionEnd specific
Reason string `json:"reason,omitempty"` // "clear", "logout", "prompt_input_exit", "other"
// Stop specific
StopResponse string `json:"stop_response,omitempty"` // The model's final response content
// Notification specific
NotificationLevel string `json:"notification_level,omitempty"` // "error" or "warning"
NotificationMessage string `json:"notification_message,omitempty"` // The notification content
}
Input represents the JSON input passed to hooks via stdin
type MatcherConfig ¶
type MatcherConfig struct {
// Matcher is a regex pattern to match tool names (e.g., "shell|edit_file")
// Use "*" to match all tools
Matcher string `json:"matcher,omitempty" yaml:"matcher,omitempty"`
// Hooks are the hooks to execute when the matcher matches
Hooks []Hook `json:"hooks" yaml:"hooks"`
}
MatcherConfig represents a hook matcher with its hooks
type Output ¶
type Output struct {
// Continue indicates whether to continue execution (default: true)
Continue *bool `json:"continue,omitempty"`
// StopReason is the message to show when continue=false
StopReason string `json:"stop_reason,omitempty"`
// SuppressOutput hides stdout from transcript
SuppressOutput bool `json:"suppress_output,omitempty"`
// SystemMessage is a warning to show the user
SystemMessage string `json:"system_message,omitempty"`
// Decision is for blocking operations
Decision string `json:"decision,omitempty"`
// Reason is the message explaining the decision
Reason string `json:"reason,omitempty"`
// HookSpecificOutput contains event-specific fields
HookSpecificOutput *HookSpecificOutput `json:"hook_specific_output,omitempty"`
}
Output represents the JSON output from a hook
func (*Output) ShouldContinue ¶
ShouldContinue returns whether execution should continue
type Registry ¶ added in v1.52.0
type Registry struct {
// contains filtered or unexported fields
}
Registry maps a HookType to a HandlerFactory, and a builtin name to a BuiltinFunc.
A Registry is safe for concurrent use. NewRegistry returns a registry pre-populated with the two universal handler kinds ("command" and "builtin"); embedders register additional handler types or named builtin Go functions on it before passing it to NewExecutorWithRegistry.
The process-wide DefaultRegistry is one such registry, used by NewExecutor. It is convenient for callers that don't have any runtime-owned builtins of their own; callers that do (e.g. the agent runtime) should construct a private registry to keep their builtins out of any shared global state.
func NewRegistry ¶ added in v1.52.0
func NewRegistry() *Registry
NewRegistry returns a registry pre-populated with the two universal handler kinds: HookTypeCommand (shell command hooks) and HookTypeBuiltin (in-process Go callbacks looked up via Registry.RegisterBuiltin).
func (*Registry) Lookup ¶ added in v1.52.0
func (r *Registry) Lookup(t HookType) (HandlerFactory, bool)
Lookup returns the factory registered for t, or (nil, false) if none.
func (*Registry) LookupBuiltin ¶ added in v1.52.0
func (r *Registry) LookupBuiltin(name string) (BuiltinFunc, bool)
LookupBuiltin returns the function registered as name, or (nil, false). Exported primarily for tests and tooling that want to enumerate or validate builtins without going through the Executor.
func (*Registry) Register ¶ added in v1.52.0
func (r *Registry) Register(t HookType, f HandlerFactory)
Register associates a factory with a hook type. A subsequent registration of the same type replaces the previous one.
func (*Registry) RegisterBuiltin ¶ added in v1.52.0
func (r *Registry) RegisterBuiltin(name string, fn BuiltinFunc) error
RegisterBuiltin makes fn callable as `{type: builtin, command: name}` on this registry.
Subsequent registrations of the same name replace the previous one, matching the Registry.Register contract. Empty name or nil fn are rejected.
RegisterBuiltin is safe for concurrent use, but typical callers register from package-level setup or a runtime constructor.
type Result ¶
type Result struct {
// Allowed indicates if the operation should proceed
Allowed bool
// Message is feedback to include in the response
Message string
// ModifiedInput contains any modifications to tool input (PreToolUse only)
ModifiedInput map[string]any
// AdditionalContext is context to add (PostToolUse/SessionStart)
AdditionalContext string
// SystemMessage is a warning to show the user
SystemMessage string
// ExitCode is the exit code from the hook command (0 = success, 2 = blocking error)
ExitCode int
// Stderr contains any error output from the hook
Stderr string
}
Result represents the result of executing hooks