Documentation
¶
Overview ¶
Package agent is the single home of the agent model:
- Definition — the serializable document form used by deployment documents: card, tool allow-list, engine selection (EngineRef), resource bindings, and lifecycle hooks by slot (prepare / observe / referees / commit).
- Agent — the assembled runtime form built from a Definition by the deployment layer: identity, card, tools, the constructed engine (Engine), and the attached hooks (Preparer / Observer / Referee / Committer). core/deploy returns this type instead of defining its own bound-agent shape.
- Board / BoardSnapshot — the engine execution blackboard and its serialisable snapshot, shared by engines and checkpointing.
- Checkpoint / CheckpointStore — the resume record envelope and the host-side persistence contract. Concrete stores are resource implementations under checkpoint/ (e.g. checkpoint/workspace, registered as checkpoint.Store/workspace).
- Runtime — Execute (the turn harness), Run / Request / Result / Identity, Host (host-side capabilities), Board, and the lifecycle contracts.
Index ¶
- Constants
- func CapabilityFromHost[T any](h Host) (T, bool)
- func ContainsBoardRef(v any) bool
- func ContextWithHost(ctx context.Context, h Host) context.Context
- func EmitStreamDelta(ctx context.Context, pub Publisher, runID, stepActor string, ...) error
- func EmitStreamPart(ctx context.Context, pub Publisher, runID, stepActor string, part message.Part) error
- func EventBusFromHost(h Host) (event.Bus, bool)
- func ExtractBoardRefs(v any) []string
- func GetTyped[T any](b *Board, key string) (T, bool)
- func Interrupted(intr Interrupt) error
- func IsResumable(eng Engine) bool
- func IsStreamDelta(s event.Subject) bool
- func MergeInterrupts(ctx context.Context, sources ...<-chan Interrupt) <-chan Interrupt
- func PatternAllRuns() event.Pattern
- func PatternRun(runID string) event.Pattern
- func PatternRunSteps(runID string) event.Pattern
- func PatternRunStream(runID string) event.Pattern
- func SanitiseID(id string) string
- func SignalToError(sig *ScriptSignal) error
- func SubjectParallelFork(runID string) event.Subject
- func SubjectParallelJoin(runID string) event.Subject
- func SubjectRunEnd(runID string) event.Subject
- func SubjectRunStart(runID string) event.Subject
- func SubjectStepComplete(runID, stepActor string) event.Subject
- func SubjectStepError(runID, stepActor string) event.Subject
- func SubjectStepStart(runID, stepActor string) event.Subject
- func SubjectStreamDelta(runID, stepActor string) event.Subject
- func SuggestCheckpoint(e Engine) error
- func WithResumeContext(ctx context.Context, rc ResumeContext) context.Context
- func WithRunInfo(ctx context.Context, info RunInfo) context.Context
- type Agent
- type AgentCapabilities
- type AgentCard
- type Artifact
- type BaseObserver
- type BaseReferee
- type Board
- func (b *Board) AppendChannelMessage(name string, msg message.Message)
- func (b *Board) AppendChannelMessages(name string, msgs []message.Message)
- func (b *Board) AppendSliceVar(key string, value any) error
- func (b *Board) Channel(name string) []message.Message
- func (b *Board) ChannelLen(name string) int
- func (b *Board) ChannelTail(name string, count int) []message.Message
- func (b *Board) ChannelView(name string) []message.Message
- func (b *Board) ChannelsCopy() map[string][]message.Message
- func (b *Board) Clone() *Board
- func (b *Board) DeleteVar(key string)
- func (b *Board) GetVar(key string) (any, bool)
- func (b *Board) GetVarString(key string) string
- func (b *Board) LastMessage(name string) (message.Message, bool)
- func (b *Board) PopChannelMessage(name string) (message.Message, bool)
- func (b *Board) Resolve(v any) (any, error)
- func (b *Board) ResolveString(s string) (any, error)
- func (b *Board) RestoreFrom(snap *BoardSnapshot)
- func (b *Board) SetChannel(name string, msgs []message.Message)
- func (b *Board) SetVar(key string, value any)
- func (b *Board) Snapshot() *BoardSnapshot
- func (b *Board) UpdateSliceVarItem(key string, match func(any) bool, update func(any) any)
- func (b *Board) Vars() map[string]any
- type BoardSnapshot
- type Capabilities
- type Cause
- type Checkpoint
- type CheckpointDeleter
- type CheckpointLister
- type CheckpointStore
- type CheckpointSuggester
- type Checkpointer
- type Cloneable
- type CommitView
- type CommitViewProvider
- type CommitViewProviderFunc
- type Committer
- type CommitterFunc
- type Decision
- type Definition
- type DiscardOnInterruptCauses
- type Engine
- type EngineFunc
- type EngineRef
- type ErrorKind
- type EventBusProvider
- type ExecuteOption
- func WithArtifactChannels(channels ...string) ExecuteOption
- func WithAttributes(extra map[string]string) ExecuteOption
- func WithCommitViewProvider(p CommitViewProvider) ExecuteOption
- func WithCommitter(c Committer) ExecuteOption
- func WithHost(h Host) ExecuteOption
- func WithMaxRevise(n int) ExecuteOption
- func WithObserver(o Observer) ExecuteOption
- func WithParentRunID(id string) ExecuteOption
- func WithPreparer(p Preparer) ExecuteOption
- func WithReferee(d Referee) ExecuteOption
- func WithResumeFrom(cp *Checkpoint) ExecuteOption
- func WithRunTimeout(d time.Duration) ExecuteOption
- func WithToolAllowList(names []string) ExecuteOption
- type Hook
- type Host
- type HostCapabilityMask
- type HostFuncs
- func (h HostFuncs) AskUser(ctx context.Context, prompt UserPrompt) (UserReply, error)
- func (h HostFuncs) Checkpoint(ctx context.Context, cp Checkpoint) error
- func (h HostFuncs) Interrupts() <-chan Interrupt
- func (h HostFuncs) Publish(ctx context.Context, env event.Envelope) error
- func (h HostFuncs) ReportUsage(ctx context.Context, usage inference.Usage) error
- func (h HostFuncs) UnwrapHost() Host
- type HostMiddleware
- type HostUnwrapper
- type Identity
- type Interrupt
- type InterruptedError
- type Interrupter
- type LoadAndResumeOption
- type NoopCheckpointStore
- type NoopHost
- func (NoopHost) AskUser(context.Context, UserPrompt) (UserReply, error)
- func (NoopHost) Checkpoint(context.Context, Checkpoint) error
- func (NoopHost) Interrupts() <-chan Interrupt
- func (NoopHost) Publish(context.Context, event.Envelope) error
- func (NoopHost) ReportUsage(context.Context, inference.Usage) error
- type Observer
- type Policy
- type Preparer
- type PreparerFunc
- type ProviderOutputEnvelope
- type Publisher
- type Referee
- type Request
- type RequestConfig
- type Result
- type ResumeContext
- type Resumer
- type Run
- type RunEndPublishError
- type RunInfo
- type ScriptEnv
- type ScriptRuntime
- type ScriptSignal
- type Skill
- type Status
- type SteerSource
- type StreamDeltaPayload
- type StreamDeltaType
- type StreamSink
- type StreamSinkFunc
- type UsageReporter
- type UserPrompt
- type UserPrompter
- type UserReply
Constants ¶
const ( HookSlotPreparer = "prepare" HookSlotObserver = "observe" HookSlotReferee = "referee" HookSlotCommitter = "commit" )
Hook slot names. A hook factory registers under ("hook."+slot, type); the document lists hooks under the matching top-level field (prepare / observe / referees / commit). The four slots mirror the lifecycle stages in core/agent: Preparer, Observer, Referee, Committer.
const BoardRefMarker = "${board:"
BoardRefMarker is the literal prefix of every board reference.
const BoardRefPrefix = "board"
BoardRefPrefix is the scheme-style name of the board reference namespace ("${board:path}"), shared by every layer that resolves references against the agent board at execution time (graph node configs, the script bridge's board.resolve, ...).
const MainChannel = "__main_channel"
MainChannel is the default message channel key.
Channels are an engine-level primitive: they let nodes/steps share ordered message sequences without going through Vars. Convention-level keys for "the chat transcript", "the answer", etc. belong to the agent layer; this package only provides the channel mechanism.
Naming rule: any board key (var or channel) reserved by the engine itself uses the "__" prefix. User-domain code MUST NOT introduce channel or var names beginning with "__"; doing so risks colliding with a future engine-managed slot. Existing reserved names besides MainChannel include graph-level vars VarInterruptedNode and VarToolCalls (see core/graph).
const SubjectPrefix = "agent.run."
SubjectPrefix is the fixed root every engine envelope subject MUST start with. Exposed as a constant so consumers can check strings.HasPrefix without re-deriving it.
Variables ¶
This section is empty.
Functions ¶
func CapabilityFromHost ¶
CapabilityFromHost finds an optional capability implemented by h or by an inner Host explicitly exposed through HostUnwrapper. Typed-nil hosts and capabilities are treated as unsupported.
Only decorators explicitly implementing HostUnwrapper are traversed. A wrapper can stop traversal by returning nil when it replaces the surface that authorises access, and a wrapper that scopes exactly one capability withholds it with HostCapabilityMask instead of taking the rest of the chain down with it.
func ContainsBoardRef ¶ added in v0.1.15
ContainsBoardRef reports whether v — a decoded JSON value or a plain string — contains at least one live (unescaped) "${board:*}" reference.
func ContextWithHost ¶
ContextWithHost returns a derived context carrying h. Engines that dispatch to extension points which were not designed to receive the Host directly (core/tool's Tool.Execute signature, custom plugin callbacks, …) call ContextWithHost before invoking those extensions so the extension can recover the Host via HostFromContext.
The intended consumer pattern:
// engine side: just before invoking the tool registry
ctx = agent.ContextWithHost(ctx, host)
results := reg.ExecuteAll(ctx, calls)
// tool side
host, ok := agent.HostFromContext(ctx)
if ok {
reply, err := host.AskUser(ctx, prompt)
}
nil h is allowed and a no-op (returns ctx unchanged) so callers can plumb a possibly-nil host without conditional branches.
Engines MUST NOT use the host stashed here as a substitute for the host argument they receive in Engine.Execute — the argument is the contract; the context-carried copy is purely a transport for downstream extensions that lack a Host parameter.
func EmitStreamDelta ¶
func EmitStreamDelta(ctx context.Context, pub Publisher, runID, stepActor string, payload StreamDeltaPayload) error
EmitStreamDelta is the low-level form of the EmitStreamX helpers. Custom nodes that need to set fields outside the type-specific helpers (e.g. a forward-compatible Type the SDK does not yet ship a helper for) build the payload themselves and pass it here. Required per-Type fields are validated to mirror the contract enforced by DecodeStreamDelta on the consumer side, so a malformed delta is caught at publish time instead of silently flowing to subscribers.
stepActor follows the contract documented at the top of subjects.go: it MUST start with the executing agent.id (so [PatternRunAgentStream] can fan-in by agent) and MAY append an engine-private suffix (graph runner: ".node.<nodeID>"; iterative engine: ".iter<N>"). Both runID and stepActor are sanitised by SanitiseID so caller-supplied values cannot fragment the resulting subject.
The envelope is stamped with HeaderRunID. The agent identifier is derived from the stepActor segment ahead of any optional ".node." / ".iter" suffix — it goes onto HeaderAgentID. For header-routed subscribers that key off the node id, the HeaderNodeID is populated whenever stepActor carries the graph runner's "<agent>.node.<nodeID>" form so the two transports stay aligned.
Publish errors are returned to the caller (unlike the executor's fire-and-forget convention) so node authors can decide whether to retry or surface the failure; in practice most callers just discard the error because stream deltas are observability, not control flow.
func EmitStreamPart ¶
func EmitStreamPart(ctx context.Context, pub Publisher, runID, stepActor string, part message.Part) error
EmitStreamPart publishes one assistant-token delta on the canonical stream subject. See EmitStreamDelta for the stepActor format requirement.
Use this from any node that produces incremental textual output — for example a custom RAG retriever streaming its working notes, or a post-processing node turning structured data into prose. content may be empty (callers that want "still alive" heartbeats should typically mark them differently); empty content is published as-is so the helper stays predictable. EmitStreamPart publishes one canonical output part — text, image, audio, video, file, data, tool_call, tool_result, or reasoning — as a stream delta. part must be non-nil and valid (see message.Part.Validate). See EmitStreamDelta for the stepActor format requirement.
func EventBusFromHost ¶
EventBusFromHost returns h's borrowed event bus when h implements EventBusProvider. It treats nil interfaces, typed-nil hosts, and typed-nil buses as unsupported. Built-in Host decorators preserve the optional capability without claiming EventBusProvider themselves.
func ExtractBoardRefs ¶ added in v0.1.15
ExtractBoardRefs returns the sorted, deduplicated board paths referenced anywhere inside v. Escaped references and defaults are not included.
func GetTyped ¶
GetTyped retrieves a typed value from the Board's vars. It is a generic helper rather than a method because Go does not allow type parameters on methods.
func Interrupted ¶
Interrupted wraps an Interrupt as an error that satisfies errdefs.IsInterrupted. The recommended usage from an engine is:
case intr := <-h.Interrupts():
return engine.Interrupted(intr)
Hosts inspecting the result use the standard errdefs / errors.As idiom:
if errdefs.IsInterrupted(err) {
var ie engine.InterruptedError
if errors.As(err, &ie) {
switch ie.Cause {
case engine.CauseUserInput: ...
}
}
}
A zero-value Interrupt still produces a well-formed error so callers don't need to special-case CauseUnknown.
func IsResumable ¶
IsResumable reports whether eng implements Resumer. Use the two-value variant when you want the typed handle:
if r, ok := engine.AsResumer(eng); ok { _ = r.CanResume(cp) }
func IsStreamDelta ¶
IsStreamDelta reports whether s is a stream-delta subject. Cheap (string-only) so consumers can filter envelopes before the more expensive payload decode.
Implementation note: matches subjects shaped like "engine.run.<runID>.stream.<stepActor>.delta" — i.e. the prefix is SubjectPrefix, contains ".stream." and ends with ".delta".
func MergeInterrupts ¶
MergeInterrupts fans-in N independent interrupt channels into a single output channel — the natural shape sandbox / pod hosts need to combine "user cancel", "SIGTERM", "budget exceeded", "graceful stop", … into the one channel an Engine reads from [Host.Interrupts].
Behaviour:
- The returned channel is closed once ctx is cancelled OR every non-nil source channel has been closed. Engines reading from it therefore see EOF when "everyone is done", matching how a single cooperative-interrupt channel behaves today.
- Nil source channels are skipped silently — matches the documented "nil means never fires" semantic in Interrupter.Interrupts and keeps callers from having to filter their slice.
- When zero non-nil sources are supplied the returned channel is a never-fires channel that is closed when ctx is cancelled. This keeps the helper total — engines selecting on it stay correct even in the trivial case.
- Order of forwarded interrupts is the natural runtime arrival order across the source channels. No de-duplication: a host that fires "shutdown" through two distinct sources will surface both.
Forwarding goroutines exit promptly when ctx is cancelled, so the helper is safe to use in long-lived hosts that get re-created across reloads.
func PatternAllRuns ¶
PatternAllRuns returns the wildcard pattern matching every event from every run.
engine.run.>
func PatternRun ¶
PatternRun returns the wildcard pattern matching every event of one run, regardless of engine implementation or engine-private extension.
engine.run.<runID>.>
func PatternRunSteps ¶
PatternRunSteps returns the wildcard pattern matching every step lifecycle event (start / complete / error and any engine-private step.* extension such as graph runner's "skipped") of one run.
engine.run.<runID>.step.>
func PatternRunStream ¶
PatternRunStream returns the wildcard pattern matching every stream delta of one run. Use this when you want LLM token / tool deltas but not the step lifecycle events.
Agent-level fan-in ("only agent X's events in this run") is NOT available as a NATS wildcard because the stepActor segment is collapsed into one token by SanitiseID (see the file header). Consumers route by agent through the event.HeaderAgentID envelope header instead — subscribe with PatternRun(runID) and filter on env.AgentID() in the consumer.
engine.run.<runID>.stream.>
func SanitiseID ¶
SanitiseID escapes characters that would corrupt an event.Subject when the input is concatenated into one. event.Subject segments are separated by '.', and '*' / '>' are reserved by event.Pattern for wildcards; any of these in a runID / stepActor would either fragment the subject or turn it into an unintended pattern. SanitiseID replaces each occurrence with '_'.
Empty input becomes "_" so the resulting subject keeps a constant segment count even when the engine forgot to mint an id.
Engines are expected to call SanitiseID on every user-supplied fragment they splice into a subject. The Subject* / Pattern* builders in this file already do so for their parameters; engine implementations only need it when constructing private extensions of their own.
func SignalToError ¶
func SignalToError(sig *ScriptSignal) error
SignalToError maps a script control signal into a Go error that fulfils the appropriate errdefs / engine classification, so host code (scriptnode, future scriptengine, …) can share one consistent translation step instead of branching on sig.Type by hand.
Mapping rules:
- nil signal, Type "done", or unrecognised Type return nil.
- Type "error": errdefs wrapper chosen by Kind; unknown / empty Kind degrades to errdefs.Internal (the value is preserved in the wrapped message so observability surfaces the typo).
- Type "interrupt": Interrupted with Cause taken from Kind (unknown values fall through to CauseCustom) and Detail taken from Message.
Returning nil for an unknown Type matches the existing scriptnode behaviour — only "error" / "interrupt" are observable side-effects.
func SubjectParallelFork ¶
SubjectParallelFork returns the subject an engine publishes when a parallel fan-out wave begins, before any branch starts.
engine.run.<runID>.parallel.fork
func SubjectParallelJoin ¶
SubjectParallelJoin returns the subject an engine publishes when a parallel fan-out wave has merged back into the shared state.
engine.run.<runID>.parallel.join
func SubjectRunEnd ¶
SubjectRunEnd returns the subject every engine MUST publish exactly once when Engine.Execute returns, regardless of outcome (clean completion, interrupt, cancel, failure).
engine.run.<runID>.end
func SubjectRunStart ¶
SubjectRunStart returns the subject every engine MUST publish exactly once when Engine.Execute begins.
engine.run.<runID>.start
func SubjectStepComplete ¶
SubjectStepComplete returns the subject every engine MUST publish when one step finishes successfully. See SubjectStepStart for the stepActor format.
engine.run.<runID>.step.<stepActor>.complete
func SubjectStepError ¶
SubjectStepError returns the subject every engine MUST publish when one step fails (i.e. when it would normally cause Execute to return a non-nil non-interrupt error). See SubjectStepStart for the stepActor format.
engine.run.<runID>.step.<stepActor>.error
func SubjectStepStart ¶
SubjectStepStart returns the subject every engine MUST publish when it begins executing one step. stepActor identifies the unit of work; it MUST start with the executing agent.id (so consumers can reconstruct the agent identity from the subject when the envelope header is unavailable). See the file header for the per-engine suffix conventions (graph: ".node.<id>"; iterative engine: ".iter<N>") and for why agent-level NATS wildcard fan-in goes through the event.HeaderAgentID header instead of the subject.
engine.run.<runID>.step.<stepActor>.start
func SubjectStreamDelta ¶
SubjectStreamDelta returns the subject every engine MUST use when emitting an in-flight increment from the step identified by stepActor — the canonical example is one LLM token, one dispatched tool call, or one tool result. See SubjectStepStart for the stepActor format.
Payload MUST decode to a StreamDeltaPayload; see its docs for the per-Type field requirements.
engine.run.<runID>.stream.<stepActor>.delta
func SuggestCheckpoint ¶
SuggestCheckpoint asks the engine for a voluntary checkpoint when the engine implements CheckpointSuggester; otherwise it is a no-op. Returns the engine's error directly so the host can log / retry; nil is returned both for "engine does not support suggestion" and for "engine accepted the suggestion".
func WithResumeContext ¶
func WithResumeContext(ctx context.Context, rc ResumeContext) context.Context
WithResumeContext returns a derived context carrying rc. Engines that publish telemetry attribs (attempt count, resume signal) pull from here instead of plumbing extra parameters.
func WithRunInfo ¶
WithRunInfo returns a derived context carrying info. Run identity is AMBIENT: deep components (script bindings, stream adapters, helpers far below the engine's call sites) read it from the context instead of receiving it through every intermediate signature. Engines inject it once at the run boundary.
Types ¶
type Agent ¶
type Agent struct {
ID string `json:"id"`
Card AgentCard `json:"card,omitzero"`
Tools []string `json:"tools,omitempty"`
Policy Policy `json:"-"`
Engine Engine `json:"-"`
Prepare []Preparer `json:"-"`
Observe []Observer `json:"-"`
Referees []Referee `json:"-"`
Commit []Committer `json:"-"`
}
Agent is the assembled, runnable form of a Definition: identity, card, tool allow-list, the constructed engine, and the attached lifecycle hooks. The deployment layer builds it from a Definition; hosts and runtimes consume it. The hook slices map 1:1 to the four slots declared on Definition.
Execute can be driven either by Agent.Engine (assembled agents) or by an engine passed explicitly to Execute (which wins).
func (*Agent) Close ¶ added in v0.1.11
Close releases the resources held by the agent's engine and lifecycle hooks. Components that do not implement io.Closer are skipped, and close errors are aggregated with errors.Join. Idempotency of each component is that component's responsibility. Close is safe on a nil receiver and on nil/typed-nil components.
type AgentCapabilities ¶
type AgentCapabilities struct {
Streaming bool `json:"streaming,omitempty"`
PushNotifications bool `json:"pushNotifications,omitempty"`
StateTransitionHistory bool `json:"stateTransitionHistory,omitempty"`
}
AgentCapabilities declares which optional A2A protocol features the agent supports. JSON keys exactly match the A2A spec — note the plural PushNotifications and the longer StateTransitionHistory.
type AgentCard ¶
type AgentCard struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Skills []Skill `json:"skills,omitempty"`
DefaultInputModes []string `json:"defaultInputModes,omitempty"`
DefaultOutputModes []string `json:"defaultOutputModes,omitempty"`
Capabilities AgentCapabilities `json:"capabilities,omitempty"`
}
AgentCard describes an agent's capabilities for discovery. Field names and JSON tags are a proper subset of the A2A AgentCard specification: every field marshals to the exact key A2A readers expect, and AgentCard never collides with an A2A field by using a different name.
What is intentionally NOT here: url / version / provider / documentationUrl / authentication — these belong to "how the agent is exposed as a service", a deployment concern owned by the protocol layer.
type Artifact ¶
Artifact is a named bundle of typed parts produced during a run (e.g. "summary", "tool_output_image"). Engines that write artifacts store them in a board channel; agent collects channel contents into Artifacts on the way out.
type BaseObserver ¶
type BaseObserver struct{}
BaseObserver provides no-op default implementations of every Observer method. Embed it in custom observers that only care about a subset of the lifecycle:
type metricsObserver struct {
agent.BaseObserver
metrics Metrics
}
func (o *metricsObserver) OnRunEnd(ctx context.Context, id agent.Identity, res *agent.Result) {
o.metrics.Record(res)
}
func (BaseObserver) OnInterrupt ¶
func (BaseObserver) OnInterrupt(context.Context, Identity, Interrupt)
OnInterrupt is a no-op.
func (BaseObserver) OnRunEnd ¶
func (BaseObserver) OnRunEnd(context.Context, Identity, *Result)
OnRunEnd is a no-op.
func (BaseObserver) OnRunRevise ¶
OnRunRevise is a no-op.
func (BaseObserver) OnRunStart ¶
func (BaseObserver) OnRunStart(context.Context, Identity, *Request)
OnRunStart is a no-op.
type BaseReferee ¶
type BaseReferee struct{}
BaseReferee provides no-op default implementations of every Referee method. Embed it in custom referees that only override After.
type Board ¶
type Board struct {
// contains filtered or unexported fields
}
Board is the engine execution blackboard: typed message channels plus untyped control vars, both protected by the same mutex.
Thread safety: every public method takes a mutex. Concurrent reads use RLock; mutations use Lock. The contract matches the original graph.Board it replaces — callers that previously held graph.Board across goroutines do not need to add any new locking.
Ownership: a message that reaches a channel is immutable from then on. Every mutating path clones on the way in, and nothing in core rewrites a message already on a channel, which is what lets read paths share the stored value instead of paying for a defensive copy — that is how Board.ChannelView serves its caller and what the script-side channel() projection reads through. A caller that wants to keep editing a message it appended, or one it read back, must clone it first.
Board is intentionally ignorant of agent concepts. It does not know what "messages", "answer" or "run id" mean; those names are established by callers. Per-execution metadata (ID, Attributes, Deps) belongs on Run, not here.
func LoadAndResume ¶
func LoadAndResume( ctx context.Context, eng Engine, host Host, store CheckpointStore, run Run, board *Board, opts ...LoadAndResumeOption, ) (*Board, error)
LoadAndResume is the canonical host-side helper for "either continue the existing run or start fresh". It:
- Loads the most recent checkpoint for run.RunID from store.
- If a checkpoint exists, validates it against the engine's Resumer (if implemented) — invalid checkpoints surface immediately rather than after a partial Execute.
- Populates run.ResumeFrom and threads a ResumeContext onto ctx so the engine, observers and middleware see consistent replay metadata.
- Calls eng.Execute and returns its result.
store may be nil; that is treated as "no checkpoints persisted" and is equivalent to a fresh-start with WithFreshStartAllowed honoured. board is the bootstrap board used on fresh starts and when the engine wishes to keep the host-supplied initial state (the engine itself decides whether to override with ResumeFrom.Board per the Run.ResumeFrom contract).
LoadAndResume is intentionally a one-shot helper, not a retry loop: a supervisor that wants exponential backoff or budget enforcement composes LoadAndResume with its own retry policy.
func NewBoard ¶
func NewBoard() *Board
NewBoard creates an empty Board with an initialised main channel so callers can Board.AppendChannelMessage without a nil-check.
func RestoreBoard ¶
func RestoreBoard(snap *BoardSnapshot) *Board
RestoreBoard reconstructs a Board from a snapshot. Passing nil returns a fresh empty board so resume code can use this unconditionally.
func (*Board) AppendChannelMessage ¶
AppendChannelMessage appends a message to a channel, creating the channel on demand.
func (*Board) AppendChannelMessages ¶ added in v0.4.7
AppendChannelMessages appends msgs to a channel in order, creating the channel on demand. The batch lands under one lock, so a concurrent reader sees either none of it or all of it, and every message is copied: later mutations by the caller do not affect the Board.
func (*Board) AppendSliceVar ¶
AppendSliceVar atomically appends a value to a slice stored in a board variable. It returns an error if the existing value is not a []any (instead of silently overwriting), so callers cannot lose data by typo'ing a key already used for a non-slice value.
func (*Board) Channel ¶
Channel returns a copy of messages for the given channel. An empty or missing channel returns a nil slice (not a zero-length slice) so callers can use len() == 0 uniformly.
func (*Board) ChannelLen ¶ added in v0.4.7
ChannelLen returns the number of messages on a channel without copying them. Callers that only need the count should use it: Board.Channel deep-copies the whole channel, which a len() check pays for nothing.
func (*Board) ChannelTail ¶ added in v0.4.7
ChannelTail returns a copy of the last count messages of a channel, in channel order. A non-positive count, an empty channel and a missing channel all yield nil; a count past the channel's length yields the whole channel. Only the tail is copied, so inspecting the end of a long channel does not pay for its head.
func (*Board) ChannelView ¶ added in v0.4.7
ChannelView returns a read-only view of a channel's messages: the returned slice belongs to the caller (assigning, reordering and slicing its elements is safe), while the messages and their parts are shared with the Board and must never be mutated.
It exists for read paths that hand the messages straight to a projection (encoding, marshalling, size accounting) where Board.Channel's deep copy is pure overhead. Anything that retains what it reads, mutates it, or hands it to third-party callers must use Board.Channel instead.
func (*Board) ChannelsCopy ¶
ChannelsCopy returns a deep copy of all channel message lists. Used by parallel branch execution to give each branch an independent view that can later be merged.
func (*Board) Clone ¶
Clone returns an independent copy of the board: channels and vars are deep-copied, the new board has its own mutex, and mutations on either board do not affect the other. Use it from a Preparer that wants to extend an existing board's state without mutating the previous link's output.
func (*Board) GetVarString ¶
GetVarString retrieves a board variable as a string. It returns "" when the key is missing or the stored value is not a string.
func (*Board) LastMessage ¶ added in v0.4.7
LastMessage returns a copy of the last message on a channel. The boolean is false for an empty or missing channel, matching Board.PopChannelMessage. Use it instead of reading the whole channel to look at its tail.
func (*Board) PopChannelMessage ¶ added in v0.2.1
PopChannelMessage removes and returns the last message on a channel. The boolean reports whether a message was removed; an empty or missing channel yields the zero Message and false. The returned message is the channel's stored copy and must not be mutated.
func (*Board) Resolve ¶ added in v0.1.15
Resolve expands board references throughout v, a decoded JSON value (strings, []any, map[string]any, or leaf scalars). References inside object keys are rejected: config keys are part of the node type's contract and must stay constant.
func (*Board) ResolveString ¶ added in v0.1.15
ResolveString expands board references in s. When s is exactly one reference, the variable's typed value (or typed default) is returned; otherwise the interpolated string is returned. Missing references fail with a validation error unless a default is given.
func (*Board) RestoreFrom ¶
func (b *Board) RestoreFrom(snap *BoardSnapshot)
RestoreFrom overwrites this board from a snapshot. Used by retry / rollback paths inside an executor: the executor takes a snapshot before a risky step, then restores the same Board (preserving its identity for nodes that captured a pointer to it) on failure.
func (*Board) SetChannel ¶
SetChannel replaces the entire message list for a channel. The input slice is copied; later mutations by the caller do not affect the Board.
func (*Board) Snapshot ¶
func (b *Board) Snapshot() *BoardSnapshot
Snapshot returns a serialisable copy of the current state. Vars are deep-copied so the snapshot is safe to retain after further Board mutations; values implementing Cloneable are duplicated through their Clone method, otherwise reflection is used.
func (*Board) UpdateSliceVarItem ¶
UpdateSliceVarItem finds and updates the first matching item in a slice variable. Missing keys and non-slice values are silently ignored — the typical use is "update the entry I just appended", and the caller has already verified the slice exists.
type BoardSnapshot ¶
type BoardSnapshot struct {
Vars map[string]any `json:"vars"`
Channels map[string][]message.Message `json:"channels,omitempty"`
}
BoardSnapshot is a serialisable representation of board state, used for resume / checkpoint flows. It carries no live mutex and is safe to JSON-encode.
func (*BoardSnapshot) Clone ¶
func (s *BoardSnapshot) Clone() *BoardSnapshot
Clone returns an independent deep copy of the snapshot. Use it when retaining a snapshot after the producing board mutates further, or when a checkpoint store needs caller-owned copies on Save/Load.
type Capabilities ¶
type Capabilities struct {
// SupportsResume reports whether Execute can honour
// Run.ResumeFrom. Engines that always return errdefs.NotAvailable
// for a non-nil ResumeFrom MUST leave this false; deployments
// enforcing a restart-always policy need the true case to recover
// mid-run state without losing partial work.
SupportsResume bool `json:"supports_resume,omitempty" yaml:"supports_resume,omitempty"`
// EmitsUserPrompt reports whether the engine may call
// Host.AskUser during Execute. Hosts deploying in headless /
// batch mode use this to refuse engines that would block waiting
// for a reply that nobody can supply.
EmitsUserPrompt bool `json:"emits_user_prompt,omitempty" yaml:"emits_user_prompt,omitempty"`
// EmitsCheckpoint reports whether the engine writes Checkpoints
// during Execute (independently of SupportsResume — an engine
// can write checkpoints that only an external tool can replay).
// Hosts use this to decide whether to attach a CheckpointStore.
EmitsCheckpoint bool `json:"emits_checkpoint,omitempty" yaml:"emits_checkpoint,omitempty"`
}
Capabilities describes the optional features an engine kind declares to its host. The declaration is made once, statically, when the engine kind is registered — engine factories expose it through a capability interface that core/deploy and hosts assert — never probed per instance at run time. Hosts read capabilities to:
- validate a deployment spec at Apply time (e.g. a restart policy requiring resume is rejected when the engine kind does not claim SupportsResume);
- decide which host capabilities to wire (attach a CheckpointStore only when EmitsCheckpoint is claimed);
- refuse to run an engine that needs user prompts in a headless deployment (EmitsUserPrompt=true on a host without an interactive channel becomes a fail-fast).
All fields default to "do not claim the capability" (zero value).
type Cause ¶
type Cause string
Cause classifies why a run was asked to stop. The agent layer maps these onto its higher-level commit/discard policy (e.g. discard the partial output on user_input, commit it on host_shutdown).
Engines should NEVER branch on Cause for control flow — Cause is metadata for the host, not a directive for the engine. The engine's only correct response to any cause is "stop cleanly and return".
const ( // CauseUnknown is the zero value. Hosts should avoid sending it; // it exists so a zero-value Interrupt is recognisable. CauseUnknown Cause = "" // CauseUserCancel is a user-initiated cancel ("stop talking", // "abort this turn"). Output is typically discarded. CauseUserCancel Cause = "user_cancel" // CauseUserInput is a barge-in: the user spoke / typed and the // agent should yield to fresh input. Output is typically discarded. CauseUserInput Cause = "user_input" // CauseHostShutdown is a graceful shutdown from the host. // Output should typically be committed if any was produced. CauseHostShutdown Cause = "host_shutdown" // CauseCustom carries a host-defined cause in [Interrupt.Detail]. CauseCustom Cause = "custom" )
type Checkpoint ¶
type Checkpoint struct {
// ExecID identifies the engine execution this checkpoint belongs
// to. MUST equal the producing [Run.RunID].
ExecID string `json:"exec_id"`
// Steps is an opaque, engine-defined marker set locating "where"
// the run is. For graph it is the most recently completed wave —
// the resume anchor (the frontier is rebuilt from it) and the
// "paused at" display (last element). A single-node wave degrades
// to a one-element slice; empty means "no position recorded".
// The host treats these as opaque strings.
Steps []string `json:"steps,omitempty"`
// Iteration is an optional monotonic counter for engines that
// loop (graph re-entry counter, scheduler tick, …). Zero is fine
// when the engine doesn't track iterations.
Iteration int `json:"iteration,omitempty"`
// Board is the Board state at the boundary. Always non-nil.
Board *BoardSnapshot `json:"board"`
// Payload is engine-specific extra state the engine wants to
// persist alongside the Board. Treated as opaque JSON by the
// store; the producing engine is the only consumer that knows
// how to decode it.
Payload json.RawMessage `json:"payload,omitempty"`
// Attributes mirrors [Run.Attributes] at the time the checkpoint
// was produced (run id at the agent layer, tenant, graph id, …).
// Stores may use these for indexing/lookup.
Attributes map[string]string `json:"attributes,omitempty"`
// Timestamp is the wall-clock time the engine produced the
// checkpoint. Hosts may overwrite when they actually persist.
Timestamp time.Time `json:"timestamp"`
// OriginalStartedAt is the wall-clock time the original (fresh)
// run started. Stays constant across resumes so dashboards
// computing total wall time (e.g. SLO budget burn) don't reset
// every time a host re-loads the checkpoint and resumes.
//
// Engines SHOULD copy this from [ResumeContext.StartedAt] (when
// resuming) or from the time they began the fresh run (when
// producing the first checkpoint). Hosts driving resume via
// [LoadAndResume] thread the value through automatically — the
// helper reads OriginalStartedAt off the loaded checkpoint and
// stamps it back on the next ResumeContext.
//
// Zero time means "not recorded" (engines that ship before this
// field was added, or that don't track wall time). Consumers
// should fall back to Timestamp in that case.
OriginalStartedAt time.Time `json:"original_started_at,omitzero"`
// SpecVersion identifies the engine's spec / definition version
// at the time the checkpoint was produced. The format is
// engine-defined: graph runner uses the graph definition's
// Version (falling back to its Name when Version is empty); a
// script engine could store a content hash; a host application
// could compose a spec document version.
//
// CanResume implementations compare this against the engine's
// current version: a mismatch means the underlying spec has
// drifted (graph re-edited, script reloaded, application spec
// reapplied with a new agent definition) and silently resuming
// would replay against semantics the original run never saw.
// Engines that detect drift surface errdefs.NotAvailable from
// CanResume so the host can either fall back to a fresh run or
// surface the mismatch to the operator.
//
// Empty means "no version recorded" — older checkpoints, or
// engines that have no concept of versioned spec. CanResume
// MUST treat empty as "skip drift check" rather than "always
// fail" so old checkpoints stay loadable.
SpecVersion string `json:"spec_version,omitempty"`
}
Checkpoint is the engine-agnostic persistence record produced at a safe boundary during execution. Each engine decides what its own step marker / payload looks like; this struct only owns the common envelope shape.
Engines populate Checkpoint and hand it to Checkpointer.Checkpoint (the host method). The host is responsible for writing it durably; engines must not assume the call has persisted anything.
func (Checkpoint) Clone ¶
func (cp Checkpoint) Clone() Checkpoint
Clone returns an independent deep copy of cp. Stores should clone on Save (the caller keeps ownership of cp) and again on Load (the returned record is owned by the caller), so concurrent callers can never observe each other's mutations.
func (Checkpoint) Validate ¶
func (cp Checkpoint) Validate() error
Validate checks the envelope invariants every engine and every durable store can rely on. Engine-specific fields (Steps, Iteration, Payload shape, SpecVersion) are intentionally left to the engine's own Resumer implementation so engines can keep minimal or strict admission policies.
type CheckpointDeleter ¶
CheckpointDeleter optionally extends CheckpointStore with the ability to delete a single execution's checkpoints. Used by the host when a run completes successfully and its checkpoints are no longer needed. Implementations must be safe for concurrent use.
type CheckpointLister ¶
CheckpointLister optionally extends CheckpointStore with the ability to enumerate persisted exec ids. Stores that support listing satisfy this interface; agent-level resume / dashboard code can type-assert to it. Implementations must be safe for concurrent use. The returned slice is caller-owned.
type CheckpointStore ¶
type CheckpointStore interface {
Save(ctx context.Context, cp Checkpoint) error
Load(ctx context.Context, execID string) (*Checkpoint, error)
}
CheckpointStore is the host-side persistence contract. The host's Checkpointer.Checkpoint implementation typically delegates to a CheckpointStore. The interface is intentionally narrow: Save persists; Load returns the most-recent persisted record for the given exec id, or (nil, nil) if absent.
Save must atomically replace the record for cp.ExecID; when two Save calls for the same exec id overlap, the later call wins. The store must treat cp as caller-owned and deep-copy before persisting. Load must return a caller-owned copy: mutating the returned checkpoint must not affect the store or other Load results. All methods must be safe for concurrent use.
type CheckpointSuggester ¶
type CheckpointSuggester interface {
SuggestCheckpoint() error
}
CheckpointSuggester is the optional engine-side interface a host uses to ask the engine to write a Checkpoint at the next safe boundary — typically before a voluntary restart, scale-down, or reschedule.
Semantics (advisory, not synchronous):
- The engine SHOULD call its host's Checkpointer at the next point in execution where Checkpoint.Steps is well-defined. It is NOT obligated to interrupt itself; SuggestCheckpoint returns immediately with no guarantee that the checkpoint has been written by the time it returns.
- The host typically pairs SuggestCheckpoint with a follow-up Interrupt after a grace period: "save what you can, then stop".
- Engines that have no notion of a step boundary (purely memory-resident, sub-second runs) MAY treat this as a no-op.
Unlike Capabilities this is a *behavioural* interface — the engine has to do something — so it stays an interface assertion rather than a spec field.
type Checkpointer ¶
type Checkpointer interface {
Checkpoint(ctx context.Context, cp Checkpoint) error
}
Checkpointer persists a checkpoint at a safe boundary the engine has reached. The host decides whether to actually write; engines must not assume durability.
Hosts without configured checkpointing should make this a no-op (return nil) rather than an error so engines can call it unconditionally.
type Cloneable ¶
type Cloneable interface {
Clone() any
}
Cloneable may be implemented by values stored in Board vars to provide a type-safe deep copy instead of the reflection fallback used by Board.Snapshot and RestoreBoard.
type CommitView ¶
type CommitView struct {
// LastBoard is materialized into the shallow Result copy passed to
// Committers. It must be non-nil.
LastBoard *Board
}
CommitView is the board projection a CommitViewProvider exposes only to Committer hooks. The engine-produced board remains authoritative for the Result returned to the caller and delivered to Observer.
type CommitViewProvider ¶
type CommitViewProvider interface {
CommitView(ctx context.Context, id Identity, req *Request, res *Result) (CommitView, error)
}
CommitViewProvider builds a Committer-only projection after the final Referee decision accepts the result and immediately before the Committer chain runs.
Providers run only when at least one Committer is registered and the final Result is committed. They MUST honor ctx cancellation and MUST NOT mutate req or res. Returning an error, or a CommitView with a nil LastBoard, skips every Committer and fails finalization.
type CommitViewProviderFunc ¶
type CommitViewProviderFunc func(ctx context.Context, id Identity, req *Request, res *Result) (CommitView, error)
CommitViewProviderFunc adapts a function to CommitViewProvider.
func (CommitViewProviderFunc) CommitView ¶
func (f CommitViewProviderFunc) CommitView( ctx context.Context, id Identity, req *Request, res *Result, ) (CommitView, error)
CommitView calls f.
type Committer ¶
type Committer interface {
Commit(ctx context.Context, id Identity, req *Request, res *Result) error
}
Committer persists or durably enqueues the final accepted result of a run. It is the reliable side-effect boundary between Referee and Observer:
- Referees decide whether the final result is accepted.
- Committers make an accepted result durable and may fail the run.
- Observers react best-effort after commitment has succeeded or failed.
Committers run synchronously in registration order and only when the final Result has Committed set. When a CommitViewProvider is configured, Committers receive a shallow Result copy materialized from its projected board; callers and Observers retain the engine result. Revise attempts and discarded or non-completed results are never committed. Implementations MUST treat Identity.RunID as the operation's idempotency key because a caller may retry after an ambiguous storage or transport failure. A Referee's Revise request that the configured budget does not honor is advisory and does not by itself clear Committed; Referees that require the output to be withheld must also set DiscardOutput.
A Committer MUST honor ctx cancellation and MUST NOT mutate req or res. Multiple Committers do not form a transaction: if a later Committer fails, earlier ones may already have succeeded. Prefer one Committer that writes to a transactional store or durable outbox when atomicity across side effects matters.
type CommitterFunc ¶
CommitterFunc adapts a function to Committer.
type Decision ¶
type Decision struct {
// AcceptOutput, when true, positively accepts the engine-produced
// output regardless of Status. This is primarily used to retain
// useful partial output from a cooperative interruption through the
// normal Committer chain.
//
// DiscardOutput has final precedence when both directives are
// present, including when they come from different Referees.
AcceptOutput bool
// DiscardOutput, when true, instructs Run to mark Result.Committed
// = false regardless of Status. Committers are skipped for a
// discarded run.
//
// Setting DiscardOutput on a StatusCompleted run is allowed and
// useful for moderation hooks ("the answer violates policy, do
// not persist it").
DiscardOutput bool
// Revise asks agent.Execute to discard this attempt's output and
// re-invoke Execute with a fresh board (re-seeded from
// the original Request). Honoured ONLY when the per-call
// [WithMaxRevise] budget allows another attempt; the option
// defaults to 0, so by default Revise is recorded as a
// finalize_reason but does NOT trigger another engine call —
// callers must opt in explicitly to avoid runaway loops on
// faulty Referees. An unhonored Revise does not change Committed;
// set DiscardOutput as well when the current output must not be
// persisted.
//
// When honoured the lifecycle is:
//
// 1. Referee returns Revise=true (and optionally Reason).
// 2. Run fires [Observer.OnRunRevise] with the about-to-be-
// discarded Result and the next attempt index.
// 3. Board is re-seeded via the Preparer chain; the
// same Run identifier is reused so observers that
// key by run id can correlate attempts.
// 4. Execute runs again. The Referee chain runs again
// on the new Result.
// 5. The loop exits when either Revise=false or the attempt
// counter reaches WithMaxRevise. The final Result.Attempts
// reflects how many Execute calls were made.
//
// The Reason field is exposed via [Result.State]["finalize_reason"]
// regardless of whether the Revise was honoured, so callers
// can audit why a particular attempt did not commit.
Revise bool
// Reason, when non-empty, is propagated into
// [Result.State]["finalize_reason"] and is also returned to the
// caller through the same field. Referees that want to attribute
// a decision in logs (e.g. "moderation:violation") set this
// rather than relying on log scraping.
//
// Composition rule: the first non-empty Reason wins. This means
// Referees later in the chain should only set Reason if they
// have something more specific to add.
Reason string
// State carries optional structured metadata the referee wants
// attached to [Result.State] (e.g. a handoff event). Referees MUST
// NOT mutate res directly; this is the only channel for adding
// result metadata. Entries are merged after the referee chain, with
// later referees winning on key conflicts.
State map[string]any
}
Decision is the return type of Referee.After. The zero value means "no opinion" — agent applies its defaults.
Defaults agent.Run uses when no Referee returns a directive:
- StatusCompleted runs are committed.
- StatusInterrupted / StatusCanceled / StatusAborted / StatusFailed runs are NOT committed (their partial output is dropped from the transcript view). This matches the conservative behaviour Round A had hard-coded; round B simply makes it overridable.
type Definition ¶
type Definition struct {
Card AgentCard `json:"card,omitzero"`
Tools []string `json:"tools,omitempty"`
Engine EngineRef `json:"engine,omitzero"`
Policy *Policy `json:"policy,omitempty"`
Prepare []Hook `json:"prepare,omitempty"`
Observe []Hook `json:"observe,omitempty"`
Referees []Hook `json:"referees,omitempty"`
Commit []Hook `json:"commit,omitempty"`
}
Definition is the document form of an agent: the identity card, the tool allow-list, the engine selection (itself a resource), the resource bindings, and the lifecycle hooks by slot. The assembled runtime form is Agent, built from a Definition by the deployment layer.
func (Definition) Validate ¶
func (d Definition) Validate() error
Validate checks the definition DTO.
type DiscardOnInterruptCauses ¶
type DiscardOnInterruptCauses struct {
Reason string
// contains filtered or unexported fields
}
DiscardOnInterruptCauses is a Referee factory: when ANY of the named causes fires, DiscardOutput is set so a barge-in (or equivalent host-side abort) keeps partial assistant output out of the persistent transcript.
Construct it with NewDiscardOnInterruptCauses and register as one of Agent.Referees or via WithReferee.
func NewDiscardOnInterruptCauses ¶
func NewDiscardOnInterruptCauses(reason string, causes ...Cause) *DiscardOnInterruptCauses
NewDiscardOnInterruptCauses builds a Referee that marks a run discarded whenever its interrupt cause matches one of causes. Reason is the string used as Decision.Reason when discarding fires, and is also surfaced in Result.State["finalize_reason"].
func (*DiscardOnInterruptCauses) After ¶
func (d *DiscardOnInterruptCauses) After(_ context.Context, _ Identity, _ *Request, res *Result) (Decision, error)
After implements Referee. It inspects res.Status and the interrupt cause attached to the run, returning a discarding Decision when both are present and the cause matches.
type Engine ¶
type Engine interface {
Execute(ctx context.Context, run Run, host Host, board *Board) (*Board, error)
}
Engine is the deliberately thin contract every local execution engine satisfies so the agent layer can drive it through a uniform shape. Concrete engines (core/graph DAG executor, future script- based engines, …) usually expose richer APIs in addition to this method.
Contract:
Execute MUST run to completion, until interrupted, or until ctx is cancelled.
On clean completion, return the final Board (often the same pointer as the input — engines mutate in place by default) and a nil error.
On cooperative interrupt (host sent through host.Interrupts()), return the (partial) Board together with the result of Interrupted. The error then satisfies errdefs.IsInterrupted(err) and can be destructured into an InterruptedError for the cause.
On ctx cancellation, return the (partial) Board and ctx.Err().
On any other failure, return the (partial) Board together with a domain error (preferably classified via errdefs). Returning a non-nil board on error lets the host decide whether to commit / discard / persist.
When run.ResumeFrom is non-nil, Execute resumes from that checkpoint instead of starting fresh. See Run.ResumeFrom for the resume contract; engines that do not support resume MUST return an errdefs.NotAvailable-classified error rather than silently restarting.
Engines MUST NOT close any host-owned channel and MUST NOT mutate run.Attributes or run.ResumeFrom.
type EngineFunc ¶
EngineFunc adapts a plain function to the Engine interface. Useful for test doubles and trivial engines.
type EngineRef ¶
type EngineRef struct {
Kind resource.Kind `json:"kind"`
Impl string `json:"impl,omitempty"`
Deps resource.Deps `json:"deps,omitempty"`
Settings json.RawMessage `json:"settings,omitempty"`
}
EngineRef selects the agent engine resource and its bindings. It is the document-layer reference: the runtime execution contract will be a separate Engine interface (not yet in core), so the reference deliberately does not reuse that name.
type ErrorKind ¶
type ErrorKind string
ErrorKind enumerates the errdefs categories scripts may set on signal.error({ kind, message }). Only categories that can plausibly originate from script logic are exposed — auth, rate-limit, timeout and similar wire-level classifications stay in the Go layer where they are produced.
Unknown kinds passed by a script degrade to ErrorKindInternal in SignalToError so a typo in script land cannot escape as an unclassified error.
const ( // ErrorKindValidation marks user-input / config validation // failures. Maps to [errdefs.Validation] (HTTP 400). ErrorKindValidation ErrorKind = "validation" // ErrorKindNotFound marks a lookup the script performed that // returned nothing. Maps to [errdefs.NotFound] (HTTP 404). ErrorKindNotFound ErrorKind = "not_found" // ErrorKindBudgetExceeded marks a per-run / per-tenant budget // (token, cost, iteration count) the script self-detected. // Maps to [errdefs.BudgetExceeded] (HTTP 429). ErrorKindBudgetExceeded ErrorKind = "budget_exceeded" // ErrorKindPolicyDenied marks a policy-layer refusal raised from // inside the script (tool allow-list, role check, …). Maps to // [errdefs.PolicyDenied] (HTTP 403). ErrorKindPolicyDenied ErrorKind = "policy_denied" // ErrorKindNotAvailable marks a transient unavailability of a // dependency the script needed (e.g. a knowledge store down). // Maps to [errdefs.NotAvailable] (HTTP 503). ErrorKindNotAvailable ErrorKind = "not_available" // ErrorKindInternal is the default fallback for uncategorised // script-side errors. Maps to [errdefs.Internal] (HTTP 500). ErrorKindInternal ErrorKind = "internal" )
type EventBusProvider ¶
EventBusProvider is an optional Host capability for consumers that need to subscribe to the same event surface used by Publisher.
The Host (or its owning engine/runtime) owns the returned bus and its lifecycle. Consumers only borrow it: they may subscribe and publish through the Host, but must not close the bus.
EventBusProvider is intentionally not part of Host. Hosts that only publish events do not need to manufacture a subscribable bus, and NoopHost deliberately does not implement this capability.
type ExecuteOption ¶
type ExecuteOption func(*execConfig)
ExecuteOption configures one Run invocation. Options are stateless and may be reused across calls.
func WithArtifactChannels ¶
func WithArtifactChannels(channels ...string) ExecuteOption
WithArtifactChannels names the Board channels Run should harvest into Result.Artifacts on the way out. One Artifact per listed channel: Artifact.Name = channel name; Artifact.Parts = flat concatenation of every Part across every Message in the channel (in board-write order). Channels that hold no messages after the run produce no Artifact entry — empty channels do not pollute the result with empty bundles.
This is the writer side of the Result.Artifacts contract that contract-audit #6 flagged: the godoc had been promising "engines store them in a board channel; agent collects channel contents into Artifacts on the way out" since v0.1, but no agent.Run code path actually performed the collection so the field was permanently nil for every caller.
MUST NOT include MainChannel — Run already promotes that channel into Result.Messages and a duplicate harvest would surface the same payload twice with confusing semantics (Messages keeps role + tool metadata; Artifacts is the modality-bundle view). Run silently skips MainChannel if the caller mistakenly passes it.
Example: an engine that writes a "summary" markdown blob and an "audio" TTS clip to dedicated channels:
res, _ := agent.Execute(ctx, ag, eng, req,
agent.WithArtifactChannels("summary", "audio"))
for _, a := range res.Artifacts {
switch a.Name { ... }
}
Multiple WithArtifactChannels calls accumulate (deduped at collection time) so per-agent and per-call lists compose. nil / empty input is a no-op.
func WithAttributes ¶
func WithAttributes(extra map[string]string) ExecuteOption
WithAttributes adds extra attributes that flow into Run.Attributes alongside the well-known agent_id / run_id / task_id / context_id keys. Caller-supplied keys win on conflict; agent does not overwrite.
Engines that need caller-supplied metadata read Run.Attributes via the same codepath as the well-known keys. Callers serialise any non-string values at the call site and pass the resulting map[string]string here.
func WithCommitViewProvider ¶
func WithCommitViewProvider(p CommitViewProvider) ExecuteOption
WithCommitViewProvider installs the Committer-only result projection provider for this call. It runs after the final Referee decision and only when an accepted Result has at least one Committer to invoke.
Multiple calls use last-write-wins semantics. A final nil or typed-nil provider disables an earlier provider for this call.
func WithCommitter ¶
func WithCommitter(c Committer) ExecuteOption
WithCommitter registers a Committer for this run. Committers run in registration order after any [Agent.Committers], and only for the final accepted result. The first error stops the chain and is returned from Execute.
func WithHost ¶
func WithHost(h Host) ExecuteOption
WithHost installs the Host passed to the
Host is the single extension point for every host-side capability the engine needs: event publishing (Publisher), interrupt injection (Interrupter), user prompting (UserPrompter), checkpoint persistence (Checkpointer), and token-usage reporting (UsageReporter). Composing your own host is how you wire any of those — agent does not provide narrow shortcuts because most non-trivial deployments share state across capabilities (a single metric client, a single OTel tracer, a single request-scoped logger) and a host implementation is the cleanest place to keep that state.
A host that also supports event subscriptions may implement the optional EventBusProvider. The host or engine owns that bus and closes it at the end of its lifecycle; engine consumers only borrow it and must not close it.
Embed NoopHost in your host struct and override only the methods you actually need:
type myHost struct {
NoopHost
bus event.Bus
intrCh <-chan Interrupt
}
func (h *myHost) Publish(ctx context.Context, e event.Envelope) error {
return h.bus.Publish(ctx, e)
}
func (h *myHost) Interrupts() <-chan Interrupt { return h.intrCh }
When omitted, agent falls back to NoopHost, which silently drops envelopes, never fires interrupts, refuses AskUser, drops checkpoints, and discards usage. That default is appropriate for fire-and-forget batch runs and tests — anything else needs a real host.
func WithMaxRevise ¶
func WithMaxRevise(n int) ExecuteOption
WithMaxRevise sets the upper bound on Execute invocations per Run call when a Referee returns Decision{Revise: true}.
n <= 1 (default 0) disables the revise loop entirely. A Referee asking to revise records its Reason but Run still returns after the first attempt — the safe default avoids surprise infinite loops on misconfigured After.
n >= 2 caps total attempts at n. The loop exits as soon as either no Referee asks for revise OR the attempt counter reaches n. The final Result.Attempts is the actual number of Execute calls made.
Revise restarts re-seed the board from the original Request via the configured Preparer, so the engine sees fresh inputs. Run.ResumeFrom is dropped after the first attempt — Revise means "retry from scratch", not "replay a checkpoint".
Negative values are treated as 0 (disabled). Callers that want the engine to drive its own retry policy (rate-limit backoff, transient LLM errors, …) MUST keep WithMaxRevise at the default; the revise loop is the agent-policy layer, not the engine-transport one.
func WithObserver ¶
func WithObserver(o Observer) ExecuteOption
WithObserver registers a Observer for this run. Multiple observers can be registered; they fire in registration order, after any [Agent.Observers] declared on the agent value. Panics inside an observer are caught and dropped.
func WithParentRunID ¶
func WithParentRunID(id string) ExecuteOption
WithParentRunID stamps every Run this call dispatches with the supplied parent run id (Run.ParentRunID). Use it when one agent.Run is spawned by another (multi-agent call chain, handoff, sub-agent dispatch) so dashboards / pod controllers can reconstruct the call tree and apply loop-detection / depth budgets against a stable correlation key.
The empty string is a no-op; passing the parent's runID (typically obtained from agent.Identity.RunID inside an Observer / Referee on the parent run) is the canonical use. agent.Execute does NOT auto-derive ParentRunID from any ambient context — explicit is the only contract that survives ctx propagation rewrites and cross-process dispatch (application runtimes, A2A bridges).
Engines / hosts that don't read ParentRunID are unaffected. The field is also surfaced under telemetry.AttrParentRunID by engine telemetry (the graph runner stamps it onto the run span).
func WithPreparer ¶
func WithPreparer(p Preparer) ExecuteOption
WithPreparer appends a Preparer to this run's chain. The chain runs after agent applies any [Agent.Preparers] declared on the agent value, so a call-site Preparer can build on top of agent-level preparation (history load, system prompt, etc.).
When no Preparer is registered anywhere, agent seeds a default board that appends req.Message to MainChannel and copies req.Inputs into board vars.
func WithReferee ¶
func WithReferee(d Referee) ExecuteOption
WithReferee registers a Referee for this run. Multiple deciders can be registered; they fire in registration order, after any Agent.Referees declared on the agent value. Their decisions are merged via OR over boolean fields; the first non-empty Reason wins.
func WithResumeFrom ¶
func WithResumeFrom(cp *Checkpoint) ExecuteOption
WithResumeFrom replays an interrupted run from a previously captured Checkpoint. The agent threads cp into Run.ResumeFrom and overrides the run id to cp.ExecID so the underlying engine's Resumer.CanResume sees ExecID == Run.RunID (cross-run checkpoints are programmer errors and surface as errdefs.Validation from the engine).
Typical use: a host loaded a checkpoint via its CheckpointStore, possibly after a process restart, and wants the agent to keep going from that point rather than start fresh. The host still passes the ORIGINAL agent.Request (same task id, same inputs); the engine restores board state from the checkpoint so the re-seeded inputs are effectively overwritten by the resumed state. Engines without Resumer surface NotAvailable (per the Engine contract); resume against an unsupported engine is a configuration error, not silent fall-through.
nil cp is a no-op (= fresh start). Multiple WithResumeFrom calls last-write-wins; agent does not attempt to merge checkpoints.
func WithRunTimeout ¶ added in v0.4.1
func WithRunTimeout(d time.Duration) ExecuteOption
WithRunTimeout bounds the wall-clock duration of one Execute call — seeder, every revise attempt, Referees and Committers included.
Use it when an engine's own timeout is per-Execute: that budget restarts on every attempt, so with a revise budget of N the logical run can take up to N × engine-timeout. WithRunTimeout caps the sum. The engine timeout still applies; the shorter deadline wins.
- d > 0 installs the bound;
- d <= 0 is a no-op (no run-level bound; the caller's context rules). This is the default.
A resume is a separate Execute call and therefore gets a fresh window; Policy.RunTimeout declares the same bound on the document form and is overridden by this option.
func WithToolAllowList ¶
func WithToolAllowList(names []string) ExecuteOption
WithToolAllowList overrides Agent.Tools for this call — the caller-supplied list wins wholesale (no merge), mirroring the "caller-supplied wins" rule WithAttributes documents. Pass it from tests or from power-user flows that narrow an agent's tool gate for one specific turn.
type Hook ¶
type Hook struct {
Type string `json:"type"`
Deps resource.Deps `json:"deps,omitempty"`
Settings json.RawMessage `json:"settings,omitempty"`
}
Hook is one agent lifecycle hook entry: the hook type (looked up in the resource registry under "hook.<slot>"), its resource bindings, and its opaque settings. All four slots share the same data shape.
type Host ¶
type Host interface {
Publisher
Interrupter
UserPrompter
Checkpointer
UsageReporter
}
Host is the contract a runtime exposes to a running engine.
Host is intentionally a *composition* of small, single-method interfaces — Publisher, Interrupter, UserPrompter, Checkpointer. The composition exists to keep Engine.Execute readable; downstream code (graph nodes, tools, …) should depend on the smallest interface it actually needs:
// A pure-mapping node only emits events:
func (n *MapNode) Execute(ctx, board, pub engine.Publisher) error
// A streaming LLM node also wants the interrupt channel:
func (n *LLMNode) Execute(ctx, board,
pub engine.Publisher, intr engine.Interrupter) error
Host implementations must be safe for concurrent use. The engine may invoke any method from any goroutine.
func ComposeHost ¶
func ComposeHost(base Host, mws ...HostMiddleware) Host
ComposeHost returns base wrapped by every middleware in mws, in declaration order (first = outermost). Returns base unchanged when mws is empty.
func HostFromContext ¶
HostFromContext returns the Host attached to ctx by a previous ContextWithHost call, plus an "ok" flag. The ok=false branch means either no engine wired the host into ctx (the extension is running outside an engine) or the caller passed a nil Host.
Extensions that require host capabilities should treat ok=false as a usage error and surface it via the extension's own error contract (e.g. ask_user surfaces errdefs.NotAvailable so LLMs see "I cannot prompt the user" instead of crashing).
type HostCapabilityMask ¶ added in v0.4.6
type HostCapabilityMask interface {
// MaskedCapability is the optional capability type withheld from consumers
// reached through this decorator's inner Host, or nil when the decorator
// withholds nothing. Implementations name an interface type:
//
// func (h steerlessHost) MaskedCapability() reflect.Type {
// return reflect.TypeFor[SteerSource]()
// }
MaskedCapability() reflect.Type
}
HostCapabilityMask is the opt-in contract for Host decorators that withhold one optional capability from CapabilityFromHost while leaving the rest of the chain traversable.
HostUnwrapper covers the other shape: a decorator that replaces a surface and therefore hides everything below it. A capability that is scoped to one run rather than replaced has no such boundary — withholding it must not cost consumers the capabilities they still legitimately read (delegation service, event bus, …). A decorator that owns such a capability names it here, and traversal keeps unwrapping while the named capability resolves as unsupported.
The mask applies to the inner chain: the decorator's own method set is consulted first, exactly as for any other Host, so a decorator that means to withhold a capability must not implement it itself. Masks do not cancel — once a decorator withholds a capability, no decorator further inside can re-expose it.
type HostFuncs ¶
type HostFuncs struct {
Inner Host
PublishFn func(ctx context.Context, env event.Envelope) error
InterruptsFn func() <-chan Interrupt
AskUserFn func(ctx context.Context, prompt UserPrompt) (UserReply, error)
CheckpointFn func(ctx context.Context, cp Checkpoint) error
ReportUsageFn func(ctx context.Context, usage inference.Usage) error
}
HostFuncs is the func-field adapter that lets a middleware decorate just the Host methods it cares about while delegating the rest to an underlying Host. Construct one with the inner host as Inner and override only the func fields you need:
wrapped := agent.HostFuncs{
Inner: base,
ReportUsageFn: func(ctx context.Context, u inference.Usage) error {
// budget enforcement here
return base.ReportUsage(ctx, u)
},
}
Every nil func field falls through to Inner so partial decorators stay short. Inner MUST be non-nil; a nil Inner is a programming bug and triggers a panic at the first delegated call.
func (HostFuncs) Checkpoint ¶
func (h HostFuncs) Checkpoint(ctx context.Context, cp Checkpoint) error
Checkpoint routes through CheckpointFn or Inner.
func (HostFuncs) Interrupts ¶
Interrupts routes through InterruptsFn or Inner.
func (HostFuncs) ReportUsage ¶
ReportUsage routes through ReportUsageFn or Inner.
func (HostFuncs) UnwrapHost ¶
UnwrapHost preserves optional capabilities only when Publish still delegates to Inner. A custom publisher may use a different event surface, so PublishFn is an authoritative boundary that stops capability traversal.
type HostMiddleware ¶
HostMiddleware decorates a Host with policy / observability / resource-management behaviour. The pod / agent layer typically stacks several middlewares (audit → rate-limit → budget → secret-resolve) around a base Host built from a PodSpec; this type and the ComposeHost helper exist so the stack can be declared as a slice instead of N levels of struct embedding.
Convention:
- Middleware ordering matches the slice order: ComposeHost(base, A, B, C) returns C(B(A(base))). The first middleware in the slice is the OUTERMOST wrapper and therefore runs first when an engine calls a Host method. This matches how HTTP middleware stacks are normally declared.
- Each middleware MUST return a Host value that delegates unchanged for any sub-interface it does not specifically decorate. The HostFuncs adapter is provided to make this easy: zero-value func fields delegate to the wrapped Host.
- Middlewares are invoked from any goroutine — implementations must be safe for concurrent use, mirroring the Host contract.
func TracingMiddleware ¶
func TracingMiddleware() HostMiddleware
TracingMiddleware returns a HostMiddleware that wraps every Host method call in an OTel span. It is the default observability layer for engines that emit envelopes, checkpoint, or talk back to users — making the cross-process surface visible in any trace UI without per-engine instrumentation.
Spans created (one per Host call):
- engine.host.publish — attribs: messaging.destination (subject), event.id, event.run_id (from envelope headers when present)
- engine.host.checkpoint — attribs: checkpoint.run_id, checkpoint.node_id, checkpoint.seq
- engine.host.ask_user — attribs: prompt.kind
- engine.host.report_usage — attribs: llm.provider, llm.model, llm.tokens.input, llm.tokens.output
Errors set the span status to Error and record the error.
Span lifetimes are tight (the wrapped call) so this middleware does NOT decorate Interrupts() — that returns a long-lived channel and a span around it would either be a single point-in- time event (uninteresting) or last for the entire run (better modeled by the engine's own outer span).
Compose it with other middlewares using ComposeHost; place it near the OUTER end of the stack so its spans wrap the work done by inner middlewares (rate limiting, budget enforcement, etc.).
type HostUnwrapper ¶
type HostUnwrapper interface {
UnwrapHost() Host
}
HostUnwrapper is the opt-in contract for Host decorators that preserve access to optional capabilities on an inner Host.
UnwrapHost is read-only: callers may inspect the returned Host for capabilities but do not replace the decorator's inner Host. Decorators must return nil when crossing their boundary would bypass an authoritative replacement surface. For example, HostFuncs does not unwrap when PublishFn replaces the inner publisher.
type Identity ¶
type Identity struct {
// AgentID identifies the [Agent] producing this run. Engines
// stamp it onto envelope headers (event.HeaderAgentID) and use
// it as the agent segment of step subjects.
AgentID string `json:"agent_id,omitempty"`
// RunID is the unique identifier of this execution. The host
// generates it and keeps it stable across resume / checkpoint
// cycles; revise attempts reuse the same RunID so observers see
// one logical run with N attempts.
RunID string `json:"run_id,omitempty"`
// ParentRunID identifies the parent run when this run was
// dispatched by another agent (multi-agent call chains). Empty
// for top-level runs. Typed (rather than an attribute) so
// loop / depth detection at the pod layer can rely on the
// contract.
ParentRunID string `json:"parent_run_id,omitempty"`
// TaskID scopes this run to a long-running A2A task. Empty when
// the caller did not supply one.
TaskID string `json:"task_id,omitempty"`
// ConversationID names the thread-of-interaction scope (A2A
// contextId semantics). Empty when the turn is unscoped.
ConversationID string `json:"conversation_id,omitempty"`
}
Identity is the strongly-typed identity bundle for one execution. It is a typed value (rather than loose attributes) so the downstream executor sees the same identity the harness minted.
type Interrupt ¶
Interrupt is the value the host sends through Host.Interrupts() to ask the running engine to stop. It is also the payload of the Interrupted error so the host can introspect why.
Interrupt is a plain value — copy it freely.
type InterruptedError ¶
type InterruptedError = interruptedErr
InterruptedError is the concrete error type returned by Interrupted. It is exported so hosts can use errors.As to destructure it; the preferred way to produce one is Interrupted, not direct construction.
Implements errdefs interrupted-marker so errdefs.IsInterrupted returns true on any error wrapping (or equal to) one of these.
type Interrupter ¶
type Interrupter interface {
Interrupts() <-chan Interrupt
}
Interrupter exposes the host's cooperative-interrupt channel.
Engines select on the returned channel between steps:
select {
case intr := <-h.Interrupts():
return engine.Interrupted(intr)
case <-ctx.Done():
return ctx.Err()
default:
}
A nil channel means "no cooperative interrupt available"; engines should treat it as "never fires" — receiving on nil blocks forever, which is the correct semantic.
type LoadAndResumeOption ¶
type LoadAndResumeOption func(*loadAndResumeOpts)
LoadAndResumeOption tunes LoadAndResume behaviour.
func WithFreshStartAllowed ¶
func WithFreshStartAllowed(allowed bool) LoadAndResumeOption
WithFreshStartAllowed controls behaviour when the store has no checkpoint for run.RunID. Default true — execute fresh. Pass false to require an existing checkpoint and surface errdefs.NotFound when none is present (useful for "resume only" admin commands).
func WithResumeAttempt ¶
func WithResumeAttempt(n int) LoadAndResumeOption
WithResumeAttempt sets ResumeContext.Attempt. The default of 1 is correct on fresh runs; supervisors implementing retry loops should bump this for each re-attempt.
func WithResumeSignal ¶
func WithResumeSignal(s string) LoadAndResumeOption
WithResumeSignal sets ResumeContext.Signal for the about-to-run execution. Defaults to "manual".
func WithResumeStartedAt ¶
func WithResumeStartedAt(t time.Time) LoadAndResumeOption
WithResumeStartedAt sets ResumeContext.StartedAt. Default is time.Now() at the moment LoadAndResume is invoked. Pass an earlier timestamp when continuing a long-running run so dashboard "total wall time" remains accurate.
type NoopCheckpointStore ¶
type NoopCheckpointStore struct{}
NoopCheckpointStore drops every checkpoint and reports no state. Use as a default when checkpointing is not configured.
func (NoopCheckpointStore) Load ¶
func (NoopCheckpointStore) Load(context.Context, string) (*Checkpoint, error)
Load satisfies CheckpointStore.
func (NoopCheckpointStore) Save ¶
func (NoopCheckpointStore) Save(context.Context, Checkpoint) error
Save satisfies CheckpointStore.
type NoopHost ¶
type NoopHost struct{}
NoopHost is a zero-cost Host implementation that discards events, never reports interrupts, refuses user prompts, and skips checkpoints. It is meant for tests and embedded scenarios where an engine is invoked outside any real runtime.
func (NoopHost) AskUser ¶
AskUser returns errdefs.NotAvailable so engines can detect that user interaction is unsupported in this host.
func (NoopHost) Checkpoint ¶
func (NoopHost) Checkpoint(context.Context, Checkpoint) error
Checkpoint discards the checkpoint.
func (NoopHost) Interrupts ¶
Interrupts returns nil so engines that select on it block forever on that case (i.e. interrupts never fire under NoopHost).
type Observer ¶
type Observer interface {
// OnRunStart fires after Run prepared the engine inputs but
// before Execute is invoked. id carries the immutable
// identification fields agreed for this turn.
OnRunStart(ctx context.Context, id Identity, req *Request)
// OnInterrupt fires only when the engine returned an interrupt
// error. It runs before OnRunEnd. intr carries the structured
// reason supplied by the host.
OnInterrupt(ctx context.Context, id Identity, intr Interrupt)
// OnRunRevise fires when a Referee asked agent.Execute to re-invoke
// Execute (Decision{Revise: true}) AND the
// per-call WithMaxRevise budget allows another attempt. It
// runs after the discarded attempt's classification but BEFORE
// the next OnRunStart, so observers see the lifecycle as:
//
// OnRunStart → Execute → OnRunRevise → OnRunStart → Execute → OnRunEnd
//
// prevRes is the (about-to-be-replaced) Result from the failed
// attempt — observers MUST treat it as read-only. nextAttempt
// is the 1-indexed attempt number the next Execute will
// be (== prevRes.Attempts + 1).
//
// OnRunRevise fires zero times for runs that complete on the first
// attempt or whose Referee never asks for revise.
OnRunRevise(ctx context.Context, id Identity, prevRes *Result, nextAttempt int)
// OnRunEnd fires after Execute returned and Run finished
// classifying the outcome. res is the same pointer Run is about
// to return; observers MUST treat it as read-only.
OnRunEnd(ctx context.Context, id Identity, res *Result)
}
Observer is a read-only lifecycle hook that lets callers react to stages of a Run without affecting its outcome. It is the plumbing behind agent's "metric emit on start", "transcript snapshot on interrupt", notifications, and similar best-effort patterns, none of which agent hard-codes any more.
Design rules:
Observers MUST NOT change the Result returned by Run. agent intentionally exposes the Result to OnRunEnd by pointer because it is the same value the caller will receive — observers may stash references to it (for logging, async append, …) but mutating it leaves agent's caller staring at the mutation. Treat this surface as advisory.
Observer methods MUST NOT return an error. Failures inside an observer are the observer's problem; they MUST NOT propagate into Run. Use a Committer for durable side effects that must report failure, or a Referee for policy decisions that alter whether a turn is accepted.
Observer methods are called synchronously from Run on the caller's goroutine. Blocking inside them blocks the run. Long-running side effects MUST be dispatched asynchronously by the observer itself.
Run guarantees the call sequence: OnRunStart fires exactly once before Execute; OnInterrupt fires at most once and ONLY when the engine returned an InterruptedError (foreign-shape errors that merely satisfy errdefs.IsInterrupted still classify the run as interrupted but skip OnInterrupt); OnRunEnd fires exactly once after Execute returns, regardless of outcome.
Embed BaseObserver to satisfy the interface with no-op defaults when only a subset of the lifecycle is interesting:
type metricsObserver struct {
agent.BaseObserver
metrics Metrics
}
func (o *metricsObserver) OnRunEnd(ctx context.Context, id agent.Identity, res *agent.Result) {
o.metrics.Record(res)
}
type Policy ¶
type Policy struct {
// MaxRevise bounds how many times a Referee may ask Execute to
// re-invoke the engine. Zero means one attempt (no revise).
MaxRevise int `json:"max_revise,omitempty"`
// RunTimeout, when set, bounds the wall clock of one Execute call
// (every revise attempt included) as a Go duration string, e.g.
// "10m". Empty means no run-level bound. Engines keep their own
// per-Execute timeouts; the shorter deadline wins.
RunTimeout string `json:"run_timeout,omitempty"`
// ArtifactChannels names board channels collected into
// Result.Artifacts.
ArtifactChannels []string `json:"artifact_channels,omitempty"`
}
Policy is the per-call harness policy declared on the document form and applied by Execute unless overridden per call.
type Preparer ¶
type Preparer interface {
Before(ctx context.Context, id Identity, req *Request, prev *Board) (*Board, error)
}
Preparer builds the initial Board for a run, building on the board left by the previous link in the chain.
A chain of Preparers is a linear pipeline: agent runs them in registration order, threading the board through them. The first link in the chain receives a board freshly seeded with req.Message on MainChannel and req.Inputs as board vars; each subsequent link receives the board its predecessor returned. Every Preparer must return a fresh *Board (do not mutate and re-yield the input) so the engine and downstream chain links can rely on immutability of the previous board.
It is the single extension point for "anything that should be on the board before the engine sees it":
- conversation history (load, summarise, window);
- retrieved long-term memory and knowledge-base hits;
- system prompts and persona text;
- request-scoped board vars (form fields, parameters, tool allow-lists);
- any combination of the above.
Run guarantees:
- The chain is called exactly once per Run attempt, before Execute and before any Observer's OnRunStart. Revise attempts re-run the chain from the beginning with the same Request so boards are not stale across retries.
- The returned board is mutated by the engine; Preparers MUST return a fresh value each call.
- The returned board MUST be non-nil. Returning nil is a Run infrastructure error.
Preparers may perform bounded I/O such as transcript loading or retrieval. They run synchronously on the caller's goroutine, MUST honor ctx cancellation and deadlines, and SHOULD apply their own tighter timeout when calling a dependency whose latency is not already bounded. A Preparer MUST NOT detach untracked background work from the run.
type PreparerFunc ¶
PreparerFunc is the function-typed adapter for Preparer.
Useful when the seed logic is a single closure over a transcript loader or retriever:
agent.WithPreparer(agent.PreparerFunc(func(ctx context.Context, id agent.Identity, req *agent.Request, prev *agent.Board) (*agent.Board, error) {
b := prev.Clone()
b.SetChannel("memory", retrieved)
return b, nil
}))
type ProviderOutputEnvelope ¶ added in v0.1.3
type ProviderOutputEnvelope struct {
Provider string `json:"provider"`
Extension string `json:"extension"`
Value json.RawMessage `json:"value"`
}
ProviderOutputEnvelope carries one provider-owned observational output on a StreamDeltaProviderOutputs delta: the provider / extension identity plus the raw output value. Value is opaque to the stream protocol; consumers decode it against the provider extension's schema when they recognize the family.
type Publisher ¶
Publisher emits a single event envelope.
Subject schema is NOT owned by this package: the host decides what the routing keys look like. Engines simply produce envelopes whose subject they construct from whatever convention their host has agreed with the consumer side.
Publish errors MUST NOT cause the producing engine to fail the run by default; the engine should log/record and continue. Backpressure or transport failures are an observability concern, not a control- flow signal.
Implementations MUST observe ctx.Done and return promptly. Graph-level publish timeouts are bounded only for implementations that honor this cooperative cancellation contract.
type Referee ¶
type Referee interface {
// After fires after Execute returns. The Referee
// inspects res (read-only) and the original req, and returns a
// Decision that Run merges with other Referees' decisions.
//
// id carries the immutable identification fields agreed for
// this turn. The Referee MUST NOT mutate res; agent will surface
// the merged decision via [Result.Committed] and (when a Reason
// was supplied) [Result.State]["finalize_reason"].
After(ctx context.Context, id Identity, req *Request, res *Result) (Decision, error)
}
Referee is a decision-making lifecycle hook that can influence what agent.Execute does at well-defined boundaries. It is the read-write counterpart of Observer:
- Committers persist accepted results and may return errors.
- Observers see what happened and emit best-effort side effects.
- Referees return a structured decision agent.Run interprets.
Referees expose one decision point — Referee.After — which fires after Execute returned but before Run invokes Committer and Observer. This covers two real cases:
Disposition: a barge-in cause means the assistant was cut off mid-thought; the half-baked output should not appear in the persistent transcript. A Referee returns Decision{DiscardOutput: true}.
(Reserved) Revise: the natural answer fails some quality bar (no citations, policy violation, refusal-without-reason); the Referee asks for one more model pass. The wire field is present for forward compatibility — agent does not yet honour it, and engines will need explicit support before it has any effect.
Composition ¶
Multiple Referees may be registered (Agent-scoped + per-call). They run in registration order. The merged decision is the OR over boolean fields: any Referee asking to accept, discard, or revise is retained. Discard has final precedence when Run applies the merged decision. The first non-empty Reason wins, so callers can attribute the decision in logs.
Error contract ¶
A Referee returning a non-nil error short-circuits the merge and causes Run to return (Result, decider-error). agent does NOT swap the error class — it surfaces the Referee's own error so callers can classify with errdefs. The Result is still populated (including the engine's output) so the caller can decide what to do next.
Embed BaseReferee to satisfy the interface with no-op defaults.
type Request ¶
type Request struct {
// TaskID identifies a long-lived task the request is part of.
// Empty when the caller is not tracking tasks. Maps to A2A's
// "taskId".
TaskID string `json:"taskId,omitempty"`
// ContextID identifies the conversation / session. Used as the
// conversation key passed to History.Load / Append. Empty means
// "no persistent transcript for this turn". Maps to A2A's
// "contextId".
ContextID string `json:"contextId,omitempty"`
// RunID is the host-supplied execution id. When empty Run mints
// one. The same value is propagated as Run.RunID and as the
// run id attribute on emitted events. Not part of the A2A wire
// schema — it is an internal correlation key, kept camelCase for
// stylistic consistency.
RunID string `json:"runId,omitempty"`
// ParentRunID identifies the parent run when this turn was
// dispatched by another agent (multi-agent call chains). Empty for
// top-level runs. WithParentRunID wins when both are supplied.
// Not part of the A2A wire schema — internal correlation key.
ParentRunID string `json:"parentRunId,omitempty"`
// Attributes carries host-supplied metadata for this turn that
// should flow into Run.Attributes (and from there into telemetry
// spans and event headers): tenant id, feature flags, delegation
// lineage, ... WithAttributes wins per key on conflict.
Attributes map[string]string `json:"attributes,omitempty"`
// Message is the user's turn input (text, parts, attachments).
Message message.Message `json:"message"`
// Inputs are arbitrary structured inputs the engine reads off
// the Board (form fields, parameters, …). They are written under
// their map keys as Board vars before the engine starts.
Inputs map[string]any `json:"inputs,omitempty"`
// Config carries per-request preferences (output modes, …).
// Maps to A2A's "configuration".
Config *RequestConfig `json:"configuration,omitempty"`
}
Request is one agent turn submitted to Execute.
Field names and JSON tags mirror the A2A protocol's MessageSendParams schema (camelCase: taskId, contextId, …) so requests can be serialised across the protocol without translation. The notable absence vs core/agent.Request is that Request does not carry a RuntimeID (Run is now a stateless function) and does not carry a Strategy hint (the engine is supplied directly to Run).
type RequestConfig ¶
type RequestConfig struct {
// AcceptedOutputModes constrains what modalities the caller can
// receive (e.g. ["text/plain"], ["audio/wav"], …). Engines that
// can produce multiple modalities consult it to pick one. Maps
// to A2A's "acceptedOutputModes".
AcceptedOutputModes []string `json:"acceptedOutputModes,omitempty"`
}
RequestConfig holds per-request preferences. Optional knobs are added here rather than on Request to keep Request stable across minor versions. JSON keys mirror the A2A MessageSendConfiguration schema so requests can flow across the protocol without translation.
type Result ¶
type Result struct {
// TaskID echoes the input Request.TaskID for correlation.
// Matches the A2A taskId casing.
TaskID string `json:"taskId,omitempty"`
// RunID echoes the (possibly auto-generated) execution id Run
// used to drive the
RunID string `json:"runId,omitempty"`
// Status classifies the outcome.
Status Status `json:"status"`
// Cause is set when Status == StatusInterrupted: it carries the
// Cause the host signalled. Empty otherwise.
Cause Cause `json:"cause,omitempty"`
// Messages is the slice of NEW messages produced this turn —
// excluding the input request and any history loaded before the
// turn. Suitable for streaming to a UI or appending to the
// persistent transcript (which Run already did).
Messages []message.Message `json:"messages,omitempty"`
// Artifacts collects named, multi-modal bundles the engine
// emitted via dedicated board channels.
Artifacts []Artifact `json:"artifacts,omitempty"`
// Committed reports whether this turn's output was accepted and,
// when Committers are registered, durably committed. Agent first
// derives commit eligibility from the Referee chain on top of its
// defaults:
//
// - StatusCompleted defaults to Committed=true.
// - All non-completed statuses default to Committed=false.
// - Any Referee returning AcceptOutput=true makes the result
// eligible for the normal Committer chain.
// - Any Referee returning DiscardOutput=true forces
// Committed=false, overriding AcceptOutput.
// - A Committer failure forces Committed=false and Execute
// returns the Result together with that error.
//
// Independent of Committed, Result.Messages always reflects the
// engine's actual output. When no Committer is registered,
// Committed reflects acceptance alone.
Committed bool `json:"committed"`
// State is a free-form bag carrying run-specific metadata. agent
// puts a few well-known keys (run_id, board, interrupted_node,
// …) here but does not enforce a schema beyond that.
State map[string]any `json:"state,omitempty"`
// Err is the engine's underlying error when Status indicates a
// non-completed outcome. Callers that want classification call
// errdefs.IsXxx on it; the JSON tag is "-" because errors do not
// JSON-marshal usefully.
Err error `json:"-"`
// LastBoard is the engine's final Board (possibly partial when
// Status != StatusCompleted). agent does not persist it; the
// host can choose to checkpoint via engine's Checkpointer.
LastBoard *Board `json:"-"`
// Attempts is the number of Execute invocations Run made
// before settling on this Result. 1 for fresh (single-shot)
// runs; >1 only when WithMaxRevise was enabled and at least
// one Referee returned Decision{Revise: true}.
//
// Attempts is the post-loop count, not "remaining budget":
// Attempts == 2 means the engine was invoked twice. Hooks
// reading res.Attempts in OnRunEnd see the final value.
//
// Zero is reserved for "Run never reached Execute"
// (infrastructure error). Real runs always have Attempts >= 1.
Attempts int `json:"attempts,omitempty"`
}
Result is what Run returns after one turn. The contract:
Run() returns (res, nil) for ALL business outcomes — completion, interrupt, cancel, abort, failure. Caller inspects Status to branch.
Run() returns (nil, err) ONLY for infrastructure failures the caller cannot reasonably recover from (e.g. history append refused, factory returned nil engine).
This mirrors core/agent.Result's "W-5" rule and avoids the double-encoding pattern where errors are also carried by Status.
func Execute ¶
func Execute( ctx context.Context, ag Agent, eng Engine, req Request, opts ...ExecuteOption, ) (*Result, error)
Execute executes one turn of ag against eng with the given req.
Execute is intentionally minimalist: it owns identifier minting, board assembly, hook dispatch, and result classification — and nothing else. Anything that looks like "policy" (load conversation history, run RAG retrieval, write transcripts after a turn, emit metrics, route engine envelopes to a bus, accumulate token usage, …) lives outside Execute on:
- Observer / Referee / Committer for lifecycle hooks;
- Preparer for engine-input shaping;
- the caller-supplied Host (see WithHost) for every host-side capability the engine needs (event publishing, interrupt injection, user prompting, checkpoint persistence, usage reporting). When omitted, agent falls back to NoopHost — which is fine for trivial / test runs but gives up every observability and HITL capability.
Wiring sequence ¶
- Mint a RunID (req.RunID wins, else autogenerate).
- Build an Board using either a caller-supplied Preparer (WithPreparer) or the default seeder, which simply appends req.Message to MainChannel and copies req.Inputs to board vars.
- Resolve the Host — caller-supplied via WithHost, else NoopHost.
- Build a Identity and notify all registered Hooks via OnRunStart.
- Call eng.Execute. The engine mutates the board in place per its contract.
- If the engine returned an interrupt, fire OnInterrupt with the destructured cause/detail.
- Translate the engine outcome into Status and assemble Result.
- Run the Referee chain (Referee.After) and merge the decisions; this fixes Result.Committed and any finalize_reason metadata.
- If the final result is committed and Committers exist, resolve any per-call CommitViewProvider, then invoke the Committer chain with that view.
- Fire Observer.OnRunEnd before returning.
Error contract ¶
Execute returns (res, nil) for every business outcome — completion, interrupt, cancel, abort, failure. (nil, err) is reserved for infrastructure failures the caller cannot reasonably recover from: nil engine, empty Agent.ID, or a Preparer, Referee, CommitViewProvider, or Committer that returned an error.
Observers MUST NOT cause Execute to return an error; they are advisory. Referee and Committer errors surface back to the caller — agent does not swap their error class, so callers can classify with errdefs.
type ResumeContext ¶
type ResumeContext struct {
// Attempt is 1-based: the very first attempt (fresh run, no
// resume) is 1; the first re-execution after a checkpoint is
// 2; and so on. Hosts that limit retry budget read this field.
Attempt int
// StartedAt is the wall-clock time the original [Run] began.
// Stays constant across resumes so dashboards can compute
// total wall time (e.g. SLO budget burn).
StartedAt time.Time
// Signal identifies the trigger that produced THIS resume.
// Convention (extensible — engines/middleware MUST treat
// unknown values as opaque):
//
// - "manual" — operator clicked Resume / CLI
// - "interrupt-recovery" — host re-executed after a host.Interrupts() Stop
// - "schedule" — cron / queue replayer
// - "crash" — supervisor restarted after process exit
// - "retry" — automated retry on classified failure
Signal string
// CheckpointAt is when the checkpoint that fuels this resume
// was originally produced (the engine's view of "paused at").
// Empty time.Time on fresh starts.
CheckpointAt time.Time
}
ResumeContext is the auxiliary metadata about the *current* resume attempt. Engines, observers and middleware read it from context.Context to differentiate fresh starts from resumes, count attempts in retry loops, and surface "this is replay" indicators in trace UIs.
ResumeContext is distinct from Run.ResumeFrom: the checkpoint describes WHERE the engine should pick up; ResumeContext describes WHY the resume is happening (who triggered it, which attempt this is, how long the original run has been alive). Keep them separate so hosts can drive replay + retry semantics without mutating the checkpoint payload.
func ResumeContextFromContext ¶
func ResumeContextFromContext(ctx context.Context) (ResumeContext, bool)
ResumeContextFromContext returns the ResumeContext attached to ctx, plus an "ok" flag. The ok=false branch means the engine is running a fresh start (or the host did not bother to populate the context — engines should treat both cases identically).
type Resumer ¶
type Resumer interface {
// CanResume returns nil if the given checkpoint is resumable by
// this engine, or a classified error explaining why not:
//
// - errdefs.Validation: checkpoint shape is wrong (engine kind
// mismatch, missing required Payload fields, ExecID
// conflict).
// - errdefs.NotAvailable: engine recognises the checkpoint but
// cannot resume it (incompatible engine version, removed
// node type, etc.).
//
// Implementations MUST be cheap (no I/O, no LLM calls); the
// probe runs synchronously on the host's resume path.
CanResume(cp Checkpoint) error
}
Resumer is the optional capability an Engine may advertise to signal it can drive Run.ResumeFrom and to short-circuit obvious mismatches *before* the host spins up an execution.
The interface is intentionally minimal — a single CanResume probe — so engines opt in cheaply and hosts can build resume tooling (admin UIs, CLI commands, supervised retry loops) without a full dry-run.
Engines that do NOT implement Resumer remain fully spec-compliant: the Run.ResumeFrom contract still applies and they MUST return an errdefs.NotAvailable-classified error if asked to resume. The helpers below (IsResumable, LoadAndResume) treat the absence of Resumer as "engine handles resume opaquely; trust Execute to surface the right error".
type Run ¶
type Run struct {
// Identity is the run's identity envelope. All identity
// dimensions are typed fields here — read run.AgentID /
// run.RunID / run.TaskID directly. (They resolve through the
// embedded struct, e.g. run.RunID == run.Identity.RunID.)
Identity
// Attributes carries arbitrary host-supplied metadata that should
// flow into telemetry spans and event headers (tenant id, engine
// kind, feature flags, …). Identity dimensions do NOT live here
// — they are typed fields on [Identity].
//
// Convention: keys SHOULD use the constants in core/telemetry
// (`telemetry.AttrTenantID`, `telemetry.AttrEngineKind`, …) so
// cross-package consumers (dashboards, log queries) can filter
// without per-package translation rules.
Attributes map[string]string
// ToolAllowList is the policy gate naming the tool ids this run
// is permitted to call. Engines that invoke tools honour it;
// empty means "no agent-level restriction" (the engine's own
// default applies). It is policy, not wiring — tool
// *implementations* are bound at engine assembly time (see
// [Factory] / [Config]), never per run.
ToolAllowList []string
// ResumeFrom, when non-nil, instructs the engine to continue
// execution from the provided checkpoint instead of starting a
// fresh run. The engine is the sole interpreter of
// [Checkpoint.Steps] and [Checkpoint.Payload]; the host treats
// them as opaque.
//
// Contract:
//
// - When ResumeFrom is non-nil the engine SHOULD prefer
// ResumeFrom.Board over the board parameter passed to
// [Engine.Execute]; passing both is allowed but the
// checkpoint's board takes precedence as it represents the
// state at the boundary the run paused on.
//
// - When ResumeFrom.ExecID differs from [Run.RunID] the engine
// MUST return an errdefs.Validation-classified error: forking
// a run requires a fresh execution id, not a resume.
//
// - Engines that do not support resume MUST return an
// errdefs.NotAvailable-classified error when they observe a
// non-nil ResumeFrom rather than silently restarting from
// scratch.
//
// Hosts that drive resumption typically [CheckpointStore.Load]
// the most recent checkpoint, set ResumeFrom, and call
// [Engine.Execute] again with the same Run.RunID.
ResumeFrom *Checkpoint
}
Run is the per-execution input bundle an engine receives alongside the host. It is a plain data struct — no methods, no builder, no hidden state — assembled once by the host and passed to Engine.Execute read-only.
All fields are conceptually immutable for the duration of the run. Engines may read freely; they MUST NOT mutate the maps in place nor mutate the referenced ResumeFrom checkpoint.
type RunEndPublishError ¶
type RunEndPublishError struct {
Err error
}
RunEndPublishError reports that an engine could not publish its required terminal run envelope. Consumers that use run-end as a delivery barrier must stop waiting and detach instead.
func (*RunEndPublishError) Error ¶
func (e *RunEndPublishError) Error() string
func (*RunEndPublishError) Unwrap ¶
func (e *RunEndPublishError) Unwrap() error
type RunInfo ¶
type RunInfo struct {
Identity
// Attributes mirrors [Run.Attributes] (host-supplied metadata:
// tenant id, engine kind, feature flags, …).
Attributes map[string]string
// ToolAllowList mirrors [Run.ToolAllowList]: the tool ids this run
// is permitted to call. Empty means "no agent-level restriction".
ToolAllowList []string
}
RunInfo is the read-only, node-facing projection of Run.
Engines hand it to step/node implementations instead of the full Run: everything a step legitimately needs (identity, attributes, tool policy) is here, while engine-internal machinery such as Run.ResumeFrom stays invisible. The projection is owned by this package — when Run grows a new field, the author decides here whether steps should see it, keeping every engine's step API stable.
func RunInfoFromContext ¶
RunInfoFromContext returns the RunInfo attached to ctx, plus an "ok" flag. ok=false means the caller is outside an engine run (or the engine did not populate the context) — consumers should treat the zero RunInfo as "identity unknown" rather than an error.
type ScriptEnv ¶
type ScriptEnv struct {
// Config is script-level configuration, accessible as a global in the script.
Config map[string]any
// Bindings maps names to host objects injected into the script's global scope.
// Each value is typically a map[string]any of Go functions callable from the script.
Bindings map[string]any
}
ScriptEnv carries per-execution configuration and host bindings.
type ScriptRuntime ¶
type ScriptRuntime interface {
Exec(ctx context.Context, name, source string, env *ScriptEnv) (*ScriptSignal, error)
}
ScriptRuntime executes scripts with injected host bindings. Implementations must be safe for concurrent use.
type ScriptSignal ¶
type ScriptSignal struct {
Type string `json:"type"`
Kind string `json:"kind,omitempty"`
Message string `json:"message,omitempty"`
Detail map[string]any `json:"detail,omitempty"`
}
ScriptSignal represents a control signal from a script back to the host.
Type is always one of "interrupt" / "error" / "done" — the three outcomes a script can choose to surface besides "ran to completion".
Kind is a per-Type sub-classifier:
- For Type "error" it carries an errdefs category name (see ErrorKind). SignalToError maps it onto the matching errdefs wrapper; unknown values degrade to ErrorKindInternal so a typo in script land cannot escape as an unclassified error.
- For Type "interrupt" it carries an Cause string. Unknown values degrade to CauseCustom for the same reason.
- For Type "done" Kind is unused.
Message is the human-readable detail. Detail is freeform structured metadata scripts may attach; it is preserved across host translation but is not inspected by SignalToError today.
type Skill ¶
type Skill struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Tags []string `json:"tags,omitempty"`
Examples []string `json:"examples,omitempty"`
InputModes []string `json:"inputModes,omitempty"`
OutputModes []string `json:"outputModes,omitempty"`
}
Skill is a single capability unit declared on an AgentCard. Field names mirror A2A's skill object so cards round-trip cleanly through /.well-known/agent-card.json.
type Status ¶
type Status string
Status is the terminal classification of a Run outcome. agent does NOT use Status as a control-flow signal — once Run returns, the caller decides what to do based on Status. The values mirror the A2A task-status enum so they can be serialised across protocol boundaries without translation.
const ( // StatusCompleted means the engine finished cleanly and produced // the messages / artifacts in [Result]. StatusCompleted Status = "completed" // StatusInterrupted means the engine was stopped by a cooperative // interrupt (host-injected). Result.Cause carries the reason. // By default the partial output is NOT committed (Result.Committed // is false); register a [Referee] (or rely on the default // disposition) to override. StatusInterrupted Status = "interrupted" // StatusCanceled means ctx was cancelled before the engine // finished. StatusCanceled Status = "canceled" // StatusFailed means the engine returned a domain error not // classified as interrupted / aborted. StatusFailed Status = "failed" // StatusAborted means the engine reported errdefs.IsAborted — // an unrecoverable internal halt. Distinguished from // StatusFailed so callers can apply different retry policy. StatusAborted Status = "aborted" )
type SteerSource ¶ added in v0.4.6
type SteerSource interface {
// DrainSteer returns the messages queued for this run and empties
// the queue. It MUST NOT block and MUST be safe for concurrent
// calls; nil or empty means nothing is queued.
DrainSteer() []message.Message
}
SteerSource is the optional Host capability for handing messages to a running turn — the inbound counterpart of Interrupter. It is not part of Host: the presence of this interface is exactly the answer to "does this run accept steer", and a Host that never accepts steer MUST NOT implement it (NoopHost does not, like EventBusProvider).
The producer is the runtime's session.Turn.Steer, which owns the queue; this interface is the consumer side, and DrainSteer reads that queue. A decorator that scopes a queue to one run withholds the capability from its inner chain with HostCapabilityMask — the delegation service does exactly that when a delegated run inherits its caller's host, because the queue belongs to the caller's turn.
A steered message is plain conversation content submitted while the turn is running. It reaches the engine at a boundary that the consuming deployment chose, never mid-stream: engines drain where they are already between steps, and for the graph engine that boundary is a node the graph author placed (typically tools → steer → next round), via the script global host.drainSteer(). The graph executor itself never drains — pollInterrupt is not a delivery point.
Contract:
- DrainSteer returns the messages queued so far and empties the queue. It is take-all and destructive: a message is never returned twice.
- It MUST NOT block and MUST be safe for concurrent callers. An empty result (nil or zero length) means "nothing queued".
- Draining is single-consumer by intent. A deployment that drains from two places concurrently gets an unspecified split of the queue.
- DrainSteer does NOT mean "delivered". A message a node drained and then failed to use, or that a checkpoint rollback left outside the resumed board, is gone: core guarantees the position of delivery only up to the drain call. Producers that need to report non-delivery must treat it as best-effort (see session.Turn.PendingSteer for the turn-owned queue).
- Hosts that own a steer queue implement this; embedders that wrap a Host must not synthesise one. The runtime installs its own turn-owned source on every turn and rejects a HostFactory product that already implements SteerSource.
func SteerFromHost ¶ added in v0.4.6
func SteerFromHost(h Host) (SteerSource, bool)
SteerFromHost returns h's steer source when h implements SteerSource, mirroring EventBusFromHost: nil interfaces, typed-nil hosts and typed-nil capabilities all count as unsupported, and built-in Host decorators are traversed without claiming SteerSource themselves.
type StreamDeltaPayload ¶
type StreamDeltaPayload struct {
// Type discriminates the payload variant. See StreamDeltaType
// constants for the standard values.
Type StreamDeltaType `json:"type"`
// Payload carries the raw script-supplied value for
// forward-compatible / custom event types (host.emit with an
// unrecognized type). Standard types decode their fields directly
// and leave this empty. Opaque to the stream protocol; consumers
// decode it against the event type's own schema.
Payload json.RawMessage `json:"payload,omitempty"`
// Part carries one canonical output part. Required on "part".
// Wire-encoded with the same type discriminator as
// [message.Content] parts.
Part message.Part `json:"part,omitempty"`
// Speculative reports whether the delta belongs to a parallel branch
// whose output has not yet been accepted into the parent board.
Speculative bool `json:"speculative,omitempty"`
// ForkID identifies the graph-runner parallel fork for speculative
// branch deltas and parallel_branch_* control deltas.
ForkID string `json:"fork_id,omitempty"`
// BranchID identifies the branch within ForkID for speculative
// branch deltas and parallel_branch_* control deltas.
BranchID string `json:"branch_id,omitempty"`
// Reason carries the rollback/abort reason for parallel_branch_cancel.
Reason string `json:"reason,omitempty"`
// FinishReason carries the normalized finish reason on "finish".
// Values use the inference.FinishReason vocabulary.
FinishReason string `json:"finish_reason,omitempty"`
// RequestID and ResponseID ride the "finish" delta when the
// provider exposes them.
RequestID string `json:"request_id,omitempty"`
ResponseID string `json:"response_id,omitempty"`
// ProviderOutputs carries the final observational outputs on
// "provider_outputs", one envelope per output.
ProviderOutputs []ProviderOutputEnvelope `json:"provider_outputs,omitempty"`
}
StreamDeltaPayload is the canonical decoded shape of a SubjectStreamDelta envelope's payload.
Engines MUST emit payloads that JSON-decode into this struct; the runtime constraint is checked by DecodeStreamDelta. Engines MAY add fields beyond what this struct lists — the JSON decoder is permissive on unknowns — but consumers SHOULD only rely on the fields documented here.
Per-Type field requirements:
Type Required Recommended ----------------------- --------------- -------------------- part Part — finish FinishReason RequestID, ResponseID provider_outputs ProviderOutputs — parallel_branch_accept ForkID, BranchID — parallel_branch_cancel ForkID, BranchID Reason (forward-compatible) Payload —
Speculative data deltas (part, finish, provider_outputs) additionally require both ForkID and BranchID. Non-speculative data deltas MUST carry neither field, avoiding a half-speculative state that consumers cannot interpret safely. Parallel-branch control deltas MUST NOT carry Part.
func DecodeStreamDelta ¶
func DecodeStreamDelta(env event.Envelope) (StreamDeltaPayload, error)
DecodeStreamDelta extracts the payload of a stream-delta envelope. It returns an error when the envelope payload is empty or does not JSON-decode into StreamDeltaPayload. It does NOT verify the subject; callers may pre-filter with IsStreamDelta when iterating a mixed stream.
func (StreamDeltaPayload) MarshalJSON ¶
func (p StreamDeltaPayload) MarshalJSON() ([]byte, error)
MarshalJSON encodes Part through message.MarshalPart so the wire form carries the canonical "type" discriminator.
func (*StreamDeltaPayload) UnmarshalJSON ¶
func (p *StreamDeltaPayload) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the "part" subtree through message.UnmarshalPart.
type StreamDeltaType ¶
type StreamDeltaType string
StreamDeltaType enumerates the kinds of in-flight increments a stream envelope can carry. Engines MUST set StreamDeltaPayload.Type to one of these values; consumers SHOULD treat unknown values as forward- compatible additions and skip them.
const ( // StreamDeltaPart carries one canonical [message.Part] — text, // image, audio, video, file, data, tool_call, tool_result, or // reasoning. Required field: Part. StreamDeltaPart StreamDeltaType = "part" // StreamDeltaFinish marks the terminal event of a generation's // delta sequence: the normalized finish reason and the // provider-issued request / response identifiers. Required field: // FinishReason; RequestID / ResponseID are optional. StreamDeltaFinish StreamDeltaType = "finish" // StreamDeltaProviderOutputs carries the final collection of // provider-owned observational outputs (citations, search-call // records, code-interpreter metadata) attached to a generation. // Required field: ProviderOutputs. StreamDeltaProviderOutputs StreamDeltaType = "provider_outputs" // StreamDeltaParallelBranchAccept marks a speculative parallel // branch's stream output as accepted by the graph runner. // Required fields: ForkID, BranchID. StreamDeltaParallelBranchAccept StreamDeltaType = "parallel_branch_accept" // StreamDeltaParallelBranchCancel marks a speculative parallel // branch's stream output as canceled/rolled back by the graph runner. // Required fields: ForkID, BranchID. Recommended: Reason. StreamDeltaParallelBranchCancel StreamDeltaType = "parallel_branch_cancel" )
type StreamSink ¶
type StreamSink interface {
OnDelta(ctx context.Context, env event.Envelope, delta StreamDeltaPayload) error
}
StreamSink is the consumer-side counterpart of the EmitStream* helpers. A sink receives one decoded StreamDeltaPayload at a time along with its source envelope (for headers / trace ids / raw subject access) and forwards it to whatever transport the caller cares about.
Implementations:
- MUST be safe for concurrent OnDelta calls;
- SHOULD return errors only for unrecoverable failures;
- MUST observe ctx.Done and return promptly.
type StreamSinkFunc ¶
StreamSinkFunc is a func adapter for StreamSink.
func (StreamSinkFunc) OnDelta ¶
func (f StreamSinkFunc) OnDelta(ctx context.Context, env event.Envelope, delta StreamDeltaPayload) error
OnDelta implements StreamSink.
type UsageReporter ¶
UsageReporter accepts incremental LLM token-usage reports an engine observes during a run. Each call adds delta usage; the host is responsible for accumulation, billing, and downstream telemetry.
Engines should call ReportUsage once per LLM invocation that returns usage metadata (typical: streaming nodes call it on completion with the per-call totals). Reports SHOULD be best-effort for *observability* failures — a slow exporter must not block forward progress.
Budget enforcement contract:
- The host MAY return errdefs.BudgetExceeded (or any error classified by errdefs.IsBudgetExceeded) to signal that the accumulated usage has crossed a configured budget and the next LLM call would not be authorised.
- Engines that observe such an error MUST stop performing further LLM-cost-incurring work in this run and return the error from Execute (typically wrapped). Continuing would defeat the budget.
- Any other non-nil error is observability-only — engines SHOULD log/swallow and continue, matching the pre-budget contract.
Hosts without billing or budget enforcement return nil unconditionally (see NoopHost.ReportUsage).
The usage value is inference.Usage: token totals plus the Model / LatencyMs envelope, with cost carried by Billing.Cost (a Money with explicit currency and scale) when a pricing catalog is configured.
type UserPrompt ¶
type UserPrompt struct {
Parts []message.Part
Schema []byte
Source string
Metadata map[string]string
}
UserPrompt describes what the engine is asking the host to relay to the end user. It deliberately stays one level below "chat message":
- Parts carries the multi-modal payload (text, image, audio, file, structured data) using message.Part — the same building block that message.Message content uses, minus the chat-specific Role.
- Schema is an optional structured-input hint (JSON-schema-shaped bytes) for cases where the host wants to render a form or validate the response.
- Source identifies the engine step that produced the prompt; useful for trace correlation and resume.
- Metadata is free-form host-passed-through metadata.
Why []message.Part rather than message.Message: a Message also carries Role (system/user/assistant/tool), which is a chat-layer concept the engine has no business naming. Parts give us full multi-modality (image, audio, file, data) without tying the engine to chat semantics — the agent layer wraps Parts back into a Message with the right Role on its way out, and unwraps user-supplied Parts on the way in.
type UserPrompter ¶
type UserPrompter interface {
AskUser(ctx context.Context, prompt UserPrompt) (UserReply, error)
}
UserPrompter lets an engine ask the host to prompt the end user (chat input, voice DTMF, structured form, …) and block until the reply arrives.
Hosts that don't expose user interaction should return an errdefs.NotAvailable-classified error. Engines that get such an error from a step that strictly needs user input should propagate it so the host can decide whether to fail or fall back.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package a2a adapts a remote A2A (Agent2Agent) agent into a FlowCraft agent.Engine.
|
Package a2a adapts a remote A2A (Agent2Agent) agent into a FlowCraft agent.Engine. |
|
Package agenttest provides reusable contract-test machinery for the interfaces declared in core/agent — agent.Engine, agent.Host, agent.Observer, agent.Referee, agent.CheckpointStore, agent.Resumer, agent.StreamSink, agent.Preparer, agent.Committer, agent.CommitViewProvider, agent.CheckpointSuggester and agent.ScriptRuntime.
|
Package agenttest provides reusable contract-test machinery for the interfaces declared in core/agent — agent.Engine, agent.Host, agent.Observer, agent.Referee, agent.CheckpointStore, agent.Resumer, agent.StreamSink, agent.Preparer, agent.Committer, agent.CommitViewProvider, agent.CheckpointSuggester and agent.ScriptRuntime. |
|
Package bindings exposes per-execution host APIs to script runtimes.
|
Package bindings exposes per-execution host APIs to script runtimes. |
|
checkpoint
|
|
|
workspace
Package workspace provides an agent.CheckpointStore backed by a workspace.Workspace.
|
Package workspace provides an agent.CheckpointStore backed by a workspace.Workspace. |
|
Package scriptrt owns the deployment resource protocol for script runtimes.
|
Package scriptrt owns the deployment resource protocol for script runtimes. |
|
jsrt
Package jsrt provides a goja-based JavaScript implementation of agent.ScriptRuntime.
|
Package jsrt provides a goja-based JavaScript implementation of agent.ScriptRuntime. |
|
luart
Package luart provides a pure-Go Lua 5.1 implementation of agent.ScriptRuntime using github.com/yuin/gopher-lua (no CGO).
|
Package luart provides a pure-Go Lua 5.1 implementation of agent.ScriptRuntime using github.com/yuin/gopher-lua (no CGO). |