Documentation
¶
Overview ¶
Package agent is a provider-neutral orchestration layer built on the llm package.
It turns a single model request into a complete tool-call loop: stream a turn, execute the tool calls the model requests, append the results, and continue until the model stops. RunLoop is the stateless engine; Agent is a thin stateful wrapper that adds a retained transcript, event subscription, and steering and follow-up queues.
A run operates on AgentMessage values — standard llm messages adapted with FromLLM, plus any UI-only messages an application keeps in the transcript — and projects them to llm.Message only at the request boundary via ConvertToLLM. Extension points are function fields on LoopConfig.
The package bundles no concrete tools, persistence, or system prompt, and leaves history storage and context compaction to the caller; the TransformContext hook is where compaction would attach.
Index ¶
- func RunLoop(ctx context.Context, prompts []AgentMessage, base Context, cfg LoopConfig) <-chan AgentEvent
- func ToLLM(m AgentMessage) (llm.Message, bool)
- type AfterToolCallCtx
- type AfterToolCallResult
- type Agent
- func (a *Agent) Abort()
- func (a *Agent) ClearFollowUpQueue()
- func (a *Agent) ClearQueues()
- func (a *Agent) ClearSteeringQueue()
- func (a *Agent) Continue(ctx context.Context) error
- func (a *Agent) FollowUp(message AgentMessage)
- func (a *Agent) HasQueuedMessages() bool
- func (a *Agent) Prompt(ctx context.Context, input any) error
- func (a *Agent) Reset()
- func (a *Agent) SetModel(model llm.Model)
- func (a *Agent) SetSystemPrompt(prompt string)
- func (a *Agent) SetThinkingLevel(level llm.ModelThinkingLevel)
- func (a *Agent) SetToolExecution(mode ExecutionMode)
- func (a *Agent) SetTools(tools []AgentTool)
- func (a *Agent) Snapshot() State
- func (a *Agent) Steer(message AgentMessage)
- func (a *Agent) Subscribe(listener func(AgentEvent)) (unsubscribe func())
- type AgentEvent
- type AgentEventType
- type AgentMessage
- type AgentTool
- type BeforeToolCallCtx
- type Context
- type Custom
- type ExecutionMode
- type LoopConfig
- type Options
- type QueueMode
- type State
- type StreamFn
- type ToolResult
- type TurnCtx
- type TurnUpdate
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RunLoop ¶
func RunLoop(ctx context.Context, prompts []AgentMessage, base Context, cfg LoopConfig) <-chan AgentEvent
RunLoop drives a complete tool-call loop and returns a channel of events. The final AgentEnd event carries the messages the run appended to the transcript.
prompts are the new messages that start the run; base is the existing context they extend. The loop streams an assistant turn, executes any tool calls, appends results, then consults PrepareNextTurn and ShouldStopAfterTurn before continuing. When no tool calls and no steering messages remain, it polls GetFollowUpMessages; if none arrive, the run ends.
The caller must drain the returned channel until it closes.
func ToLLM ¶ added in v0.5.2
func ToLLM(m AgentMessage) (llm.Message, bool)
ToLLM returns the standard llm.Message wrapped by FromLLM, reporting false for a custom (UI-only) AgentMessage that has no llm projection. It is the inverse of FromLLM, intended for use inside a custom ConvertToLLM that needs to pass the adapted messages through while projecting its own message types.
Types ¶
type AfterToolCallCtx ¶
type AfterToolCallCtx struct {
AssistantMessage llm.AssistantMessage
ToolCall llm.ToolCall
Args any
Result ToolResult
IsError bool
Context Context
}
AfterToolCallCtx is passed to AfterToolCall, after the tool executes and before its events are emitted.
type AfterToolCallResult ¶
type AfterToolCallResult struct {
Content []llm.ToolResultContent
Details any
IsError *bool
Terminate *bool
}
AfterToolCallResult overrides parts of an executed tool result. Each field overrides the corresponding result value only when set; a nil field keeps the original. Content replaces the whole content slice when non-nil; Details replaces the details when non-nil.
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent is a stateful wrapper over RunLoop. It owns the transcript, fans events out to subscribers, and backs the steering and follow-up queues.
Prompt blocks until the run completes; call it from its own goroutine if you want to Steer, FollowUp, or Abort concurrently. All methods are safe for concurrent use.
func (*Agent) ClearFollowUpQueue ¶
func (a *Agent) ClearFollowUpQueue()
ClearFollowUpQueue drops all queued follow-up messages.
func (*Agent) ClearQueues ¶
func (a *Agent) ClearQueues()
ClearQueues drops all queued steering and follow-up messages.
func (*Agent) ClearSteeringQueue ¶
func (a *Agent) ClearSteeringQueue()
ClearSteeringQueue drops all queued steering messages.
func (*Agent) Continue ¶
Continue resumes a run from the current transcript without adding a new message, blocking until it completes. Use it to retry or to respond after messages were appended out of band.
The transcript must be non-empty. A provider needs a user or tool result as the latest turn, so when the transcript ends with an assistant message, Continue falls back to queued messages: it drains the steering queue first, then the follow-up queue, and runs whatever it finds as the next prompt. It returns an error only when the last message is an assistant and both queues are empty.
func (*Agent) FollowUp ¶
func (a *Agent) FollowUp(message AgentMessage)
FollowUp queues a message to process after the current run would stop.
func (*Agent) HasQueuedMessages ¶
HasQueuedMessages reports whether any steering or follow-up message is queued.
func (*Agent) Prompt ¶
Prompt starts a run from a text string, a single AgentMessage, or a slice of them, and blocks until the run completes. It appends the run's messages to the transcript and returns an error if the run ended in failure or cancellation. Calling Prompt while a run is in progress returns an error.
func (*Agent) Reset ¶
func (a *Agent) Reset()
Reset clears the transcript, the last error, and both queues, keeping the configuration (model, tools, system prompt, hooks). It is meant to be called when the agent is idle.
func (*Agent) SetModel ¶ added in v0.5.0
SetModel replaces the model used on the next run. To switch models within a single run, use PrepareNextTurn instead.
func (*Agent) SetSystemPrompt ¶ added in v0.5.0
SetSystemPrompt replaces the system prompt used on the next run.
func (*Agent) SetThinkingLevel ¶ added in v0.5.0
func (a *Agent) SetThinkingLevel(level llm.ModelThinkingLevel)
SetThinkingLevel replaces the reasoning level used on the next run.
func (*Agent) SetToolExecution ¶ added in v0.5.0
func (a *Agent) SetToolExecution(mode ExecutionMode)
SetToolExecution sets the default tool execution mode for the next run.
func (*Agent) SetTools ¶ added in v0.5.0
SetTools replaces the tools available on the next run. The slice is copied.
func (*Agent) Steer ¶
func (a *Agent) Steer(message AgentMessage)
Steer queues a message to inject into the current run before its next turn.
func (*Agent) Subscribe ¶
func (a *Agent) Subscribe(listener func(AgentEvent)) (unsubscribe func())
Subscribe registers a listener for run events and returns a function that removes it. Listeners are called synchronously from the goroutine running Prompt, in event order.
type AgentEvent ¶
type AgentEvent struct {
Type AgentEventType
// Message is the message a lifecycle event refers to.
Message AgentMessage
// LLMEvent is the underlying llm event, set on MessageUpdate.
LLMEvent *llm.Event
// ToolResults are the tool results produced during a turn, set on TurnEnd.
ToolResults []llm.ToolResultMessage
// ToolCallID and ToolName identify the tool on tool execution events.
ToolCallID string
ToolName string
// Args is the validated tool arguments on tool execution events.
Args any
// Result is the (possibly partial) tool result on tool execution events.
Result any
// IsError reports whether a finished tool result is an error.
IsError bool
// Messages carries the messages the run appended, set on AgentEnd.
Messages []AgentMessage
}
AgentEvent is a single update emitted while running an agent. Fields are populated according to Type; unrelated fields are left zero.
type AgentEventType ¶
type AgentEventType string
AgentEventType identifies the kind of update emitted while a run progresses.
const ( // AgentStart marks the beginning of a run. AgentStart AgentEventType = "agent_start" // AgentEnd is the final event of a run; it carries the appended messages. AgentEnd AgentEventType = "agent_end" // TurnStart marks the beginning of one assistant turn. TurnStart AgentEventType = "turn_start" // TurnEnd marks the end of a turn, after its tool calls are executed. TurnEnd AgentEventType = "turn_end" // MessageStart marks a user, assistant, or tool-result message entering the // transcript. MessageStart AgentEventType = "message_start" // MessageUpdate carries an incremental assistant update; LLMEvent is the // underlying llm.Event passed through. MessageUpdate AgentEventType = "message_update" // MessageEnd marks a completed message. MessageEnd AgentEventType = "message_end" // ToolStart marks the start of a tool execution. ToolStart AgentEventType = "tool_execution_start" // ToolUpdate carries a partial tool result streamed during execution. ToolUpdate AgentEventType = "tool_execution_update" // ToolEnd marks a finished tool execution. ToolEnd AgentEventType = "tool_execution_end" )
type AgentMessage ¶
type AgentMessage interface {
// contains filtered or unexported methods
}
AgentMessage is any entry that can appear in an agent transcript: a standard llm message adapted with FromLLM, or an application's own UI-only message type that embeds Custom. UI-only messages take part in history and event emission but are filtered out by ConvertToLLM before the model sees them.
func FromLLM ¶
func FromLLM(m llm.Message) AgentMessage
FromLLM adapts a standard llm.Message into an AgentMessage. This is the common path for user, assistant, and tool-result messages, since the agent package cannot add methods to types owned by llm.
func UserMessage ¶ added in v0.4.0
func UserMessage(text string, images ...llm.ImageContent) AgentMessage
UserMessage builds a user AgentMessage from text and optional images — the common case for a multimodal prompt. The text block comes first, followed by each image in order. Pass the result to Prompt, Steer, FollowUp, or use it as a seed message.
type AgentTool ¶
type AgentTool struct {
// Definition is the schema and description advertised to the model.
Definition llm.ToolDefinition
// Label is an optional human-readable name for UI display. It is metadata for
// callers and does not affect execution.
Label string
// PrepareArguments optionally rewrites the raw tool-call arguments before
// schema validation, for tolerating provider quirks or filling defaults. It
// returns the arguments to validate and execute. Nil leaves them unchanged.
PrepareArguments func(arguments map[string]any) map[string]any
// Execute runs the tool. It reports failure by returning an error, which the
// engine turns into an error tool result so one failing tool does not abort
// the run. onUpdate streams partial results and is valid only for the
// duration of the call.
Execute func(ctx context.Context, callID string, args json.RawMessage, onUpdate func(ToolResult)) (ToolResult, error)
// ExecutionMode overrides the loop default for this tool. Empty inherits it.
ExecutionMode ExecutionMode
}
AgentTool is a tool the model may call during a run.
type BeforeToolCallCtx ¶
type BeforeToolCallCtx struct {
AssistantMessage llm.AssistantMessage
ToolCall llm.ToolCall
Args any
Context Context
}
BeforeToolCallCtx is passed to BeforeToolCall, after arguments validate and before the tool executes.
type Context ¶
type Context struct {
SystemPrompt string
Messages []AgentMessage
Tools []AgentTool
}
Context is the starting transcript, system prompt, and tools for a run.
type Custom ¶
type Custom struct{}
Custom is embedded by an application's own message types so they satisfy AgentMessage without referencing the interface's unexported marker.
type Notification struct {
agent.Custom
Text string
}
type ExecutionMode ¶
type ExecutionMode string
ExecutionMode selects whether a tool batch runs sequentially or in parallel.
const ( // ExecutionParallel runs a batch's tools concurrently. It is the default. ExecutionParallel ExecutionMode = "parallel" // ExecutionSequential runs a batch's tools one at a time. ExecutionSequential ExecutionMode = "sequential" )
type LoopConfig ¶
type LoopConfig struct {
// Model is the model used for turns until PrepareNextTurn replaces it.
Model llm.Model
// StreamOptions are the per-request options passed to the stream function.
StreamOptions llm.StreamOptions
// StreamFn reaches a model for one turn. A nil value uses llm.Stream. It
// exists mainly as a seam for tests and custom transports.
StreamFn StreamFn
// ConvertToLLM projects the transcript into llm.Message values for one
// request. A nil value uses the default, which unwraps FromLLM messages and
// drops everything else.
ConvertToLLM func([]AgentMessage) []llm.Message
// GetAPIKey resolves the API key for the model's provider before each turn,
// for short-lived tokens that may expire during a long run. A non-empty
// return overrides StreamOptions.APIKey; nil or "" leaves it unchanged.
GetAPIKey func(provider string) string
// ToolExecution is the default batch execution mode. Empty means parallel.
ToolExecution ExecutionMode
// BeforeToolCall runs after arguments validate and before execution.
// Returning block=true skips the tool; reason becomes the error result text.
BeforeToolCall func(BeforeToolCallCtx) (block bool, reason string)
// AfterToolCall runs after a tool finishes. A non-nil return overrides the
// executed result field by field; nil keeps it unchanged.
AfterToolCall func(AfterToolCallCtx) *AfterToolCallResult
// ShouldStopAfterTurn requests a graceful stop after the current turn,
// before another model request starts.
ShouldStopAfterTurn func(TurnCtx) bool
// PrepareNextTurn may replace the model, thinking level, or context for the
// next turn. Returning nil keeps the current settings.
PrepareNextTurn func(TurnCtx) *TurnUpdate
// TransformContext adjusts the transcript before projection. It is the
// attachment point for context compaction; phase one ships no default.
TransformContext func([]AgentMessage) []AgentMessage
// GetSteeringMessages is polled after each turn's tool calls finish, to
// inject messages mid-run.
GetSteeringMessages func() []AgentMessage
// GetFollowUpMessages is polled when the agent would otherwise stop, to
// continue it with another turn.
GetFollowUpMessages func() []AgentMessage
}
LoopConfig configures a run. All extension points are function fields; the zero value of each is "no hook". Given a Model and ConvertToLLM, the zero config is a plain tool loop with no interception.
type Options ¶
type Options struct {
SystemPrompt string
Model llm.Model
ThinkingLevel llm.ModelThinkingLevel
Tools []AgentTool
Messages []AgentMessage
ConvertToLLM func([]AgentMessage) []llm.Message
TransformContext func([]AgentMessage) []AgentMessage
ToolExecution ExecutionMode
// GetAPIKey resolves the provider API key before each turn, for short-lived
// tokens. A non-empty return overrides the key; nil or "" leaves it unchanged.
GetAPIKey func(provider string) string
// SteeringMode and FollowUpMode control how many queued messages are injected
// at one drain point. The zero value is QueueOneAtATime.
SteeringMode QueueMode
FollowUpMode QueueMode
// StreamFn reaches a model for one turn. A nil value uses llm.Stream. It
// exists mainly as a seam for tests and custom transports.
StreamFn StreamFn
// StreamOptions are the base per-request options passed to the stream
// function on every turn, for knobs like Temperature, MaxTokens, Headers, the
// OnRequest and OnResponse observers, or the RewriteRequest hook. The agent
// sets Reasoning from ThinkingLevel and resolves APIKey via GetAPIKey, so
// values in those two fields are ignored here.
StreamOptions llm.StreamOptions
BeforeToolCall func(BeforeToolCallCtx) (block bool, reason string)
AfterToolCall func(AfterToolCallCtx) *AfterToolCallResult
ShouldStopAfterTurn func(TurnCtx) bool
PrepareNextTurn func(TurnCtx) *TurnUpdate
}
Options configures a new Agent. The hook fields mirror LoopConfig and apply to every run the agent drives.
type QueueMode ¶
type QueueMode string
QueueMode controls how many queued steering or follow-up messages are injected at one drain point.
type State ¶
type State struct {
SystemPrompt string
Model llm.Model
ThinkingLevel llm.ModelThinkingLevel
Tools []AgentTool
Messages []AgentMessage
// IsStreaming reports whether a prompt or continuation is in progress.
IsStreaming bool
// StreamingMessage is the partial message for the response currently
// streaming, or nil when none is in flight. It updates as deltas arrive and
// clears when the message completes.
StreamingMessage AgentMessage
// PendingToolCalls holds the ids of tool calls currently executing, in the
// order they started.
PendingToolCalls []string
// ErrorMessage holds the error from the most recent failed turn, if any.
ErrorMessage string
}
State is a read-only snapshot of an Agent's runtime state.
type StreamFn ¶
type StreamFn func(ctx context.Context, model llm.Model, input llm.Context, options llm.StreamOptions) (<-chan llm.Event, error)
StreamFn reaches a model for one turn. It has the same shape as llm.Stream, which is the default when LoopConfig.StreamFn is nil.
Like llm.Stream, it must not fail by panicking: a setup failure is returned as an error, and a mid-stream failure arrives on the channel as an EventError carrying an AssistantMessage whose StopReason is error or aborted.
type ToolResult ¶
type ToolResult struct {
// Content is the text or image content returned to the model.
Content []llm.ToolResultContent
// Details is arbitrary structured data for logs or UI rendering.
Details any
// Terminate hints that the run should stop after the current tool batch. A
// batch stops the run only when every result in it sets Terminate.
Terminate bool
}
ToolResult is what a tool returns to the model, with optional structured details for logging or UI and an optional early-termination hint.
type TurnCtx ¶
type TurnCtx struct {
// Message is the assistant message that completed the turn.
Message llm.AssistantMessage
// ToolResults are the tool results produced during the turn.
ToolResults []llm.ToolResultMessage
// Context is the current run context.
Context Context
// NewMessages are the messages this run would return if it exits now.
NewMessages []AgentMessage
}
TurnCtx is passed to the per-turn hooks after a turn's assistant message and tool results have been appended.
type TurnUpdate ¶
type TurnUpdate struct {
Context *Context
Model *llm.Model
ThinkingLevel *llm.ModelThinkingLevel
}
TurnUpdate replaces runtime state before the next turn. Nil fields keep the current value.