runner

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Jun 3, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package runner executes the ReAct feedback loop for the fino Agent SDK. A Runner holds only configuration; each run owns its own message list, so a single Runner can be reused concurrently across independent runs.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotSuspended is returned by Result.SuspendedRun when the result did not
	// suspend (it completed normally). It is distinct from ErrInvalidApproval.
	ErrNotSuspended = errors.New("result is not suspended")
	// ErrInvalidApproval is the sentinel wrapped by ApprovalError. It reports an
	// invalid resume input: a malformed SuspendedRun, or an approval set that
	// does not match the suspended pending calls (missing, unknown, duplicate).
	ErrInvalidApproval = errors.New("invalid approval")
	// ErrResumeAgentMismatch indicates ResumeApproved was called with an agent
	// other than the one active when the run suspended (SuspendedRun.LastAgentName).
	// Resuming under the wrong agent would resolve tools in the wrong context.
	ErrResumeAgentMismatch = errors.New("resume agent does not match suspended agent")
)
View Source
var (
	// ErrMaxTurns indicates the run exceeded the configured maximum number of turns.
	ErrMaxTurns = errors.New("max turns exceeded")
	// ErrToolNotFound indicates the model requested a tool the current mode does not provide.
	ErrToolNotFound = errors.New("tool not found")
	// ErrSystemMessageInHistory indicates the run input contained a system
	// message; the Runner injects system instructions from the mode instead.
	ErrSystemMessageInHistory = errors.New("system message in history")
	// ErrToolDenied is the sentinel wrapped by ToolDeniedError when a Policy denies a tool call.
	ErrToolDenied = errors.New("tool denied")
	// ErrStreamContract indicates a model.Model.Stream implementation violated
	// its event contract: it must yield exactly one TurnMessage per turn and must
	// not yield FinalMessage (FinalMessage is emitted only by the Runner).
	ErrStreamContract = errors.New("model stream contract violated")
	// ErrInvalidToolCallID indicates a tool_use block carried an empty ID. The
	// Runner rejects the batch before authorizing or executing any tool, because
	// approval, the idempotency key, and the replay tape all key on the ID.
	ErrInvalidToolCallID = errors.New("invalid tool call id")
	// ErrDuplicateToolCallID indicates two tool_use blocks in the same batch
	// shared an ID, which makes approval matching and the idempotency key
	// ambiguous. The Runner rejects the batch before executing any tool.
	ErrDuplicateToolCallID = errors.New("duplicate tool call id")
)

Functions

This section is empty.

Types

type Approval added in v0.4.0

type Approval struct {
	CallID   string
	Approved bool
	Reason   string
}

Approval is a human decision about one suspended tool call, matched to the call by CallID (the tool_use block's ID). Approved=true executes the tool; Approved=false rejects it and writes Reason into a model-visible error tool_result so the model can adjust.

type ApprovalError added in v0.4.0

type ApprovalError struct {
	Missing   []string // pending CallIDs with no approval
	Unknown   []string // approval CallIDs not in the pending set
	Duplicate []string // CallIDs approved more than once
}

ApprovalError reports that the approvals passed to ResumeApproved did not validly cover the suspended pending calls. It wraps ErrInvalidApproval.

func (*ApprovalError) Error added in v0.4.0

func (e *ApprovalError) Error() string

Error implements the error interface.

func (*ApprovalError) Unwrap added in v0.4.0

func (e *ApprovalError) Unwrap() error

Unwrap returns ErrInvalidApproval so errors.Is(err, ErrInvalidApproval) is true.

type Input

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

Input is the initial message list for a run. Construct it with Text or Messages rather than building it directly.

func Messages

func Messages(messages []message.Message) Input

Messages returns an Input from an existing message history. The slice is copied, so later caller mutations do not affect the run.

func Text

func Text(text string) Input

Text returns an Input containing a single user text message.

type Option

type Option func(*Runner)

Option configures a Runner.

func WithHooks

func WithHooks(h *hooks.Hooks) Option

WithHooks sets the lifecycle hooks observed during a run.

func WithMaxConcurrency

func WithMaxConcurrency(n int) Option

WithMaxConcurrency sets the maximum number of tools the Runner may execute concurrently within a single tool-call batch. A value of n <= 1 (the default) keeps tools serial. A value of n > 1 permits up to n tools at once only when every tool in the batch declares tool.Effects.ParallelSafe; otherwise the whole batch falls back to serial execution. The Runner still authorizes all calls serially in call order, preserves result order, and is fail-fast on the first error.

func WithMaxTurns

func WithMaxTurns(n int) Option

WithMaxTurns sets the maximum number of model turns per run.

func WithPolicy

func WithPolicy(p policy.Policy) Option

WithPolicy sets the authorization policy consulted before each tool call. A nil policy is ignored, preserving the default AllowAll.

type PendingToolCall added in v0.3.0

type PendingToolCall struct {
	Tool   tool.Info
	Call   message.ToolUse
	Reason string
}

PendingToolCall describes a tool call that a Policy suspended. It carries the tool's static info, the original model tool_use, and the Policy's reason, so a caller can present the call for human approval and later resume it.

type Result

type Result struct {
	Message   message.Message
	Messages  []message.Message
	LastAgent *agent.Agent
	LastMode  string
	// Suspended reports that a Policy suspended the run before executing a tool
	// batch. The history tail is the dangling assistant tool_use message.
	Suspended bool
	// PendingCalls holds the suspended tool calls (only those whose decision
	// resolved to DecisionSuspend), in call order. It is non-empty only when
	// Suspended is true.
	PendingCalls []PendingToolCall
	// contains filtered or unexported fields
}

Result is the outcome of a run. A run terminates in one of two successful shapes: a completed run (Suspended == false) carries the final Message; a suspended run (Suspended == true) carries PendingCalls and leaves Message zero. The two are mutually exclusive (loop-semantics I3).

func (*Result) SuspendedRun added in v0.4.0

func (r *Result) SuspendedRun() (SuspendedRun, error)

SuspendedRun extracts a value snapshot from a suspended Result. It returns ErrNotSuspended if the result completed normally.

func (*Result) Text

func (r *Result) Text() string

Text returns the text of the final message.

type RunOption

type RunOption func(*runConfig)

RunOption configures a single run.

func WithMode

func WithMode(name string) RunOption

WithMode selects which mode of the Agent to start the run in. The default is the Agent's default mode.

func WithModelOptions

func WithModelOptions(opts ...model.Option) RunOption

WithModelOptions appends model options applied after the mode's own defaults.

func WithResumeFromPendingTools added in v0.2.1

func WithResumeFromPendingTools() RunOption

WithResumeFromPendingTools makes a run first execute any pending tool calls at the tail of the input history before calling the model. Pending calls are the tool_uses of the last message when it is an assistant message that carries at least one tool_use and therefore has no following tool_result message.

This is the minimal seam for mid-batch / human-in-the-loop resume identified by invariant I10 (see docs/spec/loop-semantics.md §7.2): it exposes the ability to continue a run that was interrupted after the model requested tools but before they ran (e.g. while awaiting human approval), without introducing any checkpoint, session, or graph concept. When the seam is off (the default) or the tail has no pending tool_use, it is a no-op and the run starts at the model as usual.

func WithRunID added in v0.6.0

func WithRunID(id string) RunOption

WithRunID sets the run-scoped identifier surfaced to tools via tool.ExecutionContext. It is shared by Run, Stream, and ResumeApproved. When omitted, the run ID is empty and the derived IdempotencyKey is also empty, which preserves prior behavior exactly.

type Runner

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

Runner executes the ReAct loop against an Agent. It holds only configuration (model, turn limit, policy, hooks); each Run owns its own message list.

func New

func New(m model.Model, opts ...Option) (*Runner, error)

New creates a Runner with the given model and options. It returns an error if the model is nil or the configured maximum turns is not positive. The defaults are 10 turns and an AllowAll policy.

func (*Runner) ResumeApproved added in v0.4.0

func (r *Runner) ResumeApproved(ctx context.Context, a *agent.Agent, suspended SuspendedRun, approvals []Approval, opts ...RunOption) (*Result, error)

ResumeApproved continues a suspended run after a human has approved or rejected its pending tool calls. It validates that a matches the agent active at suspend time and that the approvals validly cover the pending calls, then executes the batch in call order — approved (and previously-allowed) calls run their tools; rejected calls produce a model-visible error tool_result — and resumes the ReAct loop from the next model turn. It does not re-consult the Policy: human approval replaces policy authorization for the suspended calls.

func (*Runner) Run

func (r *Runner) Run(ctx context.Context, a *agent.Agent, input Input, opts ...RunOption) (*Result, error)

Run executes the ReAct loop until the model returns a message with no tool calls, the turn limit is reached, or an error occurs. It returns an error if the agent is nil, the input contains a system message, or the selected mode is not found.

func (*Runner) Stream

func (r *Runner) Stream(ctx context.Context, a *agent.Agent, input Input, opts ...RunOption) iter.Seq2[model.Event, error]

Stream executes the ReAct loop like Run but yields semantic events as they occur. Iteration stops after a final message with no tool calls, on the turn limit, or on the first error. Terminal errors are yielded as a model.StreamError alongside a non-nil iterator error. The arguments mirror Run.

type SuspendedRun added in v0.4.0

type SuspendedRun struct {
	Messages      []message.Message
	LastAgentName string
	LastMode      string
	PendingCalls  []PendingToolCall
	// RunID is the run-scoped identifier captured at suspend time (empty when
	// the original run used no WithRunID). ResumeApproved restores it so
	// approved and previously-allowed calls receive the same
	// tool.ExecutionContext.IdempotencyKey they would have in the original run
	// (loop-semantics I13). Fixtures without it deserialize to "".
	RunID string
}

SuspendedRun is a plain value snapshot of a suspended run, extracted from a suspended Result via Result.SuspendedRun. It is not a checkpoint, state machine, or anything the core persists; the caller persists it however they like and later passes it to Runner.ResumeApproved together with the live agent. LastAgentName and LastMode record the agent and mode active at suspend time (a handoff may have switched away from the root agent before the suspend); ResumeApproved checks the agent the caller passes against LastAgentName so the resume re-enters the correct context. The agent itself is code reconstructed by the caller, so the snapshot holds only its name — no live object reference. PendingCalls holds only the suspended calls.

The snapshot contains no live object references, so it is straightforward to serialize; whether json.Marshal succeeds depends on the caller's data — notably tool.Info.Metadata (map[string]any) on each PendingCall, which the core does not sanitize. Keep tool metadata JSON-marshalable if you persist SuspendedRun as JSON.

func SuspendedRunFrom added in v0.8.0

func SuspendedRunFrom(e model.Suspended) SuspendedRun

SuspendedRunFrom rebuilds a SuspendedRun from a model.Suspended stream event, so a Stream consumer can resume with ResumeApproved exactly as a Run-path caller would from Result.SuspendedRun. The two carry the same data; this is the Stream-side adapter, kept here because model must not import runner (loop-semantics §5).

type ToolDeniedError

type ToolDeniedError struct {
	Tool     tool.Info
	Decision policy.Decision
}

ToolDeniedError reports that a Policy denied a tool invocation. It wraps ErrToolDenied and carries the denied tool's Info and the Policy Decision.

func (*ToolDeniedError) Error

func (e *ToolDeniedError) Error() string

Error implements the error interface.

func (*ToolDeniedError) Unwrap

func (e *ToolDeniedError) Unwrap() error

Unwrap returns ErrToolDenied so errors.Is(err, ErrToolDenied) reports true.

Jump to

Keyboard shortcuts

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