hooks

package
v2.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 20, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package hooks dispatches shell commands on agent-side event boundaries.

Motivation and shape: an agent produces a stream of tool-call, tool- response, and model-text events. Operators occasionally want to observe these boundaries from outside the process — to write lifecycle files (Scion's agent-info.json via `sciontool hook`), forward alerts to a notification service, feed a custom telemetry pipeline, etc. Rather than building N adapter binaries that each duplicate cmd/core-agent's wiring, this package lets consumers declare `{event: [{command}]}` mappings in .agents/config.json and have the agent spawn those commands with a JSON envelope on stdin whenever an event fires.

The wire shape (JSON payload + `hook_event_name` top-level field) is the same one Scion's `sciontool hook --dialect=<name>` expects, so the Scion integration is just `{"tool-start": [{"command": "sciontool hook --dialect=core-agent"}]}` — no core-agent code knows anything about Scion. Non-Scion consumers see the same envelope and can parse it however they like.

Index

Constants

View Source
const DefaultTimeoutSeconds = 10

DefaultTimeoutSeconds is the per-invocation subprocess timeout applied when a Handler doesn't set TimeoutSeconds. Matches the 10-second cap Scion's Antigravity harness uses for its own hook config, since the same commands (`sciontool hook`) are the primary consumer.

Variables

View Source
var KnownEvents = []string{
	"tool-start",
	"tool-end",
	"model-start",
	"agent-end",
}

KnownEvents is the set of hook event names the dispatcher may fire. Names deliberately match Scion's normalized event vocabulary (pkg/sciontool/hooks/types.go in scion) so a Scion dialect.yaml can use an identity mapping.

New events land here when the dispatcher gains a code path that emits them — no more, no less. Validate() rejects config entries for event names not in this set so typos fail loudly.

Functions

This section is empty.

Types

type Config

type Config map[string][]Handler

Config is the on-disk shape loaded from `.agents/config.json` under `"hooks"`. Keys are hook event names (see KnownEvents); values are the ordered list of handlers to run when the event fires. Empty or nil is valid — the dispatcher becomes a no-op.

Handlers run sequentially per event — parallelism between them would race on stdout/stderr writes and complicate cleanup with negligible wall-clock benefit (handlers are typically fast subprocesses).

func (Config) Validate

func (c Config) Validate() error

Validate rejects malformed configs at startup so operators find out about typos before the agent runs the first turn.

type Dispatcher

type Dispatcher struct {
	// contains filtered or unexported fields
}

Dispatcher fires configured shell commands on agent event boundaries. Construct one per agent (or per session) via New, pass OnEvent and OnTurnEnd to agent.WithEventHook, and the agent tap loop drives the rest.

Concurrency: OnEvent and OnTurnEnd are safe to call from a single goroutine (the agent's event tap). Handlers run synchronously in that goroutine with per-command timeouts — a misbehaving hook stalls the agent for at most Handler.TimeoutSeconds, matching the semantics of Scion's Antigravity harness.

func New

func New(cfg Config, sessionID string, stderr io.Writer) *Dispatcher

New builds a Dispatcher from cfg. sessionID is threaded onto every envelope's `session_id` field (empty is allowed — the field is omitted from the envelope). stderr receives one line per hook that fails; pass io.Discard to suppress.

cfg is copied — mutating the passed-in Config after New has no effect. Validate cfg before calling if you want early config errors.

func (*Dispatcher) Empty

func (d *Dispatcher) Empty() bool

Empty reports whether the dispatcher has no configured handlers. Callers can skip wiring it into the agent when Empty is true to avoid the per-event bookkeeping cost.

func (*Dispatcher) OnEvent

func (d *Dispatcher) OnEvent(ev *session.Event)

OnEvent is the per-event callback. Walks ev's content parts and fires the corresponding hook events:

  • FunctionCall part → "tool-start"
  • FunctionResponse part → "tool-end"
  • First Text+Partial after turn-start or tool-end → "model-start"

Nil or empty events are no-ops.

func (*Dispatcher) OnTurnEnd

func (d *Dispatcher) OnTurnEnd()

OnTurnEnd is the end-of-turn callback. Fires "agent-end" and clears per-turn state. Wired into the same post-turn cleanup that runs watchdog / compaction / checkpoint hooks.

type Handler

type Handler struct {
	// Command is passed to `/bin/sh -c` so pipes, redirections, and
	// shell substitutions work. Matches the Antigravity harness pattern
	// (`jq ... | sciontool hook --dialect=...`).
	Command string `json:"command"`

	// TimeoutSeconds bounds the subprocess wall-clock; the process is
	// killed if it hasn't exited by then. Zero means DefaultTimeoutSeconds.
	// Explicit negative values are rejected by Validate.
	TimeoutSeconds int `json:"timeout_seconds,omitempty"`
}

Handler declares one command to spawn when the parent event fires.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL