Help us shape the future of Go! Take the 2026 Go Developer Survey

loop

package
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package loop implements the agent loop: it calls the model, executes tool calls, asks for approvals and checkpoints state so interrupted runs can be resumed. Everything else, such as hooks and context engineering, plugs into the loop as Middleware.

Index

Constants

View Source
const DefaultMaxTurns = 50

DefaultMaxTurns bounds the number of model calls in a run.

View Source
const DefaultOutputRetries = 2

DefaultOutputRetries is how often an answer that does not match Config.Output is sent back to the model when Output.Retries is zero.

View Source
const HandoffFromKey = "agentx.handoff.from"

HandoffFromKey is the run value that names the agent that handed the run off to the agent running it, in a run continued by agent.WithHandoffs. Middleware such as tracing reads it.

View Source
const SessionValuePrefix = "session."

SessionValuePrefix starts the keys of run values that belong to the session rather than the run. agent.Runner saves them with the session when a run ends and passes them to the session's next runs in Request.Values; callers of Runtime.Run carry them from Result.Values themselves.

View Source
const StateVersion = 1

StateVersion is the version of the State format written by this package. Resume accepts only states of this version.

Variables

View Source
var (
	Approve = Approval{Approved: true}
	Deny    = Approval{Reason: "The user denied this tool call."}
)

Approve and Deny are convenience approvals.

View Source
var ErrCheckpointNotFound = errors.New("checkpoint not found")

ErrCheckpointNotFound is returned when a checkpoint does not exist.

View Source
var ErrNoCheckpointer = errors.New("loop: resume requires a checkpointer")

ErrNoCheckpointer is returned by Resume when the runtime has no Checkpointer. Resume such runs from Interrupt.State with ResumeState.

Functions

func Block

func Block(reason string) error

Block returns an error that ends the run with StatusBlocked. Use it in BeforeRun to reject the input.

func CloseToolCalls

func CloseToolCalls(msgs []*model.Message, content string) *model.Message

CloseToolCalls returns a message that answers, with an error result holding content, the tool calls of the last assistant message in msgs that have no result, or nil if all are answered. Append it to end a conversation without leaving calls unanswered, which providers reject.

func Deps

func Deps[T any](ctx context.Context) (T, bool)

Deps returns the dependency of type T that ctx carries; see WithDeps. The type must match exactly: a dependency stored as a concrete type is not returned for an interface it implements.

func ForwardEvent

func ForwardEvent(ctx context.Context, ev *Event) bool

ForwardEvent passes ev, an event of a nested run that the current tool call runs, such as a sub-agent's run, to the consumer of the run executing the call. The consumer gets a copy with Depth increased by one and, for an event of the nested run itself, ParentCallID set to the ID of the call; events the nested run forwarded keep theirs. The copy keeps the nested run's RunID, Agent and Turn.

ForwardEvent is safe for concurrent use, also by calls running concurrently. It reports false, without delivering the event, outside a tool call of a run, once the call has returned, or when the consumer stopped iterating. The nested run need not stop then: like any tool call, the call keeps its result.

func InterruptCall

func InterruptCall(ctx context.Context, intr *Interrupt) bool

InterruptCall asks the run executing the current tool call to interrupt with the approval requests of intr, which typically come from a nested run such as a sub-agent. The tool's result is discarded. When the run is resumed, the tool is called again and ResumedCall returns intr and the decisions for its requests.

InterruptCall reports false outside a tool call of a run; the tool must then handle the interrupt itself.

func ReportUsage

func ReportUsage(ctx context.Context, u model.Usage)

ReportUsage adds usage, for example of a sub-agent's run, to the run executing the current tool call, so the run's usage covers the work done on its behalf. Outside a tool call it does nothing.

func Stop

func Stop(reason string) error

Stop returns an error that ends the run with StatusStopped.

func WithDeps

func WithDeps[T any](ctx context.Context, v T) context.Context

WithDeps returns a context carrying v as the dependency of type T, for example a database handle or the current user, in the style of the OpenAI Agents SDK's run context and Pydantic AI's deps. Tools, hooks, middleware and instruction functions of runs started with the context read it with Deps. A context holds one dependency per type; a later WithDeps of the same type replaces it for the derived context.

Dependencies live only in the context: they are not sent to the model and not saved with checkpoints or sessions. Pass them again, with the context of Resume, when an interrupted run is resumed.

func WithheldAnswer

func WithheldAnswer(name string) *model.Message

WithheldAnswer returns the assistant message that replaces an answer the guardrail named name withheld.

Types

type AfterModeler

type AfterModeler interface {
	AfterModel(ctx context.Context, run *Run, req *model.Request, resp *model.Response) error
}

AfterModeler runs after each model call, once the assistant message has been added to the conversation. req is the request that was sent.

type AfterRunner

type AfterRunner interface {
	AfterRun(ctx context.Context, run *Run, res *Result)
}

AfterRunner runs when a run ends, including when it is interrupted. If the consumer stops iterating early, it runs with StatusRunning: the run stopped where it was, and Result.Reason says whether it can be resumed from a checkpoint. It does not run if the consumer stops at EventRunStart, before the run took a step.

type AfterTooler

type AfterTooler interface {
	AfterTool(ctx context.Context, run *Run, plan *ToolPlan, res *model.ToolResult) error
}

AfterTooler runs after each tool call, including denied ones, and may modify the result.

type AnswerHolder

type AnswerHolder interface {
	HoldAnswer() bool
}

AnswerHolder is implemented by BeforeFinisher middleware that must see the final answer before the consumer does, such as the guard package's output guardrails with guard.HoldOutput. If HoldAnswer reports true, the runtime holds the EventDelta and EventMessage events of each model response until it knows the response has tool calls, when they are sent, or until every BeforeFinisher accepted the answer. An answer that does not end the run that way is never sent: one a BeforeFinisher rejects with a *GuardrailError or continues the run past, one the provider paused (model.FinishPause), one that fails Config.Output (its OutputRetry has no Output then), and one of a run that stops or fails before the answer is accepted. It stays in the conversation, including Result.Messages and Result.NewMessages, but is not Result.Output or OutputError.Output. Schema diagnostics reported to the consumer then describe only a generic mismatch, since details may quote unchecked text. The deltas of a response therefore arrive at once, after the response ended, unless the response calls tools.

type Approval

type Approval struct {
	Approved bool `json:"approved"`
	// Reason is shown to the model when the call is denied.
	Reason string `json:"reason,omitempty"`
	// UpdatedInput replaces the call arguments when approved.
	UpdatedInput json.RawMessage `json:"updated_input,omitempty"`
	// Remember asks middleware that decides approvals, such as the
	// permission package's Policy, to approve later calls like this one
	// without asking again, for the rest of the run or of the session.
	// Middleware reads it with ToolPlan.Approval.
	Remember RememberScope `json:"remember,omitempty"`
	// RememberRules, if set, lists what to remember instead of what the
	// middleware derives from the call, for example permission rules such
	// as "Bash(npm test *)".
	RememberRules []string `json:"remember_rules,omitempty"`
}

Approval is the decision for an ApprovalRequest.

type ApprovalRequest

type ApprovalRequest struct {
	Call *model.ToolCall `json:"call"`
	// Reason is the explanation given by the hook that asked, if any.
	Reason string `json:"reason,omitempty"`
	// Agent names the agent whose call this is. Requests of sub-agents
	// reach the parent's interrupt with their own agent name.
	Agent string `json:"agent,omitempty"`
}

ApprovalRequest asks for permission to run a tool call.

type Approver

type Approver interface {
	Approve(ctx context.Context, req *ApprovalRequest) (Approval, error)
}

Approver decides approval requests synchronously, for example by prompting a user in a terminal.

type ApproverFunc

type ApproverFunc func(ctx context.Context, req *ApprovalRequest) (Approval, error)

ApproverFunc adapts a function to Approver.

func (ApproverFunc) Approve

func (f ApproverFunc) Approve(ctx context.Context, req *ApprovalRequest) (Approval, error)

Approve calls f.

type BeforeFinisher

type BeforeFinisher interface {
	BeforeFinish(ctx context.Context, run *Run, msg *model.Message) (bool, error)
}

BeforeFinisher runs when the model answers without tool calls. Returning true continues the run; the middleware must have appended the next input with Run.Append.

type BeforeModeler

type BeforeModeler interface {
	BeforeModel(ctx context.Context, run *Run, req *model.Request) error
}

BeforeModeler runs before each model call and may modify the request. The request's options are copies, but the values in ProviderOptions are shared with the configuration, and the messages are shared with the run: replace a value in ProviderOptions or a message in Request.Messages instead of modifying it.

type BeforeRunner

type BeforeRunner interface {
	BeforeRun(ctx context.Context, run *Run, in *RunInput) error
}

BeforeRunner runs when a new run starts, before the input enters the conversation. It does not run when an interrupted run is resumed.

type BeforeTooler

type BeforeTooler interface {
	BeforeTool(ctx context.Context, run *Run, plan *ToolPlan) error
}

BeforeTooler runs for each tool call while the turn decides which calls need approval, before the run may be interrupted.

type BeforeTurner

type BeforeTurner interface {
	BeforeTurn(ctx context.Context, run *Run) error
}

BeforeTurner runs at the start of each turn, before the model request is built. It may replace the conversation, for example to compact it.

type Checkpointer

type Checkpointer interface {
	Save(ctx context.Context, st *State) error
	Load(ctx context.Context, runID string) (*State, error)
	Delete(ctx context.Context, runID string) error
}

Checkpointer persists run state so interrupted or crashed runs can be resumed. Checkpoints are keyed by run ID: 1 to 128 ASCII letters, digits, '_' and '-'; the built-in checkpointers reject others. A run overwrites its checkpoint once its input is added, after each turn, before each batch of tool calls, and when it is interrupted, fails or its consumer stops iterating; it deletes the checkpoint when it ends in a status that is not resumable or fails before its input is added.

type Compaction

type Compaction struct {
	// Messages is the conversation after compaction.
	Messages []*model.Message `json:"messages"`
	// Before and After are the estimated input sizes in tokens.
	Before  int    `json:"before"`
	After   int    `json:"after"`
	Summary string `json:"summary,omitempty"`
}

Compaction describes a compaction of the conversation for EventCompacted.

type Config

type Config struct {
	Settings
	Tools *tool.Registry
	// Middlewares extend the loop in order. If any is a ToolProvider, its
	// tools are added to a copy of Tools.
	Middlewares []Middleware
}

Config configures a Runtime.

type ContextWrapper

type ContextWrapper interface {
	WrapContext(ctx context.Context, run *Run) (_ context.Context, release func())
}

ContextWrapper derives the context of a run once when Run or Resume starts, for example to start a trace span that the run's model and tool calls become children of. The returned context must derive from ctx. release, if non-nil, is called when the Run or Resume call returns, however the run ended.

type Decision

type Decision int

Decision is the verdict of BeforeTool middleware on a tool call.

const (
	// NoDecision leaves approval to the tool's RequiresApproval.
	NoDecision Decision = iota
	// Allowed skips approval.
	Allowed
	// Asked requires approval.
	Asked
	// Denied blocks the call.
	Denied
)

func (Decision) String added in v1.1.0

func (d Decision) String() string

String returns "", "allow", "ask" or "deny", the values of hook.Decision and of Claude Code's permissionDecision.

type Event

type Event struct {
	Type      EventType `json:"type"`
	RunID     string    `json:"run_id,omitempty"`
	SessionID string    `json:"session_id,omitempty"`
	Agent     string    `json:"agent,omitempty"`
	Turn      int       `json:"turn,omitempty"`

	Delta      *model.StreamEvent `json:"delta,omitempty"`
	Message    *model.Message     `json:"message,omitempty"`
	ToolCall   *model.ToolCall    `json:"tool_call,omitempty"`
	ToolResult *model.ToolResult  `json:"tool_result,omitempty"`
	Compaction *Compaction        `json:"compaction,omitempty"`
	// OutputRetry describes the rejected answer for EventOutputRetry.
	OutputRetry *OutputRetry `json:"output_retry,omitempty"`
	Interrupt   *Interrupt   `json:"interrupt,omitempty"`
	// Handoff describes the transfer for EventHandoff.
	Handoff *Handoff `json:"handoff,omitempty"`
	// Usage is the usage of the model call for EventMessage.
	Usage  model.Usage `json:"usage,omitzero"`
	Result *Result     `json:"result,omitempty"`

	// Depth is zero for the events of the run itself. Events of nested runs,
	// such as a sub-agent's, that a tool call forwards with ForwardEvent
	// have the depth of the nested run below this one: 1 for a sub-agent, 2
	// for a sub-agent of a sub-agent. Their RunID, Agent and Turn are those
	// of the nested run, and an EventRunEnd among them ends the nested run,
	// not this one.
	Depth int `json:"depth,omitempty"`
	// ParentCallID is set on forwarded events to the ID of the tool call
	// that started the nested run that emitted them, in the run directly
	// above it.
	ParentCallID string `json:"parent_call_id,omitempty"`
}

Event is emitted by a run.

func (*Event) Text

func (e *Event) Text() string

Text returns the text of a delta or message event.

type EventType

type EventType string

EventType identifies a runtime event.

const (
	// EventRunStart is the first event of a run or a resumed run.
	EventRunStart EventType = "run_start"
	// EventTurnStart precedes each model call.
	EventTurnStart EventType = "turn_start"
	// EventDelta carries a streaming delta when streaming is enabled.
	EventDelta EventType = "delta"
	// EventMessage carries a complete assistant message.
	EventMessage EventType = "message"
	// EventToolCall is emitted before a tool call runs.
	EventToolCall EventType = "tool_call"
	// EventToolResult carries the result of a tool call.
	EventToolResult EventType = "tool_result"
	// EventCompacted reports that the conversation was compacted.
	EventCompacted EventType = "compacted"
	// EventOutputRetry reports that the final answer did not match
	// Config.Output and was sent back to the model; see OutputRetry.
	EventOutputRetry EventType = "output_retry"
	// EventHandoff reports that the run was handed off to another agent,
	// whose events follow; see Handoff. Its Agent is the agent handing off.
	EventHandoff EventType = "handoff"
	// EventInterrupted reports that the run paused for approval.
	EventInterrupted EventType = "interrupted"
	// EventRunEnd is the last event and carries the Result.
	EventRunEnd EventType = "run_end"
)

type Funcs

type Funcs struct {
	MiddlewareName     string
	WrapContextFunc    func(ctx context.Context, run *Run) (context.Context, func())
	BeforeRunFunc      func(ctx context.Context, run *Run, in *RunInput) error
	BeforeTurnFunc     func(ctx context.Context, run *Run) error
	BeforeModelFunc    func(ctx context.Context, run *Run, req *model.Request) error
	WrapModelFunc      func(run *Run, next model.Model) model.Model
	AfterModelFunc     func(ctx context.Context, run *Run, req *model.Request, resp *model.Response) error
	BeforeToolFunc     func(ctx context.Context, run *Run, plan *ToolPlan) error
	RevalidateToolFunc func(ctx context.Context, run *Run, plan *ToolPlan) error
	WrapToolFunc       func(run *Run, next tool.Invoker) tool.Invoker
	AfterToolFunc      func(ctx context.Context, run *Run, plan *ToolPlan, res *model.ToolResult) error
	BeforeFinishFunc   func(ctx context.Context, run *Run, msg *model.Message) (bool, error)
	AfterRunFunc       func(ctx context.Context, run *Run, res *Result)
}

Funcs builds a middleware from functions. Nil functions are skipped.

func (*Funcs) AfterModel

func (f *Funcs) AfterModel(ctx context.Context, run *Run, req *model.Request, resp *model.Response) error

AfterModel implements AfterModeler.

func (*Funcs) AfterRun

func (f *Funcs) AfterRun(ctx context.Context, run *Run, res *Result)

AfterRun implements AfterRunner.

func (*Funcs) AfterTool

func (f *Funcs) AfterTool(ctx context.Context, run *Run, plan *ToolPlan, res *model.ToolResult) error

AfterTool implements AfterTooler.

func (*Funcs) BeforeFinish

func (f *Funcs) BeforeFinish(ctx context.Context, run *Run, msg *model.Message) (bool, error)

BeforeFinish implements BeforeFinisher.

func (*Funcs) BeforeModel

func (f *Funcs) BeforeModel(ctx context.Context, run *Run, req *model.Request) error

BeforeModel implements BeforeModeler.

func (*Funcs) BeforeRun

func (f *Funcs) BeforeRun(ctx context.Context, run *Run, in *RunInput) error

BeforeRun implements BeforeRunner.

func (*Funcs) BeforeTool

func (f *Funcs) BeforeTool(ctx context.Context, run *Run, plan *ToolPlan) error

BeforeTool implements BeforeTooler.

func (*Funcs) BeforeTurn

func (f *Funcs) BeforeTurn(ctx context.Context, run *Run) error

BeforeTurn implements BeforeTurner.

func (*Funcs) Name

func (f *Funcs) Name() string

Name implements Middleware.

func (*Funcs) RevalidateTool

func (f *Funcs) RevalidateTool(ctx context.Context, run *Run, plan *ToolPlan) error

RevalidateTool implements ToolRevalidator.

func (*Funcs) WrapContext

func (f *Funcs) WrapContext(ctx context.Context, run *Run) (context.Context, func())

WrapContext implements ContextWrapper.

func (*Funcs) WrapModel

func (f *Funcs) WrapModel(run *Run, next model.Model) model.Model

WrapModel implements ModelWrapper.

func (*Funcs) WrapTool

func (f *Funcs) WrapTool(run *Run, next tool.Invoker) tool.Invoker

WrapTool implements ToolWrapper.

type Guardrail

type Guardrail struct {
	// Name names the guardrail.
	Name string `json:"name"`
	// Kind is where it ran: "input", "output", "tool_input" or
	// "tool_output".
	Kind string `json:"kind,omitempty"`
	// Message explains the trip, for example to show to the user.
	Message string `json:"message,omitempty"`
	// Info is any information the guardrail attached.
	Info any `json:"info,omitempty"`
}

Guardrail describes the guardrail whose tripwire ended a run, for Result.Guardrail. See the guard package.

type GuardrailError

type GuardrailError struct {
	Guardrail Guardrail
	// Usage is the usage of a model call the guardrail cut short or
	// rejected, which a ModelWrapper returning the error reports so the
	// run still counts it.
	Usage model.Usage
}

GuardrailError is returned by middleware, typically the guard package's, when a guardrail's tripwire triggers. Returned from a stage or from a model call (see ModelWrapper), it ends the run with StatusBlocked and Result.Guardrail set; Result.Output is then empty, so a withheld answer is not shown by accident. Returned from BeforeFinish, it withholds the final answer: the conversation, the run's result and what a Runner saves keep WithheldAnswer in its place, which keeps the conversation valid for providers. Its events were sent already unless an AnswerHolder held them.

func (*GuardrailError) Error

func (e *GuardrailError) Error() string

type Handoff

type Handoff struct {
	// From and To name the agents.
	From string `json:"from"`
	To   string `json:"to"`
	// Tool and CallID identify the tool call that requested the handoff.
	Tool   string `json:"tool,omitempty"`
	CallID string `json:"call_id,omitempty"`
	// Input holds the arguments of the call, for handoffs with a typed
	// input.
	Input json.RawMessage `json:"input,omitempty"`
}

Handoff describes the transfer of a run from one agent to another, for EventHandoff. See agent.WithHandoffs.

type Interrupt

type Interrupt struct {
	RunID    string             `json:"run_id"`
	Requests []*ApprovalRequest `json:"requests"`
	// State is the state of the paused run. It lets ResumeState continue
	// the run without a Checkpointer, as sub-agents do.
	State *State `json:"state,omitempty"`
}

Interrupt describes a run paused for approval. Resume it with a decision for each request, keyed by the request's call ID.

func ResumedCall

func ResumedCall(ctx context.Context) (intr *Interrupt, approvals map[string]Approval, ok bool)

ResumedCall reports whether the current tool call repeats a call that raised an interrupt with InterruptCall, and returns that interrupt and the decisions for its requests.

type Limits

type Limits struct {
	// Requests bounds the model calls.
	Requests int `json:"requests,omitempty"`
	// ToolCalls bounds the executed tool calls. A batch that would exceed
	// it does not start; denied calls do not count.
	ToolCalls int `json:"tool_calls,omitempty"`
	// InputTokens bounds model.Usage.PromptTokens, the input tokens
	// including cache reads and writes.
	InputTokens int `json:"input_tokens,omitempty"`
	// OutputTokens bounds model.Usage.OutputTokens.
	OutputTokens int `json:"output_tokens,omitempty"`
	// TotalTokens bounds model.Usage.TotalTokens.
	TotalTokens int `json:"total_tokens,omitempty"`
}

Limits bounds the usage of a run, including the usage that nested runs, such as sub-agents, report with ReportUsage. Zero fields are unlimited.

Requests is checked before each model call, ToolCalls before each batch of tool calls, and the token limits before both: once tokens are spent, the run does no further work. A run that reaches a limit ends with StatusLimitExceeded; tool calls that did not run are answered as such, so the conversation stays valid. A final answer is kept even if its model call crossed a token limit.

type MemoryCheckpointer

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

MemoryCheckpointer keeps checkpoints in process memory, encoded as JSON like a persistent checkpointer, so a state that cannot be stored fails here too. The zero value is ready to use.

func NewMemoryCheckpointer

func NewMemoryCheckpointer() *MemoryCheckpointer

NewMemoryCheckpointer returns an empty in-memory checkpointer.

func (*MemoryCheckpointer) Delete

func (c *MemoryCheckpointer) Delete(_ context.Context, runID string) error

Delete implements Checkpointer.

func (*MemoryCheckpointer) Load

func (c *MemoryCheckpointer) Load(_ context.Context, runID string) (*State, error)

Load implements Checkpointer.

func (*MemoryCheckpointer) Save

func (c *MemoryCheckpointer) Save(_ context.Context, st *State) error

Save implements Checkpointer.

type Middleware

type Middleware interface {
	// Name identifies the middleware. Names must be unique in a Runtime.
	Name() string
}

Middleware extends the agent loop. Every middleware implements Name and any of the stage interfaces below; the runtime calls only the stages a middleware implements.

Before stages run in registration order, After stages in reverse order, and for Wrap stages the first middleware is outermost.

An error returned from a stage ends the run: Block and Stop end it with StatusBlocked and StatusStopped, any other error fails it. Middleware that should not stop the run on failure must handle its errors itself.

type ModelWrapper

type ModelWrapper interface {
	WrapModel(run *Run, next model.Model) model.Model
}

ModelWrapper wraps each model call. The wrapper may call next any number of times, including not at all.

type Output

type Output struct {
	// Name names the schema for providers. It defaults to "output" and may
	// hold letters, digits, underscores and dashes, up to 64.
	Name string
	// Schema describes the answer. It must be an object schema with
	// properties, such as tool.SchemaOf of a struct type.
	Schema *tool.Schema
	// Retries bounds how often an answer that does not match Schema is sent
	// back to the model. Zero means DefaultOutputRetries; a negative value
	// means none.
	Retries int
}

Output asks the model for a final answer in JSON matching Schema and validates it.

Unless Config.ResponseFormat is set, the runtime requests Schema as the response format in strict mode; see tool.Schema.StrictJSON. When the model answers without tool calls, the answer is checked against Schema before BeforeFinish middleware, such as Stop hooks, runs. An answer in a single Markdown code block, such as one fenced with ```json, is checked without the fence. An answer that does not match is sent back to the model with the problems found, and the run continues; each attempt takes a turn. Once the retries are used up, the run fails with an *OutputError; resuming it gives the model the same number of retries again. Answers cut off by the token limit, withheld by a content filter or refused are not checked: they end the run as they would without Output. Each answer sent back is reported with an EventOutputRetry.

type OutputError

type OutputError struct {
	// Output is the text of the last answer. It is empty when an
	// AnswerHolder holds answers: it was rejected before answer-holder checks.
	Output string
	// Problems lists what does not match the schema. When an AnswerHolder
	// holds answers, it reports only a generic mismatch, since detailed
	// diagnostics may quote text that has not passed answer-holder checks.
	Problems []string
	// Attempts is the number of answers checked.
	Attempts int
}

OutputError reports that the final answer of a run did not match Config.Output after all retries. It is the error of the failed run.

func (*OutputError) Error

func (e *OutputError) Error() string

type OutputRetry

type OutputRetry struct {
	// Output is the text of the answer. It is empty when an AnswerHolder
	// holds answers: it was rejected before answer-holder checks.
	Output string `json:"output"`
	// Problems lists what does not match the schema. When an AnswerHolder
	// holds answers, it reports only a generic mismatch; see OutputError.
	Problems []string `json:"problems,omitempty"`
	// Attempt numbers the answer among those the run checked, from 1. The
	// count restarts when a run that failed with an *OutputError is
	// resumed.
	Attempt int `json:"attempt"`
	// Retries is the number of retries allowed; the run fails with an
	// *OutputError if answer Retries+1 does not match either.
	Retries int `json:"retries"`
}

OutputRetry describes an answer that did not match Config.Output and was sent back to the model, for EventOutputRetry.

type PendingCall

type PendingCall struct {
	Call *model.ToolCall `json:"call"`
	// Denied is set when middleware or the approver rejected the call.
	Denied bool   `json:"denied,omitempty"`
	Reason string `json:"reason,omitempty"`
	// NeedsApproval is set while approval is outstanding.
	NeedsApproval bool `json:"needs_approval,omitempty"`
	ReadOnly      bool `json:"read_only,omitempty"`
	// Concurrent is set for calls of tools marked tool.Concurrent.
	Concurrent bool `json:"concurrent,omitempty"`
	// Values holds middleware state; see ToolPlan.SetValue.
	Values map[string]string `json:"values,omitempty"`
	// ModelArguments holds the arguments the model sent when middleware or
	// an approval changed them; Call holds the arguments that run.
	ModelArguments json.RawMessage `json:"model_arguments,omitempty"`
	// Started is set once the call began executing, and Result once it
	// finished. A resumed run reuses results and does not repeat a call
	// with side effects that may have run.
	Started bool              `json:"started,omitempty"`
	Result  *model.ToolResult `json:"result,omitempty"`
	// Nested is the interrupt the call raised for approvals of a nested
	// run, such as a sub-agent; see InterruptCall. NestedApprovals holds
	// the decisions once they are made.
	Nested          *Interrupt          `json:"nested,omitempty"`
	NestedApprovals map[string]Approval `json:"nested_approvals,omitempty"`
	// Approval is the decision of the approval the call needed, if any,
	// without its UpdatedInput; see ToolPlan.Approval.
	Approval *Approval `json:"approval,omitempty"`
	// Resuming is set while the call is repeated to resume its nested run.
	// If the run stops then, the nested run may have executed approved
	// calls, so a call with side effects is not repeated, like any cut-off
	// call with side effects. A read-only call is repeated with its nested
	// run, like any cut-off read-only call.
	Resuming bool `json:"resuming,omitempty"`
}

PendingCall is a tool call whose pre-execution checks have run.

type RememberScope

type RememberScope string

RememberScope is how long an approval is remembered; see Approval.Remember.

const (
	// RememberRun remembers an approval for the rest of the run.
	RememberRun RememberScope = "run"
	// RememberSession remembers an approval for the rest of the session:
	// it is kept in a run value whose key starts with SessionValuePrefix,
	// which agent.Runner carries to the session's later runs.
	RememberSession RememberScope = "session"
)

type Request

type Request struct {
	// RunID identifies the run; generated when empty.
	RunID     string
	SessionID string
	// History is the conversation before Input. It is not modified.
	History []*model.Message
	// Input is the new user message.
	Input *model.Message
	// NewSession marks the first run of a session, for example to fire the
	// SessionStart hook.
	NewSession bool
	// MaxTurns, if positive, overrides Config.MaxTurns for this run.
	MaxTurns int
	// Limits, if non-nil, replaces Config.Limits for this run.
	Limits *Limits
	// Values are the initial values of the run, as Run.SetValue stores
	// them, for example the session-scoped values of the session's earlier
	// runs; see SessionValuePrefix. Empty values are ignored.
	Values map[string]string
	// Usage and Turns are the usage and turns of earlier parts of the same
	// run, such as those of an agent that handed the run off to this one.
	// They count toward the limits and are included in the Result.
	Usage model.Usage
	Turns int
}

Request starts a run.

type Result

type Result struct {
	RunID  string `json:"run_id"`
	Status Status `json:"status"`
	// Reason explains a non-completed status.
	Reason string `json:"reason,omitempty"`
	// FinishReason is the finish reason of the last model response, for
	// example model.FinishRefusal when the model declined to answer.
	FinishReason model.FinishReason `json:"finish_reason,omitempty"`
	// Output is the text of the final assistant message, or the result of
	// the tool the run stopped at; see Config.StopAtTools. Decode parses it.
	Output string `json:"output"`
	// Messages is the whole conversation after the run, including history.
	// It may be shorter than history plus new messages after compaction.
	Messages []*model.Message `json:"messages,omitempty"`
	// NewMessages are the messages added by this run.
	NewMessages []*model.Message `json:"new_messages,omitempty"`
	Usage       model.Usage      `json:"usage"`
	Turns       int              `json:"turns"`
	// Interrupt lists the approvals needed to resume.
	Interrupt *Interrupt `json:"interrupt,omitempty"`
	// Err is the error of a failed run. In JSON it is the string "error".
	Err error `json:"-"`
	// Values are the run's values when it ended; see Run.SetValue and
	// SessionValuePrefix.
	Values map[string]string `json:"values,omitempty"`
	// Agent names the agent that ran last, which differs from the agent
	// started when the run was handed off; see EventHandoff.
	Agent string `json:"agent,omitempty"`
	// Guardrail describes the guardrail whose tripwire ended a run with
	// StatusBlocked; see GuardrailError.
	Guardrail *Guardrail `json:"guardrail,omitempty"`
}

Result summarizes a finished or interrupted run.

func Collect

func Collect(events iter.Seq2[*Event, error], onEvent func(*Event)) (*Result, error)

Collect drains events and returns the run result. onEvent, if non-nil, sees every event, including those forwarded from nested runs.

func (*Result) Decode

func (r *Result) Decode(v any) error

Decode unmarshals the output of a completed run into v with encoding/json. Output in a single Markdown code block is decoded from inside the block, as Config.Output checks it. It returns an error if the run did not complete or the model refused to answer.

func (*Result) DecodeAs

func (r *Result) DecodeAs[T any]() (T, error)

DecodeAs decodes the output of a completed run as a T, as Decode does. Agents configured with agent.WithOutput[T] answer with JSON of a T.

func (Result) MarshalJSON

func (r Result) MarshalJSON() ([]byte, error)

MarshalJSON encodes the result with Err as the string "error".

func (*Result) UnmarshalJSON

func (r *Result) UnmarshalJSON(b []byte) error

UnmarshalJSON decodes a result encoded by MarshalJSON. Err, if set, only keeps the error's text.

type Run

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

Run gives middleware access to the state of a run. The methods are not safe for concurrent use, except the read methods and Emit while tools execute.

func CallingRun

func CallingRun(ctx context.Context) *Run

CallingRun returns the run executing the current tool call, or nil outside a tool call of a run. Calls may run concurrently, so only the read methods of the run may be used.

func (*Run) Agent

func (r *Run) Agent() string

Agent returns the agent name.

func (*Run) Append

func (r *Run) Append(msgs ...*model.Message)

Append adds messages to the conversation.

func (*Run) Caller

func (r *Run) Caller() *Run

Caller returns the run whose tool call started this run, such as the run that called a sub-agent, or nil if the run was not started by a tool call. Following Caller from CallingRun leads to the outermost run, which holds the session of a sub-agent's work. Only the read methods of the returned run may be used.

func (*Run) DecodeValue

func (r *Run) DecodeValue[T any](key string) (T, bool, error)

DecodeValue decodes the run value of key, as stored by EncodeValue, into a T. It reports false if the run has no such value; a value that is not JSON of a T is an error.

func (*Run) Emit

func (r *Run) Emit(ev *Event) bool

Emit sends an event to the consumer of the run. It reports false when the consumer stopped iterating; no event is delivered after that. Tool calls running concurrently may emit too: their events are delivered one at a time, from the goroutine iterating the run.

func (*Run) EncodeValue

func (r *Run) EncodeValue(key string, v any) error

EncodeValue stores v as the run value of key, encoded as JSON, so values other than strings survive checkpoints; read it with DecodeValue. A nil v deletes the key. Like SetValue, it must not be called from tools that may run concurrently.

func (*Run) Halt

func (r *Run) Halt(reason string)

Halt stops the run with StatusStopped after the current step.

func (*Run) ID

func (r *Run) ID() string

ID returns the run ID.

func (*Run) Logger

func (r *Run) Logger() *slog.Logger

Logger returns the runtime logger.

func (*Run) Messages

func (r *Run) Messages() []*model.Message

Messages returns the conversation, including history. It must not be modified.

func (*Run) NewMessages

func (r *Run) NewMessages() []*model.Message

NewMessages returns the messages this run added to the conversation, starting with its input. It must not be modified.

func (*Run) ReplaceMessages

func (r *Run) ReplaceMessages(msgs []*model.Message)

ReplaceMessages replaces the conversation with a shorter one whose messages were removed from the front, as compaction does.

func (*Run) SessionID

func (r *Run) SessionID() string

SessionID returns the session ID.

func (*Run) SetValue

func (r *Run) SetValue(key, value string)

SetValue stores a value in the run state, which is saved with checkpoints. An empty value deletes the key. Prefix keys with the middleware name.

func (*Run) System

func (r *Run) System() string

System returns the system prompt of the run.

func (*Run) Tools

func (r *Run) Tools() []*model.ToolSpec

Tools returns the specs of the available tools.

func (*Run) Turn

func (r *Run) Turn() int

Turn returns the current turn, zero before the first model call.

func (*Run) Value

func (r *Run) Value(key string) (string, bool)

Value returns a value stored with SetValue.

type RunInput

type RunInput struct {
	NewSession bool
	// Query is the text of the input message before any middleware ran.
	Query string
	// System is the system prompt. It starts as Config.Instructions.
	System string
	// Message is the input message. Middleware may modify it, for example
	// by inserting parts in front of the user's text.
	Message *model.Message
}

RunInput is the input of a new run, as seen by BeforeRun.

type Runtime

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

Runtime runs the agent loop. It is safe for concurrent runs.

func New

func New(cfg Config) (*Runtime, error)

New returns a Runtime.

func (*Runtime) Config

func (r *Runtime) Config() Config

Config returns the runtime configuration.

func (*Runtime) Discard

func (r *Runtime) Discard(ctx context.Context, runID string) error

Discard deletes the checkpoint of a run that will not be resumed. A missing checkpoint is not an error.

func (*Runtime) Resume

func (r *Runtime) Resume(ctx context.Context, runID string, approvals map[string]Approval) iter.Seq2[*Event, error]

Resume continues a run from its checkpoint. It resumes runs that were interrupted for approval, that failed, or whose process stopped while they were running; see Status.Resumable.

approvals maps tool call IDs to decisions for an interrupted run; calls without a decision are denied. Approvals are ignored for runs that were not interrupted: requests they raise go to the Approver or interrupt the run. Finished tool calls are not repeated, and calls with side effects that were cut off are reported to the model as possibly incomplete instead of being run again.

A run that stopped after the model's final answer, before the answer was handled, handles it without calling the model again. If the consumer stops at EventRunStart, the checkpoint is left as it was.

func (*Runtime) ResumeState

func (r *Runtime) ResumeState(ctx context.Context, st *State, approvals map[string]Approval) iter.Seq2[*Event, error]

ResumeState is like Resume for a state obtained elsewhere, such as Interrupt.State of an earlier run, without a Checkpointer.

func (*Runtime) Run

func (r *Runtime) Run(ctx context.Context, req *Request) iter.Seq2[*Event, error]

Run starts a run and returns its events. The last event is EventRunEnd; a failed run then yields its error.

If the consumer stops iterating early, the run stops before its next step: tool calls that have not started do not run, and AfterRun middleware sees StatusRunning. Once the input was added, the state is checkpointed as running, so with a Checkpointer the run can be resumed from where it stopped; Result.Reason says whether it can. If the consumer stops at EventRunStart, nothing runs, not even AfterRun, and there is nothing to resume.

type Settings added in v1.1.0

type Settings struct {
	// Name identifies the agent in events and hooks.
	Name  string
	Model model.Model
	// Instructions is the base system prompt; middleware may extend it.
	Instructions string
	// ToolMiddlewares wrap every tool call inside the ToolWrapper
	// middleware; the first is outermost.
	ToolMiddlewares []tool.Middleware
	Checkpointer    Checkpointer
	// Approver decides calls that need approval. Without one, the run is
	// interrupted and must be resumed with approvals.
	Approver Approver

	MaxTurns       int
	MaxTokens      int
	Temperature    *float64
	TopP           *float64
	Reasoning      *model.Reasoning
	ToolChoice     *model.ToolChoice
	ResponseFormat *model.ResponseFormat
	// Seed is passed to the provider in model.Request.Seed.
	Seed *int
	// ProviderOptions is passed to the provider in
	// model.Request.ProviderOptions.
	ProviderOptions map[string]map[string]any
	// Stream calls Model.Stream and emits delta events.
	Stream bool
	// Parallelism bounds tool calls running concurrently, which are those of
	// read-only and concurrent tools. Defaults to 8.
	Parallelism int
	Logger      *slog.Logger

	// Output asks the model for JSON matching a schema and validates the
	// final answer; see Output.
	Output *Output
	// Limits bounds the usage of each run; Request.Limits overrides it.
	Limits Limits
	// Budget, if set, is called with the run's usage so far before each
	// model call and each batch of tool calls. An error ends the run with
	// StatusLimitExceeded and the error's text as the reason, for example
	// when the usage priced in a currency exceeds a budget.
	Budget func(model.Usage) error
	// StopAtTools ends the run as completed once a tool with one of these
	// names returns a result that is not an error. Result.Output is the
	// content of that result, and the model is not called again.
	StopAtTools []string
	// StopWhen ends the run as completed when any condition holds after a
	// turn's tool calls have run; see StopCondition.
	StopWhen []StopCondition
}

Settings are the run settings shared by Config and agent.Config: the model and its request options, approval and checkpointing, and the limits and stop conditions of runs.

type State

type State struct {
	// Version is the format version; see StateVersion.
	Version   int    `json:"version,omitempty"`
	RunID     string `json:"run_id"`
	SessionID string `json:"session_id,omitempty"`
	Agent     string `json:"agent,omitempty"`
	Turn      int    `json:"turn"`
	// System is the system prompt assembled at the start of the run.
	System string `json:"system,omitempty"`
	// Messages is the whole conversation, including prior history.
	Messages []*model.Message `json:"messages"`
	// Start is the index in Messages where this run's input begins.
	Start  int         `json:"start"`
	Usage  model.Usage `json:"usage"`
	Status Status      `json:"status"`
	Reason string      `json:"reason,omitempty"`
	// FinishReason is the finish reason of the last model response. A run
	// resumed after the model's final answer uses it to handle the answer.
	FinishReason model.FinishReason `json:"finish_reason,omitempty"`
	// Pending holds the planned tool calls of the current turn while they
	// are being approved or executed.
	Pending []*PendingCall `json:"pending,omitempty"`
	// Values holds middleware state; see Run.SetValue.
	Values    map[string]string `json:"values,omitempty"`
	UpdatedAt time.Time         `json:"updated_at"`
	// MaxTurns and Limits are the run's overrides of the configuration,
	// from its Request, kept so that a resumed run applies them too.
	MaxTurns int     `json:"max_turns,omitempty"`
	Limits   *Limits `json:"limits,omitempty"`
	// OutputRetries counts the answers sent back to the model because they
	// did not match Config.Output.
	OutputRetries int `json:"output_retries,omitempty"`
}

State is the resumable state of a run. It is what checkpoints store.

func (*State) Clone

func (s *State) Clone() *State

Clone returns a deep copy of s.

func (*State) NewMessages

func (s *State) NewMessages() []*model.Message

NewMessages returns the messages added by this run.

type Status

type Status string

Status is the state of a run.

const (
	StatusRunning   Status = "running"
	StatusCompleted Status = "completed"
	// StatusInterrupted means the run is waiting for tool approvals and can
	// be resumed.
	StatusInterrupted Status = "interrupted"
	StatusFailed      Status = "failed"
	// StatusMaxTurns means the turn limit was reached.
	StatusMaxTurns Status = "max_turns"
	// StatusStopped means middleware, for example a hook, stopped the run.
	StatusStopped Status = "stopped"
	// StatusBlocked means middleware, for example a UserPromptSubmit hook,
	// rejected the input.
	StatusBlocked Status = "blocked"
	// StatusIncomplete means the final answer is incomplete: it was cut off
	// by the output token limit or withheld by a content filter. Reason
	// says which. Result.Output holds what was returned unless an AnswerHolder
	// withholds it.
	StatusIncomplete Status = "incomplete"
	// StatusLimitExceeded means the run reached one of its Limits or its
	// Budget; Reason names it. Like StatusMaxTurns it is final: continue
	// the conversation with a new run, which starts with its own limits.
	StatusLimitExceeded Status = "limit_exceeded"
)

func (Status) Done

func (s Status) Done() bool

Done reports whether the status is final.

func (Status) Resumable

func (s Status) Resumable() bool

Resumable reports whether a run in this status can be resumed: an interrupted run waiting for approvals, a failed run, or a run still marked running because its process crashed or its consumer stopped. Checkpoints of runs that end in any other status are deleted.

type Step

type Step struct {
	// Turn is the turn number.
	Turn int
	// Message is the model's message of the turn.
	Message *model.Message
	// ToolResults are the results of its tool calls, in call order.
	ToolResults []*model.ToolResult
	// Usage is the run's usage so far.
	Usage model.Usage
}

Step is a turn whose tool calls have run, as a StopCondition sees it.

type StopCondition

type StopCondition func(step *Step) bool

StopCondition reports whether a run should end after a step. It must not modify the step. A run that a condition stops ends as completed, and Result.Output is the text of the model's last message, which may be empty when the model only called tools.

type ToolPlan

type ToolPlan struct {
	// Call is the tool call. BeforeTool may rewrite its arguments.
	Call *model.ToolCall
	// Info describes the tool; Known is false for unknown tools. Its
	// RequiresApproval is that of this call when the tool is a
	// tool.CallApprover.
	Info  tool.Info
	Known bool
	// contains filtered or unexported fields
}

ToolPlan is a tool call as seen by BeforeTool and AfterTool.

func (*ToolPlan) Allow

func (p *ToolPlan) Allow()

Allow skips approval unless another middleware asks or denies.

func (*ToolPlan) Approval

func (p *ToolPlan) Approval() (Approval, bool)

Approval returns the decision of the approval the call needed, without its UpdatedInput, and reports false if the call needed none or is not decided yet. AfterTool middleware uses it, for example, to remember an approval; see Approval.Remember.

func (*ToolPlan) Ask

func (p *ToolPlan) Ask(reason string)

Ask requires approval; reason is shown to the approver.

func (*ToolPlan) Decision

func (p *ToolPlan) Decision() (Decision, string)

Decision returns the decision so far and its reason.

func (*ToolPlan) Deny

func (p *ToolPlan) Deny(reason string)

Deny blocks the call; reason is shown to the model.

func (*ToolPlan) SetValue

func (p *ToolPlan) SetValue(key, value string)

SetValue stores a value with the call. It is saved with checkpoints, so AfterTool sees it after an interrupted run resumes. An empty value deletes the key.

func (*ToolPlan) Value

func (p *ToolPlan) Value(key string) (string, bool)

Value returns a value stored with SetValue.

type ToolProvider

type ToolProvider interface {
	Tools() []tool.Tool
}

ToolProvider contributes tools to the runtime's registry.

type ToolRevalidator

type ToolRevalidator interface {
	RevalidateTool(ctx context.Context, run *Run, plan *ToolPlan) error
}

ToolRevalidator checks arguments changed by an approved UpdatedInput, after approval and before execution. Middleware that enforces argument policies in BeforeTool must also implement this stage; BeforeTool itself is not repeated, so hooks and other one-time operations do not run twice.

RevalidateTool may Deny the call or return an error, as BeforeTool does. Ask is already satisfied by the explicit approval, including approval of the edited arguments, and does not prompt again. Call and Info describe those arguments, and Approval returns the decision. Revalidators must not rewrite Call; the runtime rejects a changed call rather than execute arguments an earlier revalidator did not check.

Revalidation runs in middleware order, stopping at a denial or error. Unchanged and denied approvals do not run this stage.

type ToolWrapper

type ToolWrapper interface {
	WrapTool(run *Run, next tool.Invoker) tool.Invoker
}

ToolWrapper wraps each tool execution, after the arguments have been validated. Read-only and concurrent calls run concurrently, so the returned invoker must be safe for concurrent use and may only call the read methods of run and Emit.

Directories

Path Synopsis
Package checkpointtest checks loop.Checkpointer implementations against the contract the built-in checkpointers follow.
Package checkpointtest checks loop.Checkpointer implementations against the contract the built-in checkpointers follow.

Jump to

Keyboard shortcuts

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