hooks

package
v1.0.0-rc1 Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2026 License: AGPL-3.0 Imports: 8 Imported by: 0

Documentation

Overview

Package hooks is the agent kernel's single extension mechanism: typed hook points with explicit result semantics and error policies.

A hook point is a package-level Point[E, R] descriptor carrying both type parameters, so callers write ToolCallHook.Emit(ctx, reg, ev) without spelling out E and R. The Registry stores handlers type-erased and is copy-on-write: registration takes a mutex, dispatch is a single atomic load. Tool calls run from N goroutines concurrently, so Emit must never block on registration.

Index

Constants

This section is empty.

Variables

View Source
var (
	SessionStart = Point[SessionEvent, struct{}]{Kind: "session_start"}
	SessionEnd   = Point[SessionEvent, struct{}]{Kind: "session_end"}
)
View Source
var BeforeCompact = Point[CompactEvent, CancelResult]{
	Kind:   "before_compact",
	Reduce: StopWhen[CompactEvent](func(r CancelResult) bool { return r.Cancel }),
}
View Source
var BeforeRun = Point[RunStartEvent, RunStartResult]{
	Kind: "before_run",
	Reduce: Fold(func(acc *RunStartResult, ev *RunStartEvent, out RunStartResult) {
		if out.SystemPrompt != nil {

			ev.SystemPrompt = *out.SystemPrompt
			acc.SystemPrompt = out.SystemPrompt
		}
		acc.Prepend = append(acc.Prepend, out.Prepend...)
	}),
}
View Source
var Context = Point[ContextEvent, ContextResult]{
	Kind: "context",
	Reduce: Fold(func(acc *ContextResult, ev *ContextEvent, out ContextResult) {
		if out.Messages == nil {
			return
		}
		ev.Messages = out.Messages
		acc.Messages = out.Messages
	}),
}
View Source
var RunEnd = Point[RunEndEvent, struct{}]{Kind: "run_end"}
View Source
var ToolCallHook = Point[ToolCallEvent, ToolCallResult]{
	Kind:    "tool_call",
	OnError: FailClosed,
	Reduce:  StopWhen[ToolCallEvent](func(r ToolCallResult) bool { return r.Block }),
}

ToolCallHook is fail-closed: a handler that errors out cannot be assumed to have approved the call, so the caller must treat any error as a denial.

View Source
var ToolResult = Point[ToolResultEvent, ToolResultPatch]{
	Kind: "tool_result",
	Reduce: Fold(func(acc *ToolResultPatch, ev *ToolResultEvent, out ToolResultPatch) {

		if out.Content != nil {
			ev.Content = *out.Content
			acc.Content = out.Content
		}
		if out.IsError != nil {
			ev.IsError = *out.IsError
			acc.IsError = out.IsError
		}
		if out.Terminate != nil {
			ev.Terminate = *out.Terminate
			acc.Terminate = out.Terminate
		}
	}),
}

Functions

This section is empty.

Types

type CancelResult

type CancelResult struct {
	Cancel bool
	Reason string
}

type CompactEvent

type CompactEvent struct {
	SessionID     string
	Trigger       string
	ContextTokens int
	ContextWindow int
}

type ContextEvent

type ContextEvent struct {
	SessionID string
	Turn      int
	Messages  []*Msg
}

type ContextResult

type ContextResult struct {
	Messages []*Msg
}

ContextResult replaces the whole message list; nil means unchanged.

type ErrorPolicy

type ErrorPolicy uint8
const (
	ContinueOnError ErrorPolicy = iota // collect, report, keep dispatching
	FailClosed                         // first error aborts dispatch; caller must deny
)

type HandlerError

type HandlerError struct {
	Source string
	Kind   Kind
	Err    error
	Panic  any
	Stack  []byte
}

HandlerError attributes a failure to the handler that produced it. Handlers are registered with a mandatory source so a hook failure never has to be traced back by hand.

func (*HandlerError) Error

func (e *HandlerError) Error() string

func (*HandlerError) Unwrap

func (e *HandlerError) Unwrap() error

type Kind

type Kind string

type Msg

type Msg = aop.Message

Aliases keep event definitions readable without pulling agent in (that would be an import cycle).

type Point

type Point[E any, R any] struct {
	Kind    Kind
	Reduce  Reducer[E, R] // nil => pure observation
	OnError ErrorPolicy
}

func (Point[E, R]) Emit

func (p Point[E, R]) Emit(ctx context.Context, r *Registry, ev E) (R, error)

Emit runs the point's handlers sequentially in registration order and folds their results through Reduce. With no handlers it returns the zero result without allocating.

func (Point[E, R]) On

func (p Point[E, R]) On(r *Registry, source string, fn func(context.Context, E) (R, error)) (unsubscribe func())

On registers fn for this point and returns an idempotent unsubscribe that is safe to call from inside a dispatch. source is mandatory: an unattributable handler cannot be reported when it fails, so an empty source (or a nil fn) is a programming error and panics. A nil registry is not — hooks are optional wiring, so registration on one is a no-op.

type Reducer

type Reducer[E any, R any] func(acc *R, ev *E, out R) (stop bool)

Reducer folds one handler result into the accumulated result. ev is a pointer so fold-style points can let the next handler observe the previous handler's change. Returning true short-circuits the remaining handlers.

func Fold

func Fold[E any, R any](apply func(acc *R, ev *E, out R)) Reducer[E, R]

Fold is the mutation shape: apply merges each result into both the accumulator and the event, so the next handler sees what the previous one changed. It never short-circuits — every handler gets a turn.

func StopWhen

func StopWhen[E any, R any](pred func(R) bool) Reducer[E, R]

StopWhen is the veto shape: the first handler whose result satisfies pred wins and the rest are skipped. Results that fail pred are discarded.

type Registry

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

func New

func New() *Registry

func (*Registry) AddCleanup

func (r *Registry) AddCleanup(fn func()) (remove func())

AddCleanup registers a function to run on Clear. The returned remove is idempotent.

func (*Registry) Clear

func (r *Registry) Clear()

Clear drops every handler and runs the registered cleanups in registration order.

func (*Registry) Has

func (r *Registry) Has(kind Kind) bool

Has is the zero-handler fast path: one atomic load plus a map lookup, no locks and no allocations.

func (*Registry) Len

func (r *Registry) Len(kind Kind) int

func (*Registry) SetErrorSink

func (r *Registry) SetErrorSink(fn func(*HandlerError))

SetErrorSink installs the reporter for handler failures. Passing nil disables reporting; errors are still collected and returned by Emit.

type RunEndEvent

type RunEndEvent struct {
	SessionID      string
	TurnID         string
	Stop           StopReason
	Output         string
	Messages       []*Msg
	MessageCounter int64
	Usage          *Usage
	Err            error
}

type RunStartEvent

type RunStartEvent struct {
	SessionID    string
	TurnID       string
	AgentName    string
	Model        string
	Turn         int
	SystemPrompt string
	ToolNames    []string
}

RunStartEvent carries the config context a handler needs in flattened form, since the event type cannot reference *agent.Config.

type RunStartResult

type RunStartResult struct {
	SystemPrompt *string
	Prepend      []*Msg
}

RunStartResult replaces the system prompt (nil = keep) and prepends messages to the turn.

type SessionEvent

type SessionEvent struct {
	SessionID string
	ParentID  string
	AgentName string
	Model     string
	Reason    string
}

type StopReason

type StopReason string

StopReason lives here rather than in agent because run_end events carry it; agent aliases these back.

const (
	StopReasonCompleted  StopReason = "completed"
	StopReasonTerminated StopReason = "terminated"
	StopReasonStopped    StopReason = "stopped"
	StopReasonBudget     StopReason = "budget"
	StopReasonError      StopReason = "error"
	StopReasonCanceled   StopReason = "canceled"
)

type ToolCall

type ToolCall = aop.ToolCall

Aliases keep event definitions readable without pulling agent in (that would be an import cycle).

type ToolCallEvent

type ToolCallEvent struct {
	SessionID        string
	TurnID           string
	AssistantMessage *Msg
	Call             *ToolCall
	SystemPrompt     string
	Messages         []*Msg
}

type ToolCallResult

type ToolCallResult struct {
	Block  bool
	Reason string
}

type ToolResultEvent

type ToolResultEvent struct {
	SessionID  string
	TurnID     string
	Call       *ToolCall
	Content    string
	IsError    bool
	Terminate  bool
	DurationMs int
	Full       *tool.Result
}

type ToolResultPatch

type ToolResultPatch struct {
	Content   *string
	IsError   *bool
	Terminate *bool
}

ToolResultPatch patches individual fields; nil fields are left alone.

type Usage

type Usage = aop.TokenUsage

Aliases keep event definitions readable without pulling agent in (that would be an import cycle).

Jump to

Keyboard shortcuts

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