Documentation
¶
Overview ¶
Package loop contains Glue's provider-agnostic agent loop.
The loop streams assistant responses from a provider, executes requested tools, appends tool results, and repeats until the provider stops or the context is canceled. It must not depend on the public glue package, provider packages, stores, CLI code, or Markdown context discovery.
The entry point is Run, which executes a RunRequest until the provider stops or the context is canceled. Tools run sequentially in source order by default; set RunRequest.Parallel to dispatch a single assistant message's tool calls concurrently while preserving transcript order. RunRequest.MaxTurns bounds the turn count and surfaces budget exhaustion as StopReasonMaxTurns.
Index ¶
- Variables
- type AllowAll
- type ContentPart
- type ContentType
- type DenyAll
- type Event
- type EventType
- type Hook
- type ImageContent
- type Message
- type MessageRole
- type Permission
- type PermissionDecision
- type PermissionRequest
- type Provider
- type ProviderEvent
- type ProviderEventType
- type ProviderRequest
- type RememberScope
- type RunRequest
- type RunResult
- type StopReason
- type Tool
- type ToolCall
- type ToolExecutor
- type ToolResult
- type ToolSpec
- type Usage
Constants ¶
This section is empty.
Variables ¶
var ErrSkipTool = errors.New("glue: skip tool")
ErrSkipTool returned from Hook.PreTool records a skipped tool call as an error tool result without executing the tool.
Functions ¶
This section is empty.
Types ¶
type AllowAll ¶
type AllowAll struct{}
AllowAll allows every PermissionRequest. It is intended for tests and explicitly trusted hosts.
func (AllowAll) Decide ¶
func (AllowAll) Decide(context.Context, PermissionRequest) (PermissionDecision, error)
Decide implements Permission.
type ContentPart ¶
type ContentPart struct {
Type ContentType `json:"type"`
Text string `json:"text,omitempty"`
Thinking string `json:"thinking,omitempty"`
Image *ImageContent `json:"image,omitempty"`
ToolCall *ToolCall `json:"tool_call,omitempty"`
Signature string `json:"signature,omitempty"`
}
ContentPart is a provider-neutral message content block.
Type selects which payload field is meaningful; other fields stay zero. The struct shape is intentionally JSON-friendly so transcripts can be persisted by file-backed stores without a separate wire format.
type ContentType ¶
type ContentType string
ContentType identifies the active payload field in a ContentPart.
const ( ContentTypeText ContentType = "text" ContentTypeThinking ContentType = "thinking" ContentTypeImage ContentType = "image" ContentTypeToolCall ContentType = "tool_call" )
type DenyAll ¶
type DenyAll struct {
Reason string
}
DenyAll denies every PermissionRequest. It is intended for tests and explicitly locked-down hosts.
func (DenyAll) Decide ¶
func (d DenyAll) Decide(context.Context, PermissionRequest) (PermissionDecision, error)
Decide implements Permission.
type Event ¶
type Event struct {
Type EventType `json:"type"`
Message *Message `json:"message,omitempty"`
Messages []Message `json:"messages,omitempty"`
Delta string `json:"delta,omitempty"`
ToolCall *ToolCall `json:"tool_call,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
ToolName string `json:"tool_name,omitempty"`
ToolResult *ToolResult `json:"tool_result,omitempty"`
Error string `json:"error,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
Event is emitted by the agent loop for callers such as sessions and CLIs. The active fields depend on Type.
type EventType ¶
type EventType string
EventType identifies events emitted by the agent loop.
const ( EventLoopStart EventType = "loop_start" EventLoopEnd EventType = "loop_end" EventTurnStart EventType = "turn_start" EventTurnEnd EventType = "turn_end" EventMessageStart EventType = "message_start" EventMessageEnd EventType = "message_end" EventTextDelta EventType = "text_delta" EventToolStart EventType = "tool_start" EventToolEnd EventType = "tool_end" EventError EventType = "error" )
type Hook ¶
type Hook interface {
PreTool(ctx context.Context, call ToolCall) error
PostTool(ctx context.Context, call ToolCall, result *ToolResult) error
}
Hook observes or alters tool-call execution.
type ImageContent ¶
ImageContent stores an inline base64-encoded image.
type Message ¶
type Message struct {
ID string `json:"id,omitempty"`
Role MessageRole `json:"role"`
Content []ContentPart `json:"content,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"`
ToolName string `json:"tool_name,omitempty"`
IsError bool `json:"is_error,omitempty"`
Provider string `json:"provider,omitempty"`
Model string `json:"model,omitempty"`
StopReason StopReason `json:"stop_reason,omitempty"`
Usage *Usage `json:"usage,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
Message is a normalized transcript entry.
type MessageRole ¶
type MessageRole string
MessageRole identifies which actor produced a transcript message.
const ( MessageRoleUser MessageRole = "user" MessageRoleAssistant MessageRole = "assistant" MessageRoleTool MessageRole = "tool" )
type Permission ¶
type Permission interface {
Decide(ctx context.Context, req PermissionRequest) (PermissionDecision, error)
}
Permission decides whether a side-effecting tool call may run.
type PermissionDecision ¶
type PermissionDecision struct {
Allow bool `json:"allow"`
Reason string `json:"reason,omitempty"`
RememberFor RememberScope `json:"remember_for,omitempty"`
}
PermissionDecision is the host's answer to a PermissionRequest.
type PermissionRequest ¶
type PermissionRequest struct {
Tool string `json:"tool"`
Action string `json:"action"`
Target string `json:"target"`
Args json.RawMessage `json:"args,omitempty"`
SessionID string `json:"session_id,omitempty"`
}
PermissionRequest describes one side-effecting tool call for the host.
type Provider ¶
type Provider interface {
Stream(ctx context.Context, req ProviderRequest) (<-chan ProviderEvent, error)
}
Provider streams assistant events for a single assistant turn. Implementations must close the returned channel when the turn ends, including on error.
type ProviderEvent ¶
type ProviderEvent struct {
Type ProviderEventType `json:"type"`
Message *Message `json:"message,omitempty"`
Delta string `json:"delta,omitempty"`
ContentIndex int `json:"content_index,omitempty"`
ToolCall *ToolCall `json:"tool_call,omitempty"`
Error string `json:"error,omitempty"`
}
ProviderEvent is a provider-neutral streaming event. The active fields depend on Type: TextDelta uses Delta, ToolCall uses ToolCall, Done uses Message, Error uses Error.
type ProviderEventType ¶
type ProviderEventType string
ProviderEventType identifies events emitted by a provider stream.
const ( ProviderEventStart ProviderEventType = "start" ProviderEventTextDelta ProviderEventType = "text_delta" ProviderEventThinkingDelta ProviderEventType = "thinking_delta" ProviderEventToolCall ProviderEventType = "tool_call" ProviderEventDone ProviderEventType = "done" ProviderEventError ProviderEventType = "error" )
type ProviderRequest ¶
type ProviderRequest struct {
Model string `json:"model"`
SystemPrompt string `json:"system_prompt,omitempty"`
Messages []Message `json:"messages,omitempty"`
Tools []ToolSpec `json:"tools,omitempty"`
Options map[string]any `json:"options,omitempty"`
}
ProviderRequest is the normalized input sent to a provider for one assistant turn.
type RememberScope ¶
type RememberScope int
RememberScope tells a host permission implementation how broadly a positive decision may be remembered. Core loop code does not cache decisions.
const ( RememberNever RememberScope = iota RememberSession RememberSessionTarget RememberForever )
type RunRequest ¶
type RunRequest struct {
Provider Provider
Model string
SystemPrompt string
Messages []Message
Tools []Tool
Options map[string]any
MaxTurns int
Parallel bool
SessionID string
Permission Permission
Hooks []Hook
Emit func(Event)
}
RunRequest configures one Run.
Provider is required. MaxTurns defaults to 32 when zero or negative. Emit receives a snapshot of every loop event in source order; nil disables event delivery. Messages, Tools, and Options are defensively copied so callers may reuse the input slices and maps.
Parallel controls within-turn tool execution. When false (the default), tool calls in a single assistant message are executed sequentially in source order. When true, the loop fans them out concurrently and waits for all of them before appending tool-result messages — but the appended results, EventToolStart, and EventToolEnd are still emitted in assistant source order so the transcript stays deterministic.
type RunResult ¶
RunResult is returned by Run. Messages is the full transcript including the input messages and every message produced during this run. NewMessages is just the messages produced during this run, in append order.
func Run ¶
func Run(ctx context.Context, req RunRequest) (RunResult, error)
Run executes a provider-agnostic agent loop until the assistant stops requesting tools, the provider errors, the context is canceled, or MaxTurns is reached.
Tool execution is sequential and deterministic: requested tool calls are executed in the order they appear in the assistant message, their result messages are appended in that same order, and unknown tools, invalid JSON arguments, missing executors, and executor errors all become tool-result messages with IsError=true so the model can see and react instead of the loop crashing.
type StopReason ¶
type StopReason string
StopReason explains why a provider finished an assistant turn.
const ( StopReasonStop StopReason = "stop" StopReasonLength StopReason = "length" StopReasonToolUse StopReason = "tool_use" StopReasonError StopReason = "error" StopReasonCanceled StopReason = "canceled" // StopReasonMaxTurns marks the last assistant message in a run // that exited because the loop turn budget (RunRequest.MaxTurns) // was exhausted while the assistant still had pending tool calls. // Distinguishes "we ran out of budget" from "the model finished" // (Stop) and from provider-side truncation (Length), so agents can // retry with a higher budget cleanly. StopReasonMaxTurns StopReason = "max_turns" )
type Tool ¶
type Tool struct {
ToolSpec
Execute ToolExecutor `json:"-"`
}
Tool combines the provider-visible specification with its local executor. The Execute field is intentionally tagged json:"-" so transcripts and provider payloads exclude it.
type ToolCall ¶
type ToolCall struct {
ID string `json:"id"`
Name string `json:"name"`
Arguments json.RawMessage `json:"arguments,omitempty"`
}
ToolCall is a model request to invoke a named tool with JSON arguments.
type ToolExecutor ¶
type ToolExecutor func(ctx context.Context, call ToolCall) (ToolResult, error)
ToolExecutor runs a tool call locally and returns a normalized result.
type ToolResult ¶
type ToolResult struct {
Content []ContentPart `json:"content,omitempty"`
IsError bool `json:"is_error,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
ToolResult is the local result produced by a ToolExecutor.
type ToolSpec ¶
type ToolSpec struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters json.RawMessage `json:"parameters,omitempty"`
RequiresPermission bool `json:"-"`
PermissionAction string `json:"-"`
PermissionTarget func(ToolCall) string `json:"-"`
}
ToolSpec is the provider-visible description of a tool.
type Usage ¶
type Usage struct {
InputTokens int64 `json:"input_tokens,omitempty"`
OutputTokens int64 `json:"output_tokens,omitempty"`
CacheReadTokens int64 `json:"cache_read_tokens,omitempty"`
CacheWriteTokens int64 `json:"cache_write_tokens,omitempty"`
TotalTokens int64 `json:"total_tokens,omitempty"`
}
Usage captures token accounting reported by a provider when available.