agentgo

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

README

AgentGo

AgentGo is a minimal, composable Go library for building AI agent applications.

AgentGo evolved from AgentCore and now develops independently.

English | 中文

What it provides

  • A message-native Agent Loop: applications keep AgentMessage; model-level Message exists only at the call boundary.
  • A single event stream for model output, tools, context projection and compaction, retries, and completion.
  • Replaceable models, tools, context management, compaction, stop guards, turn hooks, and permission gates.
  • Stateful Agent and standalone AgentLoop entry points over the same execution kernel.
  • Steering, follow-up, background tasks, sub-agents, and multi-agent team primitives.
  • Trajectory-ready context contracts: ContextItem records what the projected context contains, while ContextDemand provides the matching application-neutral demand shape.

The kernel stays policy-light: applications decide what information means, which tools are allowed, when work is complete, and how trajectories are evaluated.

Install

go get github.com/compforge/agentgo

Quick Start

package main

import (
    "fmt"
    "os"

    "github.com/compforge/agentgo"
    "github.com/compforge/agentgo/llm"
    "github.com/compforge/agentgo/tools"
)

func main() {
    model, _ := llm.NewModel(
        "openai",
        "gpt-5-mini",
        llm.WithAPIKey(os.Getenv("OPENAI_API_KEY")),
    )

    agent := agentgo.NewAgent(
        agentgo.WithModel(model),
        agentgo.WithSystemPrompt("You are a helpful coding assistant."),
        agentgo.WithTools(tools.NewRead(".", tools.NewFileReadState())),
    )

    agent.Subscribe(func(event agentgo.Event) {
        if event.Type == agentgo.EventMessageEnd {
            if message, ok := event.Message.(agentgo.Message); ok && message.Role == agentgo.RoleAssistant {
                fmt.Println(message.TextContent())
            }
        }
    })

    agent.Prompt("Summarize this repository.")
    agent.WaitForIdle()
}

Core flow

AgentMessage
    ─ContextManager / Compactor─▶ projected AgentMessage
    ─ToMessage─▶ model Message
    ─Model / Tool─▶ Event stream
    ─commit─▶ AgentMessage history

ContextItemProvider lets an application message expose identifiable information without changing its model rendering. Before each model call, EventContextProjected reports the inventory from the actual projected context. ContextItem and ContextDemand share ContextKey; applications and evaluators own all label meanings and demand-extraction rules.

Extension points

Need Contract
Model provider ChatModel
Application message AgentMessage
Tool capability Tool and optional tool interfaces
Tool authorization ToolGate
Context projection and recovery ContextManager
Compaction policy context.Compactor
Turn preparation and observation WithBeforeTurn / WithAfterTurn
Stop policy StopGuard
UI, logging, and trajectory capture <-chan Event / Agent.Subscribe

Built-in packages include model adapters under llm/, context strategies under context/, coding tools under tools/, and optional subagent/, team/, task/, proxy/, and permission/ capabilities.

Design and API

Stability

Agent, AgentLoop, Event, Tool, AgentMessage, and Message are the primary stable surface. Examples and internal implementation details may evolve faster.

License

Apache License 2.0

Documentation

Overview

Package agentgo is a minimal, composable toolkit for building AI agent applications in Go. It provides the execution kernel — the agent loop, tool dispatch, context management, and a single event stream — and leaves policy (which model, which tools, when to stop, how to render) to the caller.

Two entry points, one for each level of control:

  • AgentLoop is a pure function: given prompts, context, and a LoopConfig, it runs the loop and returns a <-chan Event. It holds no state of its own — every dependency is injected and every result is an event. Use it when you want to own the state and drive the loop directly.

  • Agent wraps the loop with conversation state, message queues, and listener dispatch. Construct it with NewAgent and the With* options, register a callback with Agent.Subscribe, then call Agent.Prompt. This is the common case.

Every lifecycle signal — streamed text, tool execution, retries, the final summary — flows through the one Event channel. Context-aware application messages may expose ContextItem values; each model call then emits the projected inventory so trajectory evaluators can correlate it with application-defined ContextDemand values without moving interpretation policy into the kernel. A single consumer can drive any front end (TUI, web, Slack, logs) without the kernel knowing which.

A small, stable surface carries most uses: Agent, AgentLoop, Event, Tool, AgentMessage, and Message. AgentMessage is the application transcript type; Message is the model protocol type produced only at the call boundary. The rest is opt-in — context strategies, stop guards, sub-agents, middleware — reached only when a use case needs it.

Models are adapters behind the ChatModel interface; the kernel imports no LLM SDK. Provider errors are classified through the RetryableError and RetryHinter interfaces plus the ErrProvider* sentinels, so any backend can take part in retries and failover. The bundled llm adapter (agentgo/llm) covers OpenAI, Anthropic, and Gemini via litellm.

See the examples directory for runnable single- and multi-agent programs.

Index

Constants

View Source
const (
	IssueMissing = "missing"
	IssueType    = "type"
	IssueValue   = "value"
	IssueUnknown = "unknown"
)
View Source
const (
	ResponseFormatText       = "text"
	ResponseFormatJSONObject = "json_object"
	ResponseFormatJSONSchema = "json_schema"
)

Variables

View Source
var (
	ErrMaxTurns         = errors.New("max turns reached")
	ErrNoModel          = errors.New("no model configured")
	ErrNoMessages       = errors.New("cannot continue: no messages in context")
	ErrAlreadyRunning   = errors.New("agent is already running")
	ErrBadContinuation  = errors.New("cannot continue from this message role without queued messages")
	ErrStopGuard        = errors.New("stop guard escalated: run terminated")
	ErrContextOverflow  = errors.New("context window overflow")
	ErrStreamPartial    = errors.New("stream closed without done event")
	ErrToolValidation   = errors.New("tool argument validation failed")
	ErrInjectNilMessage = errors.New("inject message is nil")
	ErrRunsHeld         = errors.New("agent runs are held")
)

Sentinel errors. Use with errors.Is.

View Source
var (
	ErrProviderRateLimit     = errors.New("provider rate limit")
	ErrProviderQuota         = errors.New("provider quota exhausted")
	ErrProviderTimeout       = errors.New("provider timeout")
	ErrProviderStreamIdle    = errors.New("provider stream idle")
	ErrProviderNetwork       = errors.New("provider network")
	ErrProviderAuth          = errors.New("provider auth")
	ErrProviderOverloaded    = errors.New("provider overloaded")
	ErrProviderContentFilter = errors.New("provider content filter")
)

Provider runtime sentinels. These categorize errors returned by the model adapter at call time (provider API errors, network failures, server responses). Use ClassifyProvider to derive the most specific sentinel from an error chain, or match directly with errors.Is.

Functions

func AgentLoop

func AgentLoop(ctx context.Context, prompts []AgentMessage, agentCtx AgentContext, config LoopConfig) <-chan Event

AgentLoop starts an agent loop with new prompt messages. Prompts are added to context and events are emitted for them.

The returned channel MUST be consumed until it closes: while the run is live the loop blocks on a full channel (backpressure, no event loss), so abandoning the channel without canceling ctx leaks the loop goroutine. To stop early, cancel ctx and keep draining — after cancellation delivery degrades to best-effort and the loop is guaranteed to exit and close the channel even if no one is reading.

func AgentLoopContinue

func AgentLoopContinue(ctx context.Context, agentCtx AgentContext, config LoopConfig) <-chan Event

AgentLoopContinue continues from existing context without adding new messages. The last message in context must convert to user or tool role via ToMessage.

The returned channel follows the same consumption contract as AgentLoop: drain until close, or cancel ctx and keep draining to stop early.

func AssertMessageSequence

func AssertMessageSequence(msgs []Message) error

AssertMessageSequence returns an error when the transcript would require synthetic repair before being sent to an LLM provider.

func ClassifyProvider

func ClassifyProvider(err error) error

ClassifyProvider inspects an LLM/provider error and returns the most specific matching sentinel from this package's Err* variables. Returns nil when err is nil; returns err unchanged when no classification applies, so callers can wrap with their own context.

Stream-idle is checked before generic timeout: it is a stuck connection that failover can typically rescue, whereas a generic timeout may just be a slow model. Both error-chain matching (adapters map stream-idle onto ErrProviderStreamIdle) and message pattern matching are supported because sub-agent JSON results flatten the original error to a plain string.

Context overflow is intentionally not returned here — use IsContextOverflow, which covers both the agentgo wrapper and adapter-classified errors.

func ErrorKind

func ErrorKind(err error) string

ErrorKind returns a stable, log-friendly label for err: "canceled", "stop_guard", "max_turns", "context_overflow", "stream_partial", "tool_validation", "stream_idle", "quota", "rate_limit", "timeout", "auth", "network", "overloaded", "content_filter". Returns "" for nil and "unknown" when nothing matches.

Labels are part of the public API contract — they will not change between minor versions, so harnesses can key alert routing and log filters on them instead of matching error strings.

func FailoverReason

func FailoverReason(err error) string

FailoverReason returns a stable short label ("rate_limit" / "timeout" / "stream_idle" / "network" / "overloaded") suitable for structured logging. Returns "" when err is not failover-eligible.

func IsContextOverflow

func IsContextOverflow(err error) bool

IsContextOverflow reports whether err indicates a context-overflow condition. Both the agentgo wrapper (*ContextOverflowError) and adapter-classified provider errors map onto ErrContextOverflow, so a single errors.Is covers both layers. Convenience for callers that want to detect "request too big" without caring where it surfaced.

func IsFailoverEligible

func IsFailoverEligible(err error) bool

IsFailoverEligible reports whether err matches a transient provider error suitable for cross-provider failover: rate_limit, timeout, network, or stream_idle. Returns false for auth errors, content filters, context_overflow, user cancellation, or unclassified errors.

func IsStreamIdleMessage

func IsStreamIdleMessage(s string) bool

IsStreamIdleMessage reports whether s contains the rendered marker of a stream idle-timeout abort. Useful when only the error string survives (sub-agent JSON results, structured event payloads that flatten the chain).

func ReactivateDeferred

func ReactivateDeferred(tools []Tool, msgs []AgentMessage)

ReactivateDeferred scans restored messages for tool_reference blocks and pre-activates them via the DeferActivator found in tools. This must be called after restoring a session to avoid "Tool reference not found" errors.

func ReportToolProgress

func ReportToolProgress(ctx context.Context, progress ProgressPayload)

ReportToolProgress reports structured progress during tool execution. Silently ignored if no callback is registered in the context.

func WithToolProgress

func WithToolProgress(ctx context.Context, fn ToolProgressFunc) context.Context

WithToolProgress injects a progress callback into the context.

Types

type ActivityDescriber

type ActivityDescriber interface {
	ActivityDescription(args json.RawMessage) string
}

ActivityDescriber is an optional interface for tools that provide a human-readable activity description for UI display.

type AfterTurnContext

type AfterTurnContext struct {
	TurnIndex   int
	Message     AgentMessage
	ToolResults []ToolResult
	Context     AgentContext
}

AfterTurnContext describes one normally completed model/tool turn. Context includes the assistant message and all tool results from that turn.

type AfterTurnHook

type AfterTurnHook func(context.Context, AfterTurnContext) error

AfterTurnHook runs after a normal turn's assistant message and tool results have been committed. It may update application state consumed by the next BeforeTurnHook. Returning an error stops the run.

type Agent

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

Agent is a stateful wrapper around the agent loop. It consumes loop events to update internal state, just like any external listener.

func NewAgent

func NewAgent(opts ...AgentOption) *Agent

NewAgent creates a new Agent with the given options.

When a ContextManager is set, the agent auto-wires the context-token estimator and context window when the manager implements the optional ContextEstimator / ContextWindowProvider interfaces.

func (*Agent) Abort

func (a *Agent) Abort()

Abort cancels the current execution and emits an abort marker message so the LLM knows the user interrupted.

Abort does not touch the steering/follow-up queues: messages queued before the cancellation stay queued and are replayed by the next run (see Continue). Harnesses that treat queued input as stale after an abort must clear it explicitly.

func (*Agent) AbortSilent

func (a *Agent) AbortSilent()

AbortSilent cancels the current execution without emitting an abort marker. Use for programmatic cancellation (e.g. plan mode transitions) where the cancellation is not a user interruption.

func (*Agent) BaselineContextUsage

func (a *Agent) BaselineContextUsage() *ContextUsage

BaselineContextUsage returns the current runtime baseline occupancy. Unlike ContextUsage, this never reports a transient projected view.

func (*Agent) BuildLLMMessages

func (a *Agent) BuildLLMMessages() ([]Message, error)

BuildLLMMessages constructs the message list with the same system-blocks / converted-history layout the agent loop uses for its primary LLM call.

Loop-scoped concerns are deliberately omitted: per-turn reminders are not appended, and no last-user cache_control marker is added. External callers (e.g., prompt suggestion) use this to share a stable prefix with the main conversation for prompt cache reads without writing new breakpoints of their own — the main loop's marker remains the sole writer.

Malformed tool-call / result transcripts are repaired via RepairMessageSequence.

func (*Agent) BuildLLMTools

func (a *Agent) BuildLLMTools() []ToolSpec

BuildLLMTools returns the ToolSpec list this agent would send to the LLM on its next turn — the exact same conversion buildToolSpecs runs inside the agent loop, including DeferFilter handling.

Side-channel callers (the /btw side-question path, prompt suggestion) need this to keep their request's `tools` field byte-identical to the main agent's last request — Anthropic's prompt cache rejects any prefix difference, and `tools` precedes the system-block cache breakpoint. Without this method, those callers send `tools: nil` and miss the system cache.

func (*Agent) ClearAllQueues

func (a *Agent) ClearAllQueues()

ClearAllQueues removes all queued steering and follow-up messages.

func (*Agent) ClearFollowUpQueue

func (a *Agent) ClearFollowUpQueue()

ClearFollowUpQueue removes all queued follow-up messages.

func (*Agent) ClearSteeringQueue

func (a *Agent) ClearSteeringQueue()

ClearSteeringQueue removes all queued steering messages.

func (*Agent) ContextSnapshot

func (a *Agent) ContextSnapshot() *ContextSnapshot

ContextSnapshot returns the latest context-manager snapshot for observability. Returns nil when no ContextManager is configured or no snapshot is available.

func (*Agent) ContextUsage

func (a *Agent) ContextUsage() *ContextUsage

ContextUsage returns an estimate of the current context window occupancy. Returns nil if contextWindow or contextEstimateFn is not configured.

func (*Agent) Continue

func (a *Agent) Continue(ctx context.Context) error

Continue resumes from the current context without adding new messages. If the last message is from assistant, it dequeues steering/follow-up messages (steering first) and replays them as the new prompt.

Queue retention caveat: messages queued via Steer/FollowUp survive an aborted run — Abort cancels execution but never consumes or clears the queues, so the next Continue (including any automatic resume the harness performs) replays them. If a queued directive must not outlive the run it was meant for, clear it on your abort path via ClearSteeringQueue / ClearFollowUpQueue / ClearAllQueues; agentgo cannot decide this — some harnesses queue droppable steering text, others queue task-completion notifications that must never be lost.

func (*Agent) ExportMessages

func (a *Agent) ExportMessages() []Message

ExportMessages returns only concrete model Messages. Use Messages with an application codec when custom AgentMessage values must survive persistence.

func (*Agent) FollowUp

func (a *Agent) FollowUp(msg AgentMessage)

FollowUp queues a message for the next natural continuation point. An active run consumes follow-ups when it reaches that point; an idle Agent retains them until the harness calls Continue.

func (*Agent) HasFollowUps

func (a *Agent) HasFollowUps() bool

HasFollowUps reports whether follow-up messages are waiting to be consumed.

func (*Agent) HasQueuedMessages

func (a *Agent) HasQueuedMessages() bool

HasQueuedMessages reports whether any steering or follow-up messages are queued.

func (*Agent) HoldRuns

func (a *Agent) HoldRuns() (release func())

HoldRuns stops the world for state surgery: it silently cancels any in-flight run, waits for it to drain, and rejects new run starts (PromptMessages / Continue / InjectContext return ErrRunsHeld) until the returned release is called. Use it to make multi-step mutations — swap the conversation, clear history, retarget stores — atomic with respect to the run lifecycle, including auto-continues a listener may attempt mid-surgery.

release := agent.HoldRuns()
defer release()
// no run is in flight and none can start

Semantics:

  • Counting: concurrent holders each get their own release (idempotent); runs stay rejected until every release has been called. The hold only freezes the run lifecycle — it does NOT mutually exclude two holders' state mutations; serialize holders on the host side.
  • The cancel is silent: HoldRuns itself never requests an abort marker. (A concurrent Abort can still mark the dying run — the hold does not suppress user interruptions.)
  • Queues are untouched: like Abort, a hold never consumes or clears Steer/FollowUp messages — clear them explicitly if they must not outlive the held run (see Continue's queue-retention caveat).
  • WaitForIdle after HoldRuns returns immediately: "no run in flight" is guaranteed, but that does not mean starts are allowed again.
  • MUST NOT be called from an event listener: the drained run's done channel closes only after its listeners return, so a listener waiting on HoldRuns deadlocks itself. (The same restriction applies to Reset.)

func (*Agent) ImportMessages

func (a *Agent) ImportMessages(msgs []Message) error

ImportMessages replaces message history from deserialized Messages.

func (*Agent) Inject

func (a *Agent) Inject(ctx context.Context, msg AgentMessage) (InjectResult, error)

Inject delivers a message as soon as the current agent state allows. An idle resume runs under ctx, so values threaded onto it (cwd override, deadlines) reach the resumed run's tools just as they would on PromptMessages/Continue.

Outcomes:

  • runs held (HoldRuns) → ErrRunsHeld, nothing queued
  • running → steer into current run (ctx unused; the live run keeps its own)
  • idle + assistant tail → enqueue and resume, atomically
  • idle + no assistant tail → enqueue for next run

func (*Agent) Messages

func (a *Agent) Messages() []AgentMessage

Messages returns the current message history.

func (*Agent) Prompt

func (a *Agent) Prompt(ctx context.Context, input string) error

Prompt starts a new conversation turn with the given input. The ctx scopes the run: its deadline, trace values, and cancellation propagate into the loop, and cancelling it aborts the run just like Abort.

func (*Agent) PromptMessages

func (a *Agent) PromptMessages(ctx context.Context, msgs ...AgentMessage) error

PromptMessages starts a new conversation turn with arbitrary AgentMessages. See Prompt for ctx semantics.

func (*Agent) Reset

func (a *Agent) Reset()

Reset clears all state and queues. A run already in flight is silently cancelled, drained, and wiped with the rest of the state; a run start attempted after the drain begins fails with ErrRunsHeld instead of being silently clobbered. Must not be called from an event listener (see HoldRuns).

func (*Agent) SetContextWindow

func (a *Agent) SetContextWindow(n int)

SetContextWindow updates the context window size (in tokens). The new value is also pushed to the ContextManager when it implements ContextWindowSetter, so a model hot-switch needs exactly one call to keep agent-side usage reporting and engine-side compaction thresholds in agreement.

func (*Agent) SetMessageCommitter

func (a *Agent) SetMessageCommitter(fn func(AgentMessage) error)

SetMessageCommitter replaces the durable message callback used by subsequent runs. Returning an error stops the run before the message enters context.

func (*Agent) SetMessages

func (a *Agent) SetMessages(msgs []AgentMessage) error

SetMessages replaces the message history — restore a previous conversation, or clear it with nil. Refused while a run is in flight (ErrAlreadyRunning): the loop's context commit would resurrect the replaced history as silent corruption. Hold the lifecycle first (HoldRuns) when mutating around live runs.

func (*Agent) SetModel

func (a *Agent) SetModel(m ChatModel)

SetModel changes the LLM provider. Takes effect on the next turn.

Purity contract (stable API guarantee, shared by SetThinkingLevel, SetTools, SetSystemPrompt and SetSystemBlocks): a pure field assignment under the agent's internal mutex — no events emitted, no callbacks invoked — so it is safe to call from event listeners and while holding host locks. SetContextWindow is NOT part of this contract: it calls back into the ContextManager outside the lock.

func (*Agent) SetPromptCacheKey

func (a *Agent) SetPromptCacheKey(key string)

SetPromptCacheKey changes the prompt-cache routing identity at runtime. Takes effect on the next turn. Hosts that reuse one Agent across logical conversations (session switch / reset) must update the key so a new conversation doesn't inherit the previous one's cache lineage. See WithPromptCacheKey for semantics.

func (*Agent) SetSystemBlocks

func (a *Agent) SetSystemBlocks(blocks []SystemBlock)

SetSystemBlocks sets a multi-block system prompt with per-block cache control. Takes precedence over SetSystemPrompt. Clears the single-string prompt. Purity contract: see SetModel.

func (*Agent) SetSystemPrompt

func (a *Agent) SetSystemPrompt(s string)

SetSystemPrompt changes the system prompt (single-string mode). Clears any multi-block system prompt set via SetSystemBlocks. Purity contract: see SetModel.

func (*Agent) SetThinkingLevel

func (a *Agent) SetThinkingLevel(level ThinkingLevel)

SetThinkingLevel changes the reasoning depth. Takes effect on the next turn. Purity contract: see SetModel.

func (*Agent) SetTools

func (a *Agent) SetTools(tools ...Tool)

SetTools replaces the tool set. Takes effect on the next turn. Purity contract: see SetModel.

func (*Agent) State

func (a *Agent) State() AgentState

State returns a snapshot of the agent's current state.

func (*Agent) Steer

func (a *Agent) Steer(msg AgentMessage)

Steer queues a steering message to interrupt the agent mid-run. Delivered after the current tool execution; remaining tools are skipped.

func (*Agent) Subscribe

func (a *Agent) Subscribe(fn func(Event)) func()

Subscribe registers a listener for agent events. Returns an unsubscribe function.

Dispatch contract (stable API guarantee): listeners are invoked synchronously, in registration order, on the agent's event-consumption goroutine. A listener registered before another always observes each event first — ordering-sensitive consumers (e.g. a budget sentinel that must veto before a dispatcher reacts) may rely on this. The flip side: a slow listener delays event delivery to all later listeners and backpressures the loop's event channel; offload heavy work to your own goroutine.

Lifecycle contract (stable API guarantee):

  • When EventAgentEnd listeners run, isRunning is already false — a listener may start the next run (Continue / InjectContext) directly.
  • The run's done channel — what WaitForIdle and HoldRuns wait on — closes only after all listeners for the final event have returned.
  • Therefore a listener must never call HoldRuns or Reset, and must never block on a lock that may be held by a goroutine waiting on WaitForIdle/HoldRuns: both are self-deadlocks.

func (*Agent) TotalUsage

func (a *Agent) TotalUsage() Usage

TotalUsage returns the cumulative token usage across all turns.

func (*Agent) WaitForIdle

func (a *Agent) WaitForIdle()

WaitForIdle blocks until the agent finishes the current run.

type AgentContext

type AgentContext struct {
	SystemPrompt string        // single-string system prompt (legacy)
	SystemBlocks []SystemBlock // multi-block system prompt with cache control (takes precedence)
	Messages     []AgentMessage
	Tools        []Tool
}

AgentContext holds the immutable context for a single agent loop invocation.

type AgentMessage

type AgentMessage interface {
	GetRole() Role
	GetTimestamp() time.Time
	Raw() AgentMessage
	Priority() int
	Compact(expect float64) (next AgentMessage, actual float64)
	TextContent() string
	ThinkingContent() string
	HasToolCalls() bool
	ToMessage() (message Message, include bool)
}

AgentMessage is the application-layer message abstraction used by the agent loop, context management, events, and persistence. Message implements this interface; applications can add richer message types without lowering them to the model protocol until ToMessage is called.

Raw returns the original domain message regardless of the current compacted representation. Compact asks the message for the highest-fidelity representation that fits expect, where 1 is the raw size and 0 means the smallest representation the domain can provide. actual is the size ratio the returned value reached, always relative to Raw. Compact must be monotonic and must not mutate the receiver.

Priority is application information importance: higher values are preserved longer by context policies and are not part of the model-level Message.

func Collect

func Collect(events <-chan Event) ([]AgentMessage, error)

Collect consumes all events from the channel and returns the final messages. Blocks until the channel is closed. Returns any error from EventError events.

func ToAgentMessages

func ToAgentMessages(msgs []Message) []AgentMessage

ToAgentMessages converts a Message slice to AgentMessage slice. Use this to restore conversation history from deserialized Messages.

type AgentOption

type AgentOption func(*Agent)

AgentOption configures an Agent.

func WithAbortMarkerText

func WithAbortMarkerText(inference, toolUse string) AgentOption

WithAbortMarkerText overrides the marker messages recorded when a run is cancelled: inference is used mid-inference, toolUse during tool execution. Either empty keeps that built-in default. Lets non-English harnesses localize the markers that get written into conversation history.

func WithAfterTurn

func WithAfterTurn(hook AfterTurnHook) AgentOption

WithAfterTurn installs a hook that runs after every normally completed assistant/tool turn and before the loop continues or stops.

func WithBeforeTurn

func WithBeforeTurn(hook BeforeTurnHook) AgentOption

WithBeforeTurn installs a hook that runs before every model call. Messages returned by the hook are committed before that request.

func WithCacheLastMessage

func WithCacheLastMessage(cacheControl string) AgentOption

WithCacheLastMessage tags the last non-system message with cache_control before every LLM call. Providers that support prompt caching (Anthropic, Bedrock) place a write breakpoint at that position, covering the entire preceding prefix (system blocks + conversation history + tools).

The marker lands on whichever turn is freshest — user input, tool_result, or assistant — and skips trailing per-turn system reminders. Inside a tool loop this means each LLM call writes a cache entry covering the latest tool_use+tool_result, so the next call reads them from cache instead of re-uploading.

When the agent uses a plain SystemPrompt (not SystemBlocks), the loop also pins the system message with the same cache_control as a stable floor, so fresh sessions sharing the prompt reuse the system+tools prefix. SystemBlocks users keep explicit control via SystemBlock.CacheControl.

Pass "" (default) to leave messages untouched. Pass "ephemeral" for the standard 5-minute TTL, or "ephemeral:1h" for the extended TTL where the provider supports it (use for conversations whose turn gaps regularly exceed 5 minutes). Use this when the application — not the LLM library — owns cache placement.

func WithContextManager

func WithContextManager(mgr ContextManager) AgentOption

WithContextManager sets the context lifecycle manager. When configured, it drives prompt projection, overflow recovery, and usage reporting. The agent auto-wires context-token estimation and the context window from the manager when it implements the optional ContextEstimator / ContextWindowProvider interfaces.

func WithLengthRecoveryPrompt

func WithLengthRecoveryPrompt(prompt string) AgentOption

WithLengthRecoveryPrompt overrides the user message injected when output is truncated (max_tokens) with no completed tool calls. Empty keeps the built-in default prompt.

func WithMaxRetries

func WithMaxRetries(n int) AgentOption

WithMaxRetries sets the LLM call retry limit for retryable errors.

func WithMaxToolConcurrency

func WithMaxToolConcurrency(n int) AgentOption

WithMaxToolConcurrency sets the maximum number of tools executed in parallel. 0 or 1 = sequential (default). >1 enables concurrent tool execution.

func WithMaxToolErrors

func WithMaxToolErrors(n int) AgentOption

WithMaxToolErrors sets the consecutive failure threshold per tool. After reaching this limit, the tool is disabled for the rest of the loop. 0 means unlimited (no circuit breaker).

func WithMaxTurns

func WithMaxTurns(n int) AgentOption

WithMaxTurns sets the max turns safety limit. n <= 0 falls back to the built-in default (100).

Accounting note: a "turn" is one LLM call. Length-recovery (automatic resume after a max_tokens truncation) injects extra calls that count toward this limit, so budget recoveries into tight limits.

func WithMessageCommitter

func WithMessageCommitter(fn func(AgentMessage) error) AgentOption

WithMessageCommitter installs a synchronous durable-message callback. Returning an error stops the run before the message enters context or starts requested tools.

func WithMiddlewares

func WithMiddlewares(mw ...ToolMiddleware) AgentOption

WithMiddlewares sets tool execution middlewares. Each middleware wraps the tool.Execute call. First middleware is outermost.

func WithModel

func WithModel(model ChatModel) AgentOption

WithModel sets the LLM model.

func WithOnMessage

func WithOnMessage(fn func(AgentMessage)) AgentOption

WithOnMessage registers a callback invoked after each message is appended to the agent's context. Use it for observation; durable persistence should use WithMessageCommitter so write errors can stop execution.

func WithPromptCacheKey

func WithPromptCacheKey(key string) AgentOption

WithPromptCacheKey sets the prompt-cache routing identity attached to every LLM request of this agent (e.g. OpenAI prompt_cache_key). Keep one key per long-lived conversation: requests sharing a key are routed to the same provider cache shard, so each turn can read the previous turn's prefix from cache. The adapter drops the hint for providers without key-routed caching. Empty (default) sends no hint.

func WithStopGuard

func WithStopGuard(guard StopGuard) AgentOption

WithStopGuard installs a guard that decides whether the agent may stop when the LLM emits end_turn without tool calls. Nil guard (default) means every stop is allowed — legacy behavior.

func WithSystemBlocks

func WithSystemBlocks(blocks []SystemBlock) AgentOption

WithSystemBlocks sets a multi-block system prompt with per-block cache control. Takes precedence over WithSystemPrompt.

func WithSystemPrompt

func WithSystemPrompt(prompt string) AgentOption

WithSystemPrompt sets the system prompt (single-string mode).

func WithThinkingLevel

func WithThinkingLevel(level ThinkingLevel) AgentOption

WithThinkingLevel sets the reasoning depth for models that support it.

func WithToolGate

func WithToolGate(gate ToolGate) AgentOption

WithToolGate installs a hook called once per tool call after argument validation and the optional Previewer pass. Returning Allowed=false rejects the call (Reason becomes the tool result error). The kernel does not implement permission reasoning of its own — gates are user-supplied.

func WithToolResultMessageFactory

func WithToolResultMessageFactory(factory func(ToolCall, ToolResult) AgentMessage) AgentOption

WithToolResultMessageFactory converts raw tool results into application AgentMessage values before they enter the transcript. The default keeps the built-in model Message representation. The returned message must project to a tool Message with the matching tool_call_id.

func WithTools

func WithTools(tools ...Tool) AgentOption

WithTools sets the tool list.

type AgentState

type AgentState struct {
	SystemPrompt     string
	Messages         []AgentMessage
	Tools            []Tool
	IsRunning        bool
	StreamMessage    AgentMessage        // partial message being streamed, nil when idle
	PendingToolCalls map[string]struct{} // tool call IDs currently executing
	TotalUsage       Usage               // cumulative token usage across all turns
	Error            string
}

AgentState is a snapshot of the agent's current state.

type BeforeTurnContext

type BeforeTurnContext struct {
	TurnIndex int
	Context   AgentContext
}

BeforeTurnContext describes the runtime immediately before one model call. TurnIndex is one-based. Context is a snapshot; mutating it does not change the running loop.

type BeforeTurnHook

type BeforeTurnHook func(context.Context, BeforeTurnContext) ([]AgentMessage, error)

BeforeTurnHook runs before each model call. Returned messages are committed to the transcript before the request, allowing applications to prepare or steer the next turn without teaching the loop about business phases.

type CallConfig

type CallConfig struct {
	ThinkingLevel  ThinkingLevel
	ThinkingBudget int    // max thinking tokens, 0 = use provider default
	APIKey         string // per-call API key override, empty = use model default
	SessionID      string // provider session caching identifier
	PromptCacheKey string // prompt-cache routing identity, empty = no hint
	MaxTokens      int    // per-call max tokens override, 0 = use model default
	ToolChoice     any    // "auto" / "required" / "none" / {"type":"tool","name":"xxx"}, nil = provider default
	ResponseFormat *ResponseFormat
}

CallConfig holds per-call configuration resolved from CallOptions.

func ResolveCallConfig

func ResolveCallConfig(opts []CallOption) CallConfig

ResolveCallConfig applies options and returns the resolved config.

type CallOption

type CallOption func(*CallConfig)

CallOption configures per-call LLM parameters.

func WithAPIKey

func WithAPIKey(key string) CallOption

WithAPIKey overrides the API key for a single LLM call. Enables key rotation, OAuth short-lived tokens, and multi-tenant scenarios.

func WithCallPromptCacheKey

func WithCallPromptCacheKey(key string) CallOption

WithCallPromptCacheKey sets the prompt-cache routing identity for a single LLM call. Providers with key-routed prefix caching (OpenAI prompt_cache_key) route requests sharing a key to the same cache shard; the adapter drops the hint for providers without support.

func WithCallSessionID

func WithCallSessionID(id string) CallOption

WithCallSessionID sets a session identifier for a single LLM call.

func WithJSONMode

func WithJSONMode() CallOption

WithJSONMode requests valid JSON output without enforcing a specific schema.

func WithJSONSchema

func WithJSONSchema(name, description string, schema any, strict bool) CallOption

WithJSONSchema requests structured JSON output constrained by a JSON Schema.

func WithMaxTokens

func WithMaxTokens(tokens int) CallOption

WithMaxTokens overrides the max output tokens for a single LLM call.

func WithResponseFormat

func WithResponseFormat(format *ResponseFormat) CallOption

WithResponseFormat sets a provider-native response format explicitly.

func WithThinking

func WithThinking(level ThinkingLevel) CallOption

WithThinking sets the thinking level for a single LLM call.

func WithThinkingBudget

func WithThinkingBudget(tokens int) CallOption

WithThinkingBudget sets the max thinking tokens for a single LLM call.

func WithToolChoice

func WithToolChoice(choice any) CallOption

WithToolChoice controls whether the model must call a tool. Accepted values: "auto" (default), "required" (must call a tool), "none" (no tools).

type ChatModel

type ChatModel interface {
	Generate(ctx context.Context, messages []Message, tools []ToolSpec, opts ...CallOption) (*LLMResponse, error)
	GenerateStream(ctx context.Context, messages []Message, tools []ToolSpec, opts ...CallOption) (<-chan StreamEvent, error)
	SupportsTools() bool
}

ChatModel is the LLM provider interface.

type CompactReason

type CompactReason string

CompactReason identifies why context compaction was requested.

const (
	CompactReasonManual    CompactReason = "manual"
	CompactReasonOverflow  CompactReason = "overflow"
	CompactReasonThreshold CompactReason = "threshold"
)

type CompactionInfo

type CompactionInfo struct {
	Reason         CompactReason
	Committed      bool
	TokensBefore   int
	TokensAfter    int
	MessagesBefore int
	MessagesAfter  int
	Summarized     bool
}

CompactionInfo describes one completed context compaction transaction. It reports the aggregate effect of the configured compactor; individual strategies remain an implementation detail. A nil Compaction means no compaction changed the context.

type ConcurrencySafeTool

type ConcurrencySafeTool interface {
	ConcurrencySafe(args json.RawMessage) bool
}

ConcurrencySafeTool is an optional interface for tools that declare whether they can safely execute concurrently with other tools. Takes precedence over ReadOnlyTool for concurrency scheduling.

type ContentBlock

type ContentBlock struct {
	Type     ContentType `json:"type"`
	Text     string      `json:"text,omitempty"`
	Thinking string      `json:"thinking,omitempty"`
	ToolCall *ToolCall   `json:"tool_call,omitempty"`
	Image    *ImageData  `json:"image,omitempty"`
	ToolName string      `json:"tool_name,omitempty"` // tool_reference: referenced tool name
}

ContentBlock is a tagged union for message content. Exactly one payload field is populated, matching the Type value.

func ImageBlock

func ImageBlock(data, mimeType string) ContentBlock

func ImageURLBlock

func ImageURLBlock(url string) ContentBlock

func TextBlock

func TextBlock(text string) ContentBlock

func ThinkingBlock

func ThinkingBlock(thinking string) ContentBlock

func ToolCallBlock

func ToolCallBlock(tc ToolCall) ContentBlock

func ToolRefBlock

func ToolRefBlock(toolName string) ContentBlock

type ContentTool

type ContentTool interface {
	ExecuteContent(ctx context.Context, args json.RawMessage) ([]ContentBlock, error)
}

ContentTool is an optional interface for tools that return rich content (e.g., images). When a tool implements ContentTool, the agent loop calls ExecuteContent instead of Execute, enabling multi-block responses with text + image content blocks.

type ContentType

type ContentType string

ContentType identifies the kind of content in a ContentBlock.

const (
	ContentText     ContentType = "text"
	ContentThinking ContentType = "thinking"
	ContentToolCall ContentType = "toolCall"
	ContentImage    ContentType = "image"
	ContentToolRef  ContentType = "tool_reference"
)

type ContextCommitResult

type ContextCommitResult struct {
	Messages       []AgentMessage
	Usage          *ContextUsage
	Changed        bool
	Compaction     *CompactionInfo
	CompactedCount int
	KeptCount      int
	SplitTurn      bool
}

ContextCommitResult is the result of an explicit committed rewrite. The returned Messages should replace the runtime baseline when Changed is true, for example after a manual /compact command.

type ContextDemand

type ContextDemand struct {
	ContextKey
	Signal string `json:"signal"`
}

ContextDemand is the protocol-level counterpart of ContextItem. It records that a trajectory exposed demand for the same ContextKey through an application-defined Signal. AgentGo defines the shape only; applications and evaluators decide how demands are derived and what they mean.

func CollectContextDemands

func CollectContextDemands(messages []AgentMessage) []ContextDemand

CollectContextDemands returns explicit demands exposed by messages in message order. Evaluators may combine these with demands inferred from the raw event trajectory.

type ContextDemandProvider

type ContextDemandProvider interface {
	ContextDemands() []ContextDemand
}

ContextDemandProvider is an optional capability for messages or trajectory artifacts that can state demands explicitly. Demand inferred from raw tool or model events remains the evaluator's responsibility.

type ContextEstimateFn

type ContextEstimateFn func(msgs []AgentMessage) (tokens, usageTokens, trailingTokens int)

ContextEstimateFn estimates the current context token consumption from messages. Returns total tokens, tokens from LLM Usage, and estimated trailing tokens.

type ContextEstimator

type ContextEstimator interface {
	EstimateContext([]AgentMessage) (tokens, usageTokens, trailingTokens int)
}

ContextEstimator is an optional interface a ContextManager can implement to provide token estimation. When implemented, NewAgent auto-wires it.

type ContextItem

type ContextItem struct {
	ContextKey
	Representation string `json:"representation"`
	Reason         string `json:"reason,omitempty"`
	Ref            string `json:"ref,omitempty"`
}

ContextItem describes one identifiable piece of information present in an AgentMessage. Representation, Reason, and Ref are open application labels; AgentGo records them but does not rank or interpret them.

func CollectContextItems

func CollectContextItems(messages []AgentMessage) []ContextItem

CollectContextItems returns the item inventory exposed by messages in message order. It deliberately does not validate, deduplicate, rank, or otherwise interpret application identities.

type ContextItemProvider

type ContextItemProvider interface {
	AgentMessage
	ContextItems() []ContextItem
}

ContextItemProvider is an optional AgentMessage capability. A message may expose zero, one, or many identifiable context items without changing its model projection.

type ContextKey

type ContextKey struct {
	Kind     string `json:"kind"`
	Identity string `json:"identity"`
}

ContextKey is the shared identity used to correlate information present in an agent context with information later demanded by the trajectory. Kind and Identity are application-defined; AgentGo assigns them no semantics.

type ContextManager

type ContextManager interface {
	// Project builds the prompt view for a single model call without mutating
	// the caller's runtime baseline.
	Project(ctx context.Context, msgs []AgentMessage) (ContextProjection, error)

	// Compact performs an explicit committed rewrite of msgs. The caller is
	// responsible for replacing its runtime baseline with the returned Messages
	// when Changed is true.
	Compact(ctx context.Context, msgs []AgentMessage, reason CompactReason) (ContextCommitResult, error)

	// RecoverOverflow produces a retryable view after a provider reports
	// context overflow. When ShouldCommit is true, CommitMessages should replace
	// the runtime baseline before continuing.
	RecoverOverflow(ctx context.Context, msgs []AgentMessage, cause error) (ContextRecoveryResult, error)

	// Sync tells the manager what the current runtime baseline is after restore,
	// clear, import, or any other external replacement of messages.
	Sync(msgs []AgentMessage)

	// Usage returns the latest effective context usage remembered by the
	// manager. It may reflect a projected or recovered view rather than the raw
	// runtime baseline.
	Usage() *ContextUsage

	// Snapshot returns the latest active view snapshot remembered by the
	// manager. It is intended for observability and may be nil before the
	// manager has seen any messages.
	Snapshot() *ContextSnapshot
}

ContextManager owns prompt projection, committed rewrites, overflow recovery, and usage reporting for long-running agent sessions.

The manager deliberately distinguishes between transient prompt projection and explicit baseline rewrites:

  • Project builds a prompt view for one LLM call without committing it.
  • Compact performs an explicit committed rewrite such as /compact.
  • RecoverOverflow produces a retryable prompt view after context overflow and may optionally return a new committed baseline.
  • Sync updates the manager with the current runtime baseline after external message replacement, session restore, or clear.
  • Usage reports the latest effective usage remembered by the manager.
  • Snapshot reports the current active view and recent rewrite details for debugging and UI surfaces.

type ContextOverflowError

type ContextOverflowError struct {
	Cause error
}

ContextOverflowError wraps an underlying context-overflow cause (typically a provider error). errors.Is matches ErrContextOverflow; Unwrap reaches the raw cause so callers can extract provider-specific details if needed.

func (*ContextOverflowError) Error

func (e *ContextOverflowError) Error() string

func (*ContextOverflowError) Is

func (e *ContextOverflowError) Is(target error) bool

func (*ContextOverflowError) Unwrap

func (e *ContextOverflowError) Unwrap() error

type ContextProjection

type ContextProjection struct {
	Messages       []AgentMessage
	Usage          *ContextUsage
	CommitMessages []AgentMessage
	ShouldCommit   bool
	Compaction     *CompactionInfo
}

ContextProjection is the prompt view projected for a single LLM call. By default the projection does not modify the runtime message baseline. When ShouldCommit is true, CommitMessages should replace the runtime baseline before continuing the current call.

type ContextRecoveryResult

type ContextRecoveryResult struct {
	View           []AgentMessage
	CommitMessages []AgentMessage
	Usage          *ContextUsage
	Changed        bool
	ShouldCommit   bool
	Compaction     *CompactionInfo
	CompactedCount int
	KeptCount      int
	SplitTurn      bool
}

ContextRecoveryResult is the result of overflow recovery.

View is always the retryable prompt view. CommitMessages is optional and, when ShouldCommit is true, should replace the runtime message baseline so future usage reporting and turns start from the recovered state.

type ContextSnapshot

type ContextSnapshot struct {
	Items              []ContextItem
	BaselineUsage      *ContextUsage
	Usage              *ContextUsage
	Scope              string
	TranscriptMessages int
	ActiveMessages     int
	SummaryMessages    int
	ToolMessages       int
	ClearedToolResults int
	TrimmedTextBlocks  int
	LastChanged        bool
	LastCompactedCount int
	LastKeptCount      int
	LastSplitTurn      bool
}

ContextSnapshot describes both the runtime baseline and the current active context view, including its identifiable Items, plus the most recent rewrite details remembered by the manager.

Snapshot is meant for debugging, observability, and UI surfaces such as /context. BaselineUsage always reflects the caller's current runtime message baseline. Usage reports the active view currently remembered by the manager, which may be the baseline runtime messages, a projected prompt view, or a recovered/committed view depending on the most recent operation.

type ContextUsage

type ContextUsage struct {
	Tokens         int     `json:"tokens"`          // estimated total tokens in context
	ContextWindow  int     `json:"context_window"`  // model's context window size
	Percent        float64 `json:"percent"`         // tokens / contextWindow * 100
	UsageTokens    int     `json:"usage_tokens"`    // from last LLM-reported Usage
	TrailingTokens int     `json:"trailing_tokens"` // chars/4 estimate for trailing messages
}

ContextUsage represents the current context window occupancy estimate.

type ContextWindowProvider

type ContextWindowProvider interface {
	ContextWindow() int
}

ContextWindowProvider is an optional interface a ContextManager can implement to report its configured context window size.

type ContextWindowSetter

type ContextWindowSetter interface {
	SetContextWindow(n int)
}

ContextWindowSetter is an optional interface a ContextManager can implement to receive window updates pushed through Agent.SetContextWindow. When implemented, the agent and its context manager can never disagree on the window size after a model hot-switch — without it, callers had to update both sides manually and a missed call silently skewed compaction thresholds.

type Cost

type Cost struct {
	Input      float64 `json:"input"`
	Output     float64 `json:"output"`
	CacheRead  float64 `json:"cache_read"`
	CacheWrite float64 `json:"cache_write"`
	Total      float64 `json:"total"`
}

Cost tracks monetary cost for a single LLM call in USD.

func (*Cost) Add

func (c *Cost) Add(other *Cost)

Add accumulates another Cost into this one (nil-safe).

type DeferActivator

type DeferActivator interface {
	DeferFilter
	Activate(names ...string)
}

DeferActivator is an optional extension of DeferFilter that supports pre-activating deferred tools (e.g. when restoring a session whose history contains tool_reference blocks for previously activated tools).

type DeferFilter

type DeferFilter interface {
	// IsDeferred reports whether the tool is deferred and not yet activated.
	// Unactivated deferred tools are excluded from the API request entirely.
	IsDeferred(toolName string) bool
	// WasDeferred reports whether the tool was originally in the deferred set
	// (regardless of activation). Activated deferred tools are sent with
	// defer_loading: true.
	WasDeferred(toolName string) bool
}

DeferFilter controls deferred tool loading for the LLM. When a tool in the agent's tool list implements DeferFilter:

  • IsDeferred returns true → tool schema is excluded from the API request
  • WasDeferred returns true → tool schema is sent with defer_loading: true

Unactivated deferred tools are excluded entirely. Once activated via tool_reference, they are sent with defer_loading: true so the API server manages their context loading. Tools remain registered for execution regardless — only their API visibility changes.

IsDeferred is also used by the system prompt builder to exclude unactivated tools from the tool description section (they appear in <available-deferred-tools> by name only).

type DeltaKind

type DeltaKind string

DeltaKind identifies what kind of content a message_update delta carries.

const (
	DeltaText     DeltaKind = ""         // default: regular text
	DeltaThinking DeltaKind = "thinking" // model reasoning/thinking
	DeltaToolCall DeltaKind = "toolcall" // tool call argument JSON
)

type EndReason

type EndReason string

EndReason describes why a single agent run stopped.

const (
	EndReasonStop     EndReason = "stop"
	EndReasonMaxTurns EndReason = "max_turns"
	EndReasonAborted  EndReason = "aborted"
	EndReasonError    EndReason = "error"
)

type Event

type Event struct {
	Type         EventType
	Message      AgentMessage    // for message_start/update/end, turn_end
	Delta        string          // text delta for message_update
	DeltaKind    DeltaKind       // for message_update: what kind of delta
	ToolID       string          // for tool_exec_*
	Tool         string          // tool name for tool_exec_*
	ToolLabel    string          // human-readable tool label (from ToolLabeler)
	Args         json.RawMessage // tool args for tool_exec_start/tool_exec_update
	Result       json.RawMessage // tool result for tool_exec_end and preview updates
	Progress     *ProgressPayload
	UpdateKind   ToolExecUpdateKind
	IsError      bool // tool error flag for tool_exec_end
	Preview      json.RawMessage
	ToolResults  []ToolResult    // for turn_end: all tool results from this turn
	Err          error           // for error events
	NewMessages  []AgentMessage  // for agent_end: messages added during this loop
	RetryInfo    *RetryInfo      // for retry events
	ContextItems []ContextItem   // for context_projected
	Compaction   *CompactionInfo // for context_compacted
	Summary      *RunSummary     // for agent_end: factual run summary
}

Event is a lifecycle event emitted by the agent loop. This is the single output channel for all lifecycle information.

type EventStream

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

EventStream wraps an event channel to provide both real-time iteration and deferred result collection.

Usage:

stream := agentgo.NewEventStream(AgentLoop(...))
for ev := range stream.Events() {
    // handle real-time events
}
msgs, err := stream.Result()

func NewEventStream

func NewEventStream(source <-chan Event) *EventStream

NewEventStream creates an EventStream that reads from the source channel. Events are forwarded to an internal channel for iteration. The final result is captured from EventAgentEnd.

Events() carries the same consumption contract as the source: iterate until it closes. Abandoning it mid-stream leaks the forwarding goroutine — the wrapper has no context to detect an absent reader.

func (*EventStream) Done

func (s *EventStream) Done() <-chan struct{}

Done returns a channel that is closed when the stream finishes.

func (*EventStream) Events

func (s *EventStream) Events() <-chan Event

Events returns the event channel for real-time iteration. The channel is closed when the source is exhausted.

func (*EventStream) Result

func (s *EventStream) Result() ([]AgentMessage, error)

Result blocks until the stream is done and returns the final messages. Returns the error from the last EventError, if any.

type EventType

type EventType string

EventType identifies agent lifecycle event types.

const (
	EventAgentStart EventType = "agent_start"
	EventAgentEnd   EventType = "agent_end"
	EventTurnStart  EventType = "turn_start"
	EventTurnEnd    EventType = "turn_end"
	// EventModelResponse fires after every model call completes, including any
	// tool executions it triggered. One model invocation produces one event —
	// not one logical user exchange — so steering injections and length
	// recoveries each produce additional ones within the same run.
	EventModelResponse  EventType = "model_response"
	EventMessageStart   EventType = "message_start"
	EventMessageUpdate  EventType = "message_update"
	EventMessageEnd     EventType = "message_end"
	EventToolExecStart  EventType = "tool_exec_start"
	EventToolExecUpdate EventType = "tool_exec_update"
	EventToolExecEnd    EventType = "tool_exec_end"
	// EventContextProjected fires before each model call with the identifiable
	// items exposed by the actual projected AgentMessage context. It is a trace
	// fact, not a judgment that any item was useful or sufficient.
	EventContextProjected EventType = "context_projected"
	// EventContextCompacted fires once after a ContextManager completes a
	// context compaction transaction and before the compacted view is sent to
	// the model. Internal compactor stages do not emit separate events.
	EventContextCompacted EventType = "context_compacted"
	EventRetry            EventType = "retry"
	EventError            EventType = "error"
)

type FuncTool

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

FuncTool wraps a function as a Tool (convenience helper).

func NewFuncTool

func NewFuncTool(name, description string, schema map[string]any, fn func(ctx context.Context, args json.RawMessage) (json.RawMessage, error)) *FuncTool

func (*FuncTool) Description

func (t *FuncTool) Description() string

func (*FuncTool) Execute

func (t *FuncTool) Execute(ctx context.Context, args json.RawMessage) (json.RawMessage, error)

func (*FuncTool) Name

func (t *FuncTool) Name() string

func (*FuncTool) Schema

func (t *FuncTool) Schema() map[string]any

type GateDecision

type GateDecision struct {
	Allowed     bool
	Reason      string
	UpdatedArgs json.RawMessage
}

GateDecision is the gate's verdict for one tool call.

Allowed=true => execute the tool with Call.Args, or with UpdatedArgs when set — the gate's way to return a policy-side rewrite (hook updated_input, interactive data backfill) so the tool executes exactly what was approved. Allowed=false => return Reason as the tool result error; do not execute. UpdatedArgs is ignored on a denial.

A nil decision is treated as Allowed=true (the gate has no opinion).

type GateRequest

type GateRequest struct {
	Tool      Tool
	Call      ToolCall
	ToolLabel string          // resolved via ToolLabeler when available
	Preview   json.RawMessage // resolved via Previewer when available; may be nil
}

GateRequest carries the inputs that a ToolGate sees for one tool call. Tool exposes the underlying tool instance so gates can typeswitch against any tool-specific marker interfaces they care about (e.g. capability hints) without the kernel needing to know those interfaces.

type ImageData

type ImageData struct {
	Data     string `json:"data,omitempty"`
	URL      string `json:"url,omitempty"`
	MimeType string `json:"mime_type,omitempty"`
}

ImageData holds image content as base64 data or a URL. When URL is set, providers pass it directly (no download/encoding needed). When Data is set, it is sent as a base64 data URL with MimeType. MimeType is required for base64 mode, optional for URL mode (provider infers it).

type InjectDisposition

type InjectDisposition string

InjectDisposition describes how an injected message was delivered.

const (
	InjectSteeredCurrentRun InjectDisposition = "steered_current_run"
	InjectResumedIdleRun    InjectDisposition = "resumed_idle_run"
	InjectQueued            InjectDisposition = "queued"
)

type InjectResult

type InjectResult struct {
	Disposition InjectDisposition
}

InjectResult reports the delivery outcome of Agent.Inject.

type InterruptBehavior

type InterruptBehavior string

InterruptBehavior controls what happens when a queued user message arrives while a tool is still running.

const (
	InterruptBehaviorBlock  InterruptBehavior = "block"
	InterruptBehaviorCancel InterruptBehavior = "cancel"
)

type InterruptBehaviorTool

type InterruptBehaviorTool interface {
	InterruptBehavior(args json.RawMessage) InterruptBehavior
}

InterruptBehaviorTool is an optional interface for tools that declare whether they should be cancelled or allowed to finish when a steering message arrives. Defaults to InterruptBehaviorBlock when not implemented.

type JSONSchema

type JSONSchema struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
	Schema      any    `json:"schema"`
	Strict      *bool  `json:"strict,omitempty"`
}

JSONSchema describes the provider-native JSON schema response format.

type LLMRequest

type LLMRequest struct {
	Messages []Message
	Tools    []ToolSpec
}

LLMRequest carries the inputs to a single LLM call.

type LLMResponse

type LLMResponse struct {
	Message Message
}

LLMResponse carries the result of a single LLM call.

type LoopConfig

type LoopConfig struct {
	Model         ChatModel
	MaxTurns      int           // safety limit, default 100
	MaxRetries    int           // LLM call retry limit for retryable errors, default 3
	MaxToolErrors int           // consecutive tool failure threshold per tool, 0 = unlimited
	ThinkingLevel ThinkingLevel // reasoning depth

	// BeforeTurn runs before each model call. Messages it returns are committed
	// before the request. AfterTurn runs after a normal assistant/tool turn has
	// been committed and before the loop decides whether to continue or stop.
	BeforeTurn BeforeTurnHook
	AfterTurn  AfterTurnHook

	// Context lifecycle. ContextManager drives prompt projection, overflow
	// recovery, and usage reporting. AgentMessage values lower themselves to
	// Message only at the model-call boundary.
	ContextManager ContextManager

	// ToolResultMessageFactory optionally lifts a raw tool result into an
	// application-specific message before it enters the transcript. Nil keeps
	// the built-in model Message representation. The returned message's model
	// projection must preserve the tool role and tool_call_id pairing.
	ToolResultMessageFactory func(ToolCall, ToolResult) AgentMessage

	// CommitContext replaces the runtime message baseline after an explicit
	// committed compaction, a committed projection rewrite, or committed
	// overflow recovery.
	CommitContext func(msgs []AgentMessage, usage *ContextUsage) error

	// CommitMessage durably records a message before it enters runtime context.
	// Returning an error stops the run; tools requested by that message are not
	// executed. OnMessage remains the post-commit observer hook.
	CommitMessage func(msg AgentMessage) error

	// ToolGate, when non-nil, is called once per tool call after argument
	// validation and the optional Previewer pass. Allowed=false rejects the
	// call (Reason becomes the tool result). The kernel does no permission
	// reasoning of its own.
	ToolGate ToolGate

	// Steering: called after each tool execution to check for user interruptions.
	GetSteeringMessages func() []AgentMessage

	// FollowUp: called when the agent would otherwise stop.
	GetFollowUpMessages func() []AgentMessage

	// Middlewares are applied around each tool execution (outermost first).
	// Use for logging, timing, argument/result modification, etc.
	Middlewares []ToolMiddleware

	// MaxToolConcurrency limits parallel tool execution.
	// 0 or 1 = sequential (default, backward compatible).
	// >1 = up to N tools execute concurrently within a single turn.
	MaxToolConcurrency int

	// ShouldEmitAbortMarker reports whether an abort marker message should be
	// emitted when the context is cancelled. When nil or returns false, the
	// cancellation is silent (legacy behavior). Set by Agent.Abort().
	ShouldEmitAbortMarker func() bool

	// StopAfterTool, if non-nil, is called after each successful (non-error)
	// tool execution. If it returns true, the loop exits with EndReasonStop.
	// Use this to let a terminal tool (e.g. commit_chapter) end the loop
	// without wasting turns. The exit passes through StopGuard (with
	// Trigger=StopTriggerAfterTool) like any other normal stop, so a guard
	// can veto a premature terminal-tool exit.
	StopAfterTool func(toolName string) bool

	// StopAfterToolResult is the result-aware variant of StopAfterTool. It is
	// useful when the same tool can be an intermediate step or a terminal step
	// depending on its structured result.
	StopAfterToolResult func(toolName string, result json.RawMessage) bool

	// OnMessage, if non-nil, is called after each committed message is appended
	// to context (assistant, tool result, steering). It is observational; use
	// CommitMessage when persistence failure must stop execution.
	OnMessage func(msg AgentMessage)

	// StopGuard is consulted on every normal stop (end_turn and
	// StopAfterTool exits). Nil (default) means every stop is allowed.
	StopGuard StopGuard

	// LengthRecoveryPrompt overrides the user message injected when the
	// model's output is truncated (max_tokens) with no completed tool calls.
	// Empty uses a built-in default. Each recovery is an extra LLM call on
	// top of normal turn accounting, bounded by an internal cap.
	LengthRecoveryPrompt string

	// AbortMarkerText overrides the marker message recorded when a run is
	// cancelled mid-inference (only when ShouldEmitAbortMarker returns true).
	// Empty uses a built-in default. Lets non-English harnesses localize it.
	AbortMarkerText string
	// AbortMarkerToolText overrides the marker message recorded when
	// cancellation lands during tool execution. Empty uses a built-in default.
	AbortMarkerToolText string

	// CacheLastMessage, when non-empty, instructs the loop to tag the last
	// non-system message in every LLM request with this cache_control value
	// (e.g. "ephemeral"). Providers that support prompt caching place a write
	// breakpoint at that position covering the entire preceding prefix. Empty
	// string (default) leaves messages untouched — keep cache placement under
	// application control.
	//
	// The breakpoint follows the freshest turn (user input, tool_result, or
	// assistant) and skips trailing per-turn system reminders.
	CacheLastMessage string

	// PromptCacheKey, when non-empty, is attached to every LLM request as the
	// provider's prompt-cache routing identity (e.g. OpenAI prompt_cache_key).
	// Use one key per conversation so all requests of a session land on the
	// same cache shard. The adapter drops the hint for providers without
	// key-routed caching. Empty (default) sends nothing.
	PromptCacheKey string
}

LoopConfig configures the agent loop.

type MaxTurnsError

type MaxTurnsError struct {
	Limit int
}

MaxTurnsError carries the configured turn limit. errors.Is matches ErrMaxTurns.

func (*MaxTurnsError) Error

func (e *MaxTurnsError) Error() string

func (*MaxTurnsError) Is

func (e *MaxTurnsError) Is(target error) bool

type Message

type Message struct {
	Role       Role           `json:"role"`
	Content    []ContentBlock `json:"content"`
	StopReason StopReason     `json:"stop_reason,omitempty"`
	Usage      *Usage         `json:"usage,omitempty"`
	Metadata   map[string]any `json:"metadata,omitempty"`
	Timestamp  time.Time      `json:"timestamp"`
}

Message is an LLM-level message with structured content blocks.

func AbortMsg

func AbortMsg(text, phase string) Message

AbortMsg creates an assistant abort marker message. phase is "inference" or "tool_execution".

func CollectMessages

func CollectMessages(msgs []AgentMessage) []Message

CollectMessages extracts concrete model Messages from an AgentMessage slice, dropping application-specific types.

func RepairMessageSequence

func RepairMessageSequence(msgs []Message) []Message

RepairMessageSequence ensures tool call / tool result pairs are complete. Orphaned tool calls (no matching result) get a synthetic error result inserted. Orphaned tool results (no matching call) are removed. This prevents LLM providers from rejecting malformed message sequences.

func SystemMsg

func SystemMsg(text string) Message

SystemMsg creates a system message.

func ToMessages

func ToMessages(messages []AgentMessage) []Message

ToMessages lowers application messages to the model protocol at the LLM call boundary. Messages may opt out, for example when they only exist for persistence or UI display.

func ToolResultMsg

func ToolResultMsg(toolCallID string, content json.RawMessage, isError bool) Message

ToolResultMsg creates a tool result message.

func UserMsg

func UserMsg(text string) Message

UserMsg creates a user message from plain text.

func (Message) Compact

func (m Message) Compact(float64) (AgentMessage, float64)

func (Message) GetRole

func (m Message) GetRole() Role

func (Message) GetTimestamp

func (m Message) GetTimestamp() time.Time

func (Message) HasToolCalls

func (m Message) HasToolCalls() bool

HasToolCalls reports whether any tool call blocks exist.

func (Message) IsEmpty

func (m Message) IsEmpty() bool

IsEmpty reports whether the message has no meaningful content.

func (Message) Priority

func (m Message) Priority() int

func (Message) Raw

func (m Message) Raw() AgentMessage

func (Message) TextContent

func (m Message) TextContent() string

TextContent returns the concatenated text from all text blocks.

func (Message) ThinkingContent

func (m Message) ThinkingContent() string

ThinkingContent returns the concatenated thinking text.

func (Message) ToMessage

func (m Message) ToMessage() (Message, bool)

ToMessage returns the model representation of m. Failed and aborted model responses stay available to persistence and observers but are not replayed into later model requests.

func (Message) ToolCalls

func (m Message) ToolCalls() []ToolCall

ToolCalls returns all tool call blocks.

type MessageSequenceIssue

type MessageSequenceIssue struct {
	Kind           MessageSequenceIssueKind
	MessageIndex   int
	AssistantIndex int
	ToolCallID     string
	ToolName       string
}

MessageSequenceIssue describes a structural problem in a tool call / tool result transcript. The current validator intentionally stays narrow and focuses on the two invariants the loop already repairs today:

  • every tool call should have a following tool result
  • every tool result should reference a known tool call

func ValidateMessageSequence

func ValidateMessageSequence(msgs []Message) []MessageSequenceIssue

ValidateMessageSequence reports message-sequence issues that could cause provider rejections or inconsistent replay.

type MessageSequenceIssueKind

type MessageSequenceIssueKind string
const (
	MessageSequenceIssueMissingToolResult MessageSequenceIssueKind = "missing_tool_result"
	MessageSequenceIssueOrphanToolResult  MessageSequenceIssueKind = "orphan_tool_result"
)

type ModelNamer

type ModelNamer interface {
	ModelName() string
}

ModelNamer is an optional interface for ChatModel implementations to expose their model identifier (e.g. "claude-sonnet-4-6"). Harnesses need this for display and telemetry; without it they fall back to concrete-type assertions.

type PartialStreamError

type PartialStreamError struct {
	Partial Message
}

PartialStreamError indicates a stream closed without a terminal done event. Partial carries any content received before truncation; callers can inspect it for diagnostics but MUST NOT persist it as a completed message — the stream did not finish cleanly (missing StopReason, possibly truncated tool_call args, unclosed thinking blocks).

func (*PartialStreamError) Error

func (e *PartialStreamError) Error() string

func (*PartialStreamError) Is

func (e *PartialStreamError) Is(target error) bool

type Previewer

type Previewer interface {
	Preview(ctx context.Context, args json.RawMessage) (json.RawMessage, error)
}

Previewer is an optional interface for tools that can compute a preview (e.g., diff) before execution. The agent loop calls Preview and emits the result as EventToolExecUpdate so the UI can display it before the tool runs. A preview error is returned to the model and prevents tool execution.

type ProgressPayload

type ProgressPayload struct {
	Kind       ProgressPayloadKind `json:"kind"`
	Agent      string              `json:"agent,omitempty"`
	Tool       string              `json:"tool,omitempty"`
	Summary    string              `json:"summary,omitempty"`
	Delta      string              `json:"delta,omitempty"`
	Thinking   string              `json:"thinking,omitempty"`
	Message    string              `json:"message,omitempty"`
	Turn       int                 `json:"turn,omitempty"`
	Attempt    int                 `json:"attempt,omitempty"`
	MaxRetries int                 `json:"max_retries,omitempty"`
	IsError    bool                `json:"is_error,omitempty"`
	Args       json.RawMessage     `json:"args,omitempty"`
	Meta       json.RawMessage     `json:"meta,omitempty"`
	// DeltaKind distinguishes what kind of content Delta carries when Kind is
	// ProgressToolDelta. Consumers can use this to filter/render text vs
	// tool-call argument JSON differently.
	DeltaKind DeltaKind `json:"delta_kind,omitempty"`
}

ProgressPayload is the structured progress envelope emitted by tools.

type ProgressPayloadKind

type ProgressPayloadKind string

ProgressPayloadKind distinguishes structured progress update semantics.

const (
	ProgressToolStart   ProgressPayloadKind = "tool_start"
	ProgressToolEnd     ProgressPayloadKind = "tool_end"
	ProgressToolDelta   ProgressPayloadKind = "tool_delta"
	ProgressThinking    ProgressPayloadKind = "thinking"
	ProgressSummary     ProgressPayloadKind = "summary"
	ProgressToolError   ProgressPayloadKind = "tool_error"
	ProgressTurnCounter ProgressPayloadKind = "turn_counter"
	ProgressRetry       ProgressPayloadKind = "retry"
	ProgressContext     ProgressPayloadKind = "context"
)

type ProviderNamer

type ProviderNamer interface {
	ProviderName() string
}

ProviderNamer is an optional interface for ChatModel implementations to expose their provider name (e.g. "openai", "anthropic", "gemini"). Used by the agent loop to pass provider context to GetApiKey callbacks.

type ReadOnlyTool

type ReadOnlyTool interface {
	ReadOnly(args json.RawMessage) bool
}

ReadOnlyTool is an optional interface for tools that declare read-only behavior. Read-only tools are eligible for concurrent execution by default. The args parameter allows input-dependent classification (e.g., bash is read-only for "ls" but not for "rm").

type ResponseFormat

type ResponseFormat struct {
	Type       string      `json:"type"`
	JSONSchema *JSONSchema `json:"json_schema,omitempty"`
}

ResponseFormat controls provider-native structured output.

JSON object mode asks the model to return valid JSON. JSON schema mode asks compatible providers to constrain the final response to the supplied schema. Callers should still unmarshal and validate model output like any external input.

type RetryHinter

type RetryHinter interface {
	RetryAfter() time.Duration
}

RetryHinter when implemented, supplies a provider-specified backoff hint (e.g. a Retry-After header). The loop honors it for the next retry delay, capped at its own maximum. A zero duration means "no hint, use backoff".

type RetryInfo

type RetryInfo struct {
	Attempt    int
	MaxRetries int
	Delay      time.Duration
	Err        error
}

RetryInfo carries retry context for EventRetry events.

type RetryableError

type RetryableError interface {
	Retryable() bool
}

RetryableError when implemented by an error in the chain, tells the loop whether re-issuing the identical request may succeed. Model adapters implement it so the kernel decides same-provider retries without importing any LLM SDK. Errors that do not implement it are treated as non-retryable (the loop still has its own message-pattern classification as a fallback).

type Role

type Role string

Role defines message roles.

const (
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
	RoleSystem    Role = "system"
	RoleTool      Role = "tool"
)

type RunSummary

type RunSummary struct {
	TurnCount  int
	ToolCalls  int
	ToolErrors int
	EndReason  EndReason
}

RunSummary captures loop facts that are known at the end of a run. It intentionally excludes higher-level policy judgments.

type StopDecision

type StopDecision struct {
	// Allow=true lets the stop proceed; Allow=false keeps the loop alive.
	Allow bool
	// InjectMessage is delivered as a user message on the next turn when
	// Allow=false && !Escalate. Empty InjectMessage with Allow=false is
	// treated as Allow=true (safe default — never stall silently).
	InjectMessage string
	// Escalate=true ends the run immediately with an error.
	Escalate bool
}

StopDecision is the guard's verdict.

type StopGuard

type StopGuard func(ctx context.Context, stop StopInfo) StopDecision

StopGuard is the single arbiter for every normal stop of a run. It is consulted on both stop paths:

  • StopTriggerEndTurn: the LLM produced a final text response with no tool calls and no queued follow-up messages.
  • StopTriggerAfterTool: a StopAfterTool / StopAfterToolResult hook requested an early exit after a successful terminal tool.

Error paths never consult the guard: context cancellation (Abort), StopReasonError/StopReasonAborted from the provider, and the MaxTurns safety valve all terminate directly — a guard must not be able to override user aborts or safety limits.

Return Allow=true to let the agent stop normally. Return Allow=false with an InjectMessage to keep the agent running for another turn — the message is delivered as a user message on the next LLM call. Set Escalate=true to force the run to end with a guard-escalation error (used when the guard has repeatedly blocked stops and suspects a prompt bug).

Guard state (e.g. consecutive-block counters) is the guard's own responsibility; agentgo passes only the current turn index, the stopping assistant message, and which path triggered the check.

type StopInfo

type StopInfo struct {
	// TurnIndex is the index of the turn that just produced the stopping message.
	TurnIndex int
	// Message is the assistant message whose StopReason triggered this check.
	Message Message
	// Trigger reports which stop path is consulting the guard.
	Trigger StopTrigger
}

StopInfo carries the information a StopGuard needs to decide.

type StopReason

type StopReason string

StopReason indicates why the LLM stopped generating.

const (
	StopReasonStop    StopReason = "stop"
	StopReasonLength  StopReason = "length"
	StopReasonToolUse StopReason = "toolUse"
	StopReasonError   StopReason = "error"
	StopReasonSafety  StopReason = "safety"
	StopReasonAborted StopReason = "aborted"
)

type StopTrigger

type StopTrigger string

StopTrigger identifies which stop path is consulting the guard.

const (
	// StopTriggerEndTurn is the natural stop: final assistant response,
	// no tool calls, no queued follow-ups.
	StopTriggerEndTurn StopTrigger = "end_turn"
	// StopTriggerAfterTool is an early exit requested by the harness via
	// StopAfterTool / StopAfterToolResult.
	StopTriggerAfterTool StopTrigger = "stop_after_tool"
)

type StreamEvent

type StreamEvent struct {
	Type         StreamEventType
	ContentIndex int     // which content block is being updated
	Delta        string  // text/thinking/toolcall argument delta
	Message      Message // partial (during streaming) or final (done)
	// CompletedToolCall is populated on StreamEventToolCallEnd with the fully
	// reconstructed tool call. It lets the loop start execution immediately
	// without re-parsing the partial assistant message.
	CompletedToolCall *ToolCall
	StopReason        StopReason // finish reason (for done events)
	Err               error      // for error events
}

StreamEvent is a streaming event from the LLM.

type StreamEventType

type StreamEventType string

StreamEventType identifies LLM streaming event types.

const (
	// Text content streaming
	StreamEventTextStart StreamEventType = "text_start"
	StreamEventTextDelta StreamEventType = "text_delta"
	StreamEventTextEnd   StreamEventType = "text_end"

	// Thinking/reasoning streaming
	StreamEventThinkingStart StreamEventType = "thinking_start"
	StreamEventThinkingDelta StreamEventType = "thinking_delta"
	StreamEventThinkingEnd   StreamEventType = "thinking_end"

	// Tool call streaming
	StreamEventToolCallStart StreamEventType = "toolcall_start"
	StreamEventToolCallDelta StreamEventType = "toolcall_delta"
	StreamEventToolCallEnd   StreamEventType = "toolcall_end"

	// Terminal events
	StreamEventDone  StreamEventType = "done"
	StreamEventError StreamEventType = "error"
)

type StrictSchemaTool

type StrictSchemaTool interface {
	StrictSchema() bool
}

StrictSchemaTool is an optional interface for tools that want provider-side strict schema enforcement on their arguments (e.g. OpenAI's strict tool calling). Returning true forwards `strict: true` and triggers schema normalisation in compatible providers; returning false explicitly disables strict on providers that default to it (e.g. OpenAI Responses API).

Provider adapters own strict-schema normalization and validation because the supported subset differs by provider. Tool authors should consult the adapter documentation for provider-specific restrictions.

type SwappableModel

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

SwappableModel wraps a ChatModel and allows replacing the underlying model at runtime. Swaps take effect on the next call.

func NewSwappableModel

func NewSwappableModel(initial ChatModel) *SwappableModel

func (*SwappableModel) Current

func (m *SwappableModel) Current() ChatModel

func (*SwappableModel) Generate

func (m *SwappableModel) Generate(ctx context.Context, messages []Message, tools []ToolSpec, opts ...CallOption) (*LLMResponse, error)

func (*SwappableModel) GenerateStream

func (m *SwappableModel) GenerateStream(ctx context.Context, messages []Message, tools []ToolSpec, opts ...CallOption) (<-chan StreamEvent, error)

func (*SwappableModel) ModelName

func (m *SwappableModel) ModelName() string

func (*SwappableModel) ProviderName

func (m *SwappableModel) ProviderName() string

func (*SwappableModel) SupportsTools

func (m *SwappableModel) SupportsTools() bool

func (*SwappableModel) Swap

func (m *SwappableModel) Swap(next ChatModel)

type SystemBlock

type SystemBlock struct {
	Text         string `json:"text"`
	CacheControl string `json:"cache_control,omitempty"` // e.g. "ephemeral"
}

SystemBlock is one segment of a multi-part system prompt. Use with AgentContext.SystemBlocks for per-block cache control.

type ThinkingLevel

type ThinkingLevel string

ThinkingLevel configures the reasoning depth for models that support it.

const (
	// ThinkingAuto leaves thinking/reasoning behavior to the provider/model default.
	ThinkingAuto    ThinkingLevel = ""
	ThinkingOff     ThinkingLevel = "off"
	ThinkingMinimal ThinkingLevel = "minimal"
	ThinkingLow     ThinkingLevel = "low"
	ThinkingMedium  ThinkingLevel = "medium"
	ThinkingHigh    ThinkingLevel = "high"
	ThinkingXHigh   ThinkingLevel = "xhigh"
	ThinkingMax     ThinkingLevel = "max"
)

func NormalizeThinkingLevel

func NormalizeThinkingLevel(level ThinkingLevel) ThinkingLevel

NormalizeThinkingLevel returns the canonical level used internally. Empty and "auto" both mean "do not send a thinking override".

type Tool

type Tool interface {
	Name() string
	Description() string
	Schema() map[string]any
	Execute(ctx context.Context, args json.RawMessage) (json.RawMessage, error)
}

Tool defines the minimal tool interface. Timeout control goes through context.Context. Tools can report execution progress via ReportToolProgress(ctx, payload).

type ToolCall

type ToolCall struct {
	ID             string          `json:"id"`
	Name           string          `json:"name"`
	Args           json.RawMessage `json:"args"`
	ArgsInvalid    bool            `json:"args_invalid,omitempty"`
	ArgsRawText    string          `json:"args_raw_text,omitempty"`
	ArgsParseError string          `json:"args_parse_error,omitempty"`
	// ThoughtSignature is an opaque provider reasoning signature (Gemini 3) that
	// must be persisted and replayed verbatim across turns. Empty when absent.
	ThoughtSignature string `json:"thought_signature,omitempty"`
}

ToolCall represents a tool invocation request from the LLM.

When the LLM emits args that don't parse as JSON (common cause: stream truncation, provider format bug), Args is replaced with "{}" so the surrounding Message stays JSON-serializable for persistence; the original payload and parser diagnostic are preserved in ArgsRawText / ArgsParseError. Downstream schema validation short-circuits on ArgsInvalid and surfaces the captured raw text — pointing at the real root cause instead of running "missing field" checks against the {} placeholder.

type ToolExecUpdateKind

type ToolExecUpdateKind string

ToolExecUpdateKind distinguishes update payload semantics for tool_exec_update events.

const (
	ToolExecUpdatePreview  ToolExecUpdateKind = "preview"
	ToolExecUpdateProgress ToolExecUpdateKind = "progress"
)

type ToolExecuteFunc

type ToolExecuteFunc func(ctx context.Context, args json.RawMessage) (json.RawMessage, error)

ToolExecuteFunc is the function signature for tool execution. Used as the "next" parameter in middleware chains.

type ToolGate

type ToolGate func(ctx context.Context, req GateRequest) (*GateDecision, error)

ToolGate is the pluggable hook called once per tool call, after argument validation and after the optional Previewer pass, but before tool execution. Returning a non-nil error is treated as deny with the error message as the reason. The kernel does not perform any permission reasoning of its own; install a gate (or leave it nil) to control policy.

type ToolLabeler

type ToolLabeler interface {
	Label() string
}

ToolLabeler is an optional interface for tools to provide a human-readable label.

type ToolMiddleware

type ToolMiddleware func(ctx context.Context, call ToolCall, next ToolExecuteFunc) (json.RawMessage, error)

ToolMiddleware wraps tool execution with cross-cutting concerns. Call next to continue the chain; skip next to short-circuit execution. Example: logging, timing, argument/result modification, audit.

type ToolProgressFunc

type ToolProgressFunc func(progress ProgressPayload)

ToolProgressFunc is a callback for reporting tool execution progress. Tools call ReportToolProgress to emit partial results during long operations.

type ToolResult

type ToolResult struct {
	ToolCallID    string          `json:"tool_call_id"`
	ToolName      string          `json:"-"` // internal: for toolErrors tracking
	Content       json.RawMessage `json:"content,omitempty"`
	ContentBlocks []ContentBlock  `json:"-"` // rich content (images); not serialized
	IsError       bool            `json:"is_error,omitempty"`
	Details       any             `json:"details,omitempty"` // optional metadata for UI display/logging
}

ToolResult represents a tool execution outcome.

type ToolSpec

type ToolSpec struct {
	Name         string `json:"name"`
	Description  string `json:"description"`
	Parameters   any    `json:"parameters"`
	DeferLoading bool   `json:"defer_loading,omitempty"`
	// Strict enables provider-side strict schema enforcement (OpenAI strict
	// tool calling / Structured Outputs for arguments). nil leaves the
	// provider default. Set via the optional StrictSchemaTool interface.
	Strict *bool `json:"strict,omitempty"`
}

ToolSpec describes a tool for the LLM (name + description + JSON schema).

type ToolValidationError

type ToolValidationError struct {
	ToolName string
	Issues   []ValidationIssue
}

ToolValidationError is returned when tool call arguments fail schema validation. The agent loop surfaces it as a tool_result with IsError=true, not as a fatal loop error, so the model can self-correct on the next turn. errors.Is matches ErrToolValidation.

func (*ToolValidationError) Error

func (e *ToolValidationError) Error() string

func (*ToolValidationError) Is

func (e *ToolValidationError) Is(target error) bool

type Usage

type Usage struct {
	Provider string `json:"provider,omitempty"`
	Model    string `json:"model,omitempty"`

	Input       int   `json:"input"`
	Output      int   `json:"output"`
	CacheRead   int   `json:"cache_read"`
	CacheWrite  int   `json:"cache_write"`
	TotalTokens int   `json:"total_tokens"`
	Cost        *Cost `json:"cost,omitempty"`
}

Usage tracks token consumption for a single LLM call.

Field semantics:

  • Input: prompt tokens sent to the model (includes cached tokens for some providers)
  • Output: completion tokens generated (includes reasoning tokens if applicable)
  • CacheRead: tokens served from prompt cache (Anthropic: cache_read_input_tokens)
  • CacheWrite: tokens written to prompt cache (Anthropic: cache_creation_input_tokens)
  • TotalTokens: provider-reported total, typically Input + Output
  • Provider/Model: actual provider/model that produced this call, if reported
  • Cost: monetary cost computed from model pricing (nil if pricing unavailable)

func (*Usage) Add

func (u *Usage) Add(other *Usage)

Add accumulates another Usage into this one (nil-safe).

type ValidationIssue

type ValidationIssue struct {
	Kind     string
	Path     string
	Expected string
	Received string
	Hint     string // optional fix hint, appended to the rendered message
}

ValidationIssue describes a single schema mismatch from tool arg validation.

type ValidationResult

type ValidationResult struct {
	OK        bool
	Message   string
	ErrorCode int
}

ValidationResult is the verdict from a Validator.

A failure (OK=false) is surfaced to the LLM as a normal tool_result with IsError=true. The intent is "input is structurally legal but semantically wrong" — e.g. write before read, mtime drift, deny rule. The LLM reads Message and self-corrects (typically by issuing the right tool first and retrying), without prompting the user.

ErrorCode is optional, intended for stable identification by tests and prompts; it is not interpreted by the kernel.

type Validator

type Validator interface {
	Validate(ctx context.Context, args json.RawMessage) ValidationResult
}

Validator is an optional interface for tools that want to short-circuit before Preview / ToolGate / Execute when the input is structurally legal but semantically wrong. Validators MUST NOT prompt the user, MUST NOT mutate persistent state, and SHOULD be cheap (read-only lookups, stat).

Returning OK=false produces a tool_result the LLM can act on; returning OK=true continues the normal pipeline.

Directories

Path Synopsis
Package context provides message-native context compression for agentgo: prompt projection, summary checkpoints, overflow recovery, and usage estimation.
Package context provides message-native context compression for agentgo: prompt projection, summary checkpoints, overflow recovery, and usage estimation.
examples
multi command
single command
Package llm adapts LLM providers to the agentgo.ChatModel interface.
Package llm adapts LLM providers to the agentgo.ChatModel interface.
Package permission is an optional policy engine for gating tool execution.
Package permission is an optional policy engine for gating tool execution.
Package proxy provides a ChatModel adapter that forwards LLM calls to a remote proxy server.
Package proxy provides a ChatModel adapter that forwards LLM calls to a remote proxy server.
Package schema provides a fluent builder for JSON Schema objects.
Package schema provides a fluent builder for JSON Schema objects.
Package subagent runs specialized agents with isolated contexts.
Package subagent runs specialized agents with isolated contexts.
Package task is a unified registry for background tasks.
Package task is a unified registry for background tasks.
Package team implements Team — multi-agent peer-to-peer collaboration on top of the Subagent foundation.
Package team implements Team — multi-agent peer-to-peer collaboration on top of the Subagent foundation.
Package tools provides the built-in agent tools — read, write, edit, and bash.
Package tools provides the built-in agent tools — read, write, edit, and bash.

Jump to

Keyboard shortcuts

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