agentcore

package module
v1.6.11 Latest Latest
Warning

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

Go to latest
Published: Jun 4, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

README

AgentCore

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

English | 中文

Install

go get github.com/voocel/agentcore

Design Philosophy

A restrained core with open extensibility tends to be more reliable than a complex all-in-one solution. Fewer built-ins, more possibilities.

Stability

  • Keep Agent, AgentLoop, Event, Tool, and Message stable first
  • examples/ and internal implementation details are not stable API

Architecture

agentcore/            Agent core (types, loop, agent, events)
agentcore/llm/        LLM adapters (OpenAI, Anthropic, Gemini via litellm)
agentcore/tools/      Built-in tools: read, write, edit, bash
agentcore/context/    Context runtime — projection, rewrite, overflow recovery
agentcore/task/       Background task registry (Runtime / Entry) shared by bash + subagent
agentcore/subagent/   SubAgent tool — multi-agent via tool invocation
agentcore/proxy/      ChatModel adapter that forwards calls to a remote proxy
agentcore/permission/ Optional permission engine — adapt to ToolGate yourself

Core design:

  • Standalone loop + stateful Agentloop.go is a free function with all dependencies injected; agent.go is the sole consumer of loop events, updating internal state and dispatching to listeners. Double loop: inner processes tool calls + steering, outer handles follow-up
  • Event stream — single <-chan Event output drives any UI (TUI, Web, Slack, logging)
  • Context layerContextManager (interface) + agentcore/context (default engine) drive prompt projection, overflow recovery, and—via auto-wiring—message conversion and token estimation
  • SubAgent tool (subagent/) — multi-agent via tool invocation, four modes: single, parallel, chain, background

Quick Start

Single Agent
package main

import (
    "fmt"
    "os"

    "github.com/voocel/agentcore"
    "github.com/voocel/agentcore/llm"
    "github.com/voocel/agentcore/tools"
)

func main() {
    model, err := llm.NewModel("openai", "gpt-5-mini", llm.WithAPIKey(os.Getenv("OPENAI_API_KEY")))
    if err != nil {
        panic(err)
    }

    // Shared FileReadState so Write/Edit can enforce read-before-write.
    fileState := tools.NewFileReadState()
    agent := agentcore.NewAgent(
        agentcore.WithModel(model),
        agentcore.WithSystemPrompt("You are a helpful coding assistant."),
        agentcore.WithTools(
            tools.NewRead(".", fileState),
            tools.NewWrite(".", fileState),
            tools.NewEdit(".", fileState),
            tools.NewBash("."),
        ),
    )

    agent.Subscribe(func(ev agentcore.Event) {
        if ev.Type == agentcore.EventMessageEnd {
            if msg, ok := ev.Message.(agentcore.Message); ok && msg.Role == agentcore.RoleAssistant {
                fmt.Println(msg.Content)
            }
        }
    })

    agent.Prompt("List the files in the current directory.")
    agent.WaitForIdle()
}

For tool-call gating, register a ToolGate — a single hook called once per tool call after argument validation. The kernel implements no permission policy of its own; gates are user-supplied.

gate := func(ctx context.Context, req agentcore.GateRequest) (*agentcore.GateDecision, error) {
    if req.Call.Name == "bash" {
        return &agentcore.GateDecision{Allowed: false, Reason: "bash disabled"}, nil
    }
    return &agentcore.GateDecision{Allowed: true}, nil
}

agent := agentcore.NewAgent(
    // ... model, tools, etc.
    agentcore.WithToolGate(gate),
)

The optional agentcore/permission subpackage offers a richer decision engine (modes, rules, filesystem roots, audit). Adapt it to ToolGate with a small wrapper.

Multi-Agent (SubAgent Tool)

Sub-agents are invoked as regular tools with isolated contexts. Import the agentcore/subagent subpackage:

import (
    "github.com/voocel/agentcore"
    "github.com/voocel/agentcore/llm"
    "github.com/voocel/agentcore/subagent"
    "github.com/voocel/agentcore/tools"
)

model, _ := llm.NewModel("openai", "gpt-5-mini", llm.WithAPIKey(apiKey))

// Each sub-agent gets its own FileReadState — independent read history.
scoutState := tools.NewFileReadState()
workerState := tools.NewFileReadState()

scout := subagent.Config{
    Name:         "scout",
    Description:  "Fast codebase reconnaissance",
    Model:        model,
    SystemPrompt: "Quickly explore and report findings. Be concise.",
    Tools:        []agentcore.Tool{tools.NewRead(".", scoutState), tools.NewBash(".")},
    MaxTurns:     5,
}

worker := subagent.Config{
    Name:         "worker",
    Description:  "General-purpose executor",
    Model:        model,
    SystemPrompt: "Implement tasks given to you.",
    Tools:        []agentcore.Tool{tools.NewRead(".", workerState), tools.NewWrite(".", workerState), tools.NewEdit(".", workerState), tools.NewBash(".")},
}

agent := agentcore.NewAgent(
    agentcore.WithModel(model),
    agentcore.WithTools(subagent.New(scout, worker)),
)

For background mode (async sub-agent runs that notify on completion), wire a shared task runtime:

import "github.com/voocel/agentcore/task"

rt := task.NewRuntime()
sat := subagent.New(scout, worker)
sat.SetTaskRuntime(rt)
sat.SetNotifyFn(agent.FollowUp) // route completion notifications back to the parent

Four execution modes via tool call:

// Single: one agent, one task
{"agent": "scout", "task": "Find all API endpoints"}

// Parallel: concurrent execution
{"tasks": [{"agent": "scout", "task": "Find auth code"}, {"agent": "scout", "task": "Find DB schema"}]}

// Chain: sequential with {previous} context passing
{"chain": [{"agent": "scout", "task": "Find auth code"}, {"agent": "worker", "task": "Refactor based on: {previous}"}]}

// Background: async execution, returns immediately, notifies on completion
{"agent": "worker", "task": "Run full test suite", "background": true, "description": "Running tests"}
Steering & Injection

Inject(msg) delivers a message according to the agent's current state — preferred when the caller's intent is "deliver this as soon as possible" without manually branching on running vs idle:

result, _ := agent.Inject(agentcore.UserMsg("Re-check unfinished tasks before stopping."))
fmt.Println(result.Disposition)

Outcomes:

  • steered_current_run — agent was running; message went into the current run's steering path
  • resumed_idle_run — agent was idle with an assistant-tail conversation; message queued and Continue() started
  • queued — message queued, no run started

For finer control, use the lower-level APIs directly:

agent.Steer(agentcore.UserMsg("Stop and focus on tests instead.")) // mid-run interrupt
agent.FollowUp(agentcore.UserMsg("Now run the tests."))            // queue for after current run
agent.Abort()                                                      // cancel immediately

If a message must be merged into the next explicit user prompt (rather than the agent's queues), keep that in the application layer.

Event Stream

All lifecycle events flow through a single channel — subscribe to drive any UI:

agent.Subscribe(func(ev agentcore.Event) {
    switch ev.Type {
    case agentcore.EventMessageStart:    // assistant starts streaming
    case agentcore.EventMessageUpdate:   // streaming token delta
    case agentcore.EventMessageEnd:      // message complete
    case agentcore.EventToolExecStart:   // tool execution begins
    case agentcore.EventToolExecEnd:     // tool execution ends
    case agentcore.EventError:           // error occurred
    }
})
Structured Tool Progress

Long-running tools can emit structured progress updates instead of ad-hoc JSON:

agentcore.ReportToolProgress(ctx, agentcore.ProgressPayload{
    Kind:    agentcore.ProgressSummary,
    Agent:   "worker",
    Tool:    "bash",
    Summary: "worker → bash",
})

Subscribers should read ev.Progress directly for tool progress updates:

agent.Subscribe(func(ev agentcore.Event) {
    if ev.Type == agentcore.EventToolExecUpdate && ev.Progress != nil {
        fmt.Printf("[%s] %s\n", ev.Progress.Kind, ev.Progress.Summary)
    }
})
Swappable Models

When a model needs to change at runtime, wrap it with SwappableModel. The swap takes effect on the next call. subagent.Config.Model is resolved at the start of each sub-agent run, so the same wrapper also works for sub-agents.

defaultModel, _ := llm.NewModel("openai", "gpt-5-mini", llm.WithAPIKey(apiKey))
sw := agentcore.NewSwappableModel(defaultModel)

agent := agentcore.NewAgent(agentcore.WithModel(sw))

nextModel, _ := llm.NewModel("openai", "gpt-5", llm.WithAPIKey(apiKey))
sw.Swap(nextModel) // next turn uses the new model
Custom LLM Adapter

To swap the LLM call with a proxy, mock, or custom implementation, implement the ChatModel interface and pass it via WithModel. SwappableModel and the agentcore/proxy subpackage are built on this same interface and can serve as references.

Context Compaction

Auto-summarize conversation history when approaching the context window limit. Use the built-in context manager:

import (
    "github.com/voocel/agentcore"
    agentctx "github.com/voocel/agentcore/context"
)

engine := agentctx.NewDefaultEngine(model, 128000)

agent := agentcore.NewAgent(
    agentcore.WithModel(model),
    agentcore.WithContextManager(engine),
)

NewAgent auto-wires ConvertToLLM, token estimation, and context window from the context manager when available.

When usage exceeds ContextWindow - ReserveTokens (default 16384), compaction:

  1. Keeps recent messages (default 20000 tokens)
  2. Summarizes older messages via LLM into a structured checkpoint (Goal / Progress / Key Decisions / Next Steps)
  3. Tracks file operations (read/write/edit paths) across compacted messages
  4. Supports incremental updates — subsequent compactions update the existing summary rather than re-summarizing

Built-in Tools

Tool Description
read Read file contents with head truncation (2000 lines / 50KB)
write Write file with auto-mkdir
edit Exact text replacement with fuzzy match, BOM/line-ending normalization, unified diff output
bash Execute shell commands with tail truncation (2000 lines / 50KB)

API Reference

Agent
Method Description
NewAgent(opts...) Create agent with options
Prompt(input) Start new conversation turn
PromptMessages(msgs...) Start turn with arbitrary AgentMessages
Continue() Resume from current context
Inject(msg) Deliver message via steer / idle resume / queue, depending on current state
Steer(msg) Inject steering message mid-run
FollowUp(msg) Queue message for after completion
Abort() Cancel current execution
AbortSilent() Cancel without emitting abort marker
WaitForIdle() Block until agent finishes
Subscribe(fn) Register event listener
State() Snapshot of current state
ExportMessages() Export messages for serialization
ImportMessages(msgs) Import deserialized messages
BuildLLMMessages() Materialize the next-call prompt (system → projected history)

License

Apache License 2.0

Documentation

Index

Constants

View Source
const (
	IssueMissing = "missing"
	IssueType    = "type"
)
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")
)

Sentinel errors. Use with errors.Is.

View Source
var (
	ErrProviderRateLimit  = errors.New("provider rate limit")
	ErrProviderTimeout    = errors.New("provider timeout")
	ErrProviderStreamIdle = errors.New("provider stream idle")
	ErrProviderNetwork    = errors.New("provider network")
	ErrProviderAuth       = errors.New("provider auth")
)

Provider runtime sentinels. These categorize errors returned by the LLM provider at call time (litellm 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.

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 ConvertToLLM.

func AssertMessageSequence added in v1.6.0

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 added in v1.6.9

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 (via litellm.IsStreamIdleError) 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 already handles both agentcore and litellm layers).

func FailoverReason added in v1.6.9

func FailoverReason(err error) string

FailoverReason returns a stable short label ("rate_limit" / "timeout" / "stream_idle" / "network") 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 at any layer (agentcore wrapper or raw litellm provider). Convenience for callers that want to detect "request too big" without caring where it surfaced.

func IsFailoverEligible added in v1.6.9

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, context_overflow, user cancellation, or unclassified errors.

func IsStreamIdleMessage added in v1.6.9

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 added in v1.5.2

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 added in v1.6.0

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 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 ConvertToLLM, the context-token estimator, and the context window from the manager when it implements the optional ContextLLMConverter / ContextEstimator / ContextWindower 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.

func (*Agent) AbortSilent added in v1.5.1

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 added in v1.6.1

func (a *Agent) BaselineContextUsage() *ContextUsage

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

func (*Agent) BuildLLMMessages added in v1.5.6

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 added in v1.6.10

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) ClearMessages

func (a *Agent) ClearMessages()

ClearMessages resets the message history.

func (*Agent) ClearSteeringQueue

func (a *Agent) ClearSteeringQueue()

ClearSteeringQueue removes all queued steering messages.

func (*Agent) ContextSnapshot added in v1.6.1

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() error

Continue resumes from the current context without adding new messages. If the last message is from assistant, it dequeues steering/follow-up

func (*Agent) ExportMessages

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

ExportMessages returns concrete Messages for serialization.

func (*Agent) FollowUp

func (a *Agent) FollowUp(msg AgentMessage)

FollowUp queues a message to be processed after the agent finishes.

func (*Agent) HasQueuedMessages

func (a *Agent) HasQueuedMessages() bool

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

func (*Agent) ImportMessages

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

ImportMessages replaces message history from deserialized Messages.

func (*Agent) Inject added in v1.5.7

func (a *Agent) Inject(msg AgentMessage) (InjectResult, error)

Inject delivers a message as soon as the current agent state allows.

Outcomes:

  • running → steer into current run
  • idle + assistant tail → enqueue and Continue()
  • idle + no assistant tail → enqueue for next run

Returns an error if idle resume was attempted but Continue() failed. In that case the message remains in the steering queue and will be delivered on the next run.

func (*Agent) Messages

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

Messages returns the current message history.

func (*Agent) Prompt

func (a *Agent) Prompt(input string) error

Prompt starts a new conversation turn with the given input.

func (*Agent) PromptMessages

func (a *Agent) PromptMessages(msgs ...AgentMessage) error

PromptMessages starts a new conversation turn with arbitrary AgentMessages.

func (*Agent) Reset

func (a *Agent) Reset()

Reset clears all state and queues. If the agent is running, it cancels and waits first.

func (*Agent) SetContextWindow added in v1.5.2

func (a *Agent) SetContextWindow(n int)

SetContextWindow updates the context window size (in tokens).

func (*Agent) SetMessages

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

SetMessages replaces the message history (e.g. to restore a previous conversation). The agent must not be running.

func (*Agent) SetModel

func (a *Agent) SetModel(m ChatModel)

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

func (*Agent) SetSystemBlocks added in v1.5.2

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.

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.

func (*Agent) SetThinkingLevel

func (a *Agent) SetThinkingLevel(level ThinkingLevel)

SetThinkingLevel changes the reasoning depth. Takes effect on the next turn.

func (*Agent) SetTools

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

SetTools replaces the tool set. Takes effect on the next turn.

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.

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
	TextContent() string
	ThinkingContent() string
	HasToolCalls() bool
}

AgentMessage is the app-layer message abstraction. Message implements this interface. Users can define custom types (e.g. status notifications, UI hints) that flow through the context pipeline but get filtered out by ConvertToLLM.

func Collect added in v1.5.1

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 WithCacheLastMessage added in v1.6.9

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.

Pass "" (default) to leave messages untouched. Pass "ephemeral" for the standard 5-minute TTL, or any provider-recognized value. Use this when the application — not the LLM library — owns cache placement.

func WithContextManager added in v1.6.0

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 ConvertToLLM, context-token estimation, and the context window from the manager when it implements the optional ContextLLMConverter / ContextEstimator / ContextWindower interfaces.

func WithMaxRetries

func WithMaxRetries(n int) AgentOption

WithMaxRetries sets the LLM call retry limit for retryable errors.

func WithMaxToolConcurrency added in v1.5.1

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.

func WithMiddlewares added in v1.5.1

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 added in v1.6.3

func WithOnMessage(fn func(AgentMessage)) AgentOption

WithOnMessage registers a callback invoked after each message is appended to the agent's context. Use for session logging / message persistence.

func WithStopGuard added in v1.6.4

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 added in v1.5.2

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 added in v1.6.8

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 agent core does not implement permission reasoning of its own — gates are user-supplied.

func WithTools

func WithTools(tools ...Tool) AgentOption

WithTools sets the tool list.

func WithToolsAreIdempotent added in v1.6.5

func WithToolsAreIdempotent(idempotent bool) AgentOption

WithToolsAreIdempotent declares that all tools registered on this agent are safe to re-execute with the same arguments. When true, an LLM call that fails after a tool_call has already streamed (e.g. stream-idle timeout before stop_reason arrives) will be retried instead of bailing out: the in-flight tool execution is aborted and the turn replays from scratch.

Only enable this when every tool's side effect is deduplicated by content (e.g. checkpoint+digest, write-once via tmp+rename). The default (false) preserves the conservative behavior that protects non-idempotent tools.

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 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
	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 WithCallSessionID

func WithCallSessionID(id string) CallOption

WithCallSessionID sets a session identifier for a single LLM call.

func WithJSONMode added in v1.6.9

func WithJSONMode() CallOption

WithJSONMode requests valid JSON output without enforcing a specific schema.

func WithJSONSchema added in v1.6.9

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

WithJSONSchema requests structured JSON output constrained by a JSON Schema.

func WithMaxTokens added in v1.5.1

func WithMaxTokens(tokens int) CallOption

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

func WithResponseFormat added in v1.6.9

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 added in v1.6.2

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 added in v1.6.0

type CompactReason string

CompactReason identifies why a committed context rewrite was requested. It is attached to explicit commits such as manual /compact or overflow recovery, not to transient per-request projections.

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

type ConcurrencySafer added in v1.6.0

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

ConcurrencySafer is an optional interface for tools that declare whether they can safely execute concurrently with other tools. Takes precedence over ReadOnlyer 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 added in v1.5.1

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 added in v1.5.2

func ToolRefBlock(toolName string) ContentBlock

type ContentTool added in v1.5.1

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 added in v1.6.0

type ContextCommitResult struct {
	Messages       []AgentMessage
	Usage          *ContextUsage
	Changed        bool
	Strategy       string
	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 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 added in v1.6.0

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 ContextLLMConverter added in v1.6.0

type ContextLLMConverter interface {
	ConvertToLLM([]AgentMessage) []Message
}

ContextLLMConverter is an optional interface a ContextManager can implement to provide its own AgentMessage → Message conversion (e.g. to handle summary message types). When implemented, NewAgent auto-wires it.

type ContextManager added in v1.6.0

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 added in v1.6.9

type ContextOverflowError struct {
	Cause error
}

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

func (*ContextOverflowError) Error added in v1.6.9

func (e *ContextOverflowError) Error() string

func (*ContextOverflowError) Is added in v1.6.9

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

func (*ContextOverflowError) Unwrap added in v1.6.9

func (e *ContextOverflowError) Unwrap() error

type ContextProjection added in v1.6.0

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

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 added in v1.6.0

type ContextRecoveryResult struct {
	View           []AgentMessage
	CommitMessages []AgentMessage
	Usage          *ContextUsage
	Changed        bool
	ShouldCommit   bool
	Strategy       string
	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 added in v1.6.0

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

ContextSnapshot describes both the runtime baseline and the current active context view, 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 ContextWindower added in v1.6.0

type ContextWindower interface {
	ContextWindow() int
}

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

type Cost added in v1.5.1

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 added in v1.5.1

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

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

type DeferActivator added in v1.5.2

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 added in v1.5.2

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 added in v1.6.3

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 added in v1.5.7

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
	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 added in v1.5.1

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 := agentcore.NewEventStream(AgentLoop(...))
for ev := range stream.Events() {
    // handle real-time events
}
msgs, err := stream.Result()

func NewEventStream added in v1.5.1

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.

func (*EventStream) Done added in v1.5.1

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

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

func (*EventStream) Events added in v1.5.1

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 added in v1.5.1

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"
	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"
	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 added in v1.6.8

type GateDecision struct {
	Allowed bool
	Reason  string
}

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

Allowed=true => execute the tool with Call.Args. Allowed=false => return Reason as the tool result error; do not execute.

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

type GateRequest added in v1.6.8

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 agent core 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 added in v1.5.7

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 added in v1.5.7

type InjectResult struct {
	Disposition InjectDisposition
}

InjectResult reports the delivery outcome of Agent.Inject.

type InterruptBehavior added in v1.6.0

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 InterruptBehaviorer added in v1.6.0

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

InterruptBehaviorer 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 added in v1.6.9

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 10
	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

	// Context lifecycle. ContextManager drives prompt projection, overflow
	// recovery, and usage reporting; ConvertToLLM is auto-wired from it when
	// the manager implements ContextLLMConverter.
	ContextManager ContextManager
	ConvertToLLM   func(msgs []AgentMessage) []Message

	// 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

	// 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 agent core 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 immediately with
	// EndReasonStop. Use this to let a terminal tool (e.g. commit_chapter)
	// end the loop without wasting turns.
	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 message is appended to
	// context (assistant, tool result, steering). Use for session logging.
	OnMessage func(msg AgentMessage)

	// StopGuard is consulted when the LLM would end a run without tool calls.
	// Nil (default) means every stop is allowed.
	StopGuard StopGuard

	// ToolsAreIdempotent declares that all registered tools are safe to re-execute
	// with the same arguments — i.e. running them twice produces the same observable
	// state as running them once. When true, the retry loop will not bail out just
	// because a tool_call already completed in the failed turn; instead it aborts
	// the in-flight tool executions and retries the whole turn. Default false
	// (conservative: assume tools may have non-idempotent side effects).
	ToolsAreIdempotent bool

	// 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
}

LoopConfig configures the agent loop.

type MaxTurnsError added in v1.6.9

type MaxTurnsError struct {
	Limit int
}

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

func (*MaxTurnsError) Error added in v1.6.9

func (e *MaxTurnsError) Error() string

func (*MaxTurnsError) Is added in v1.6.9

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 added in v1.5.1

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 Messages from an AgentMessage slice, dropping custom types. Use this to serialize conversation history.

func DefaultConvertToLLM

func DefaultConvertToLLM(msgs []AgentMessage) []Message

DefaultConvertToLLM filters AgentMessages to LLM-compatible Messages. Custom message types are dropped; only user/assistant/system/tool messages pass through.

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 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) 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) 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) ToolCalls

func (m Message) ToolCalls() []ToolCall

ToolCalls returns all tool call blocks.

type MessageSequenceIssue added in v1.6.0

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 added in v1.6.0

func ValidateMessageSequence(msgs []Message) []MessageSequenceIssue

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

type MessageSequenceIssueKind added in v1.6.0

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

type PartialStreamError added in v1.6.9

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 added in v1.6.9

func (e *PartialStreamError) Error() string

func (*PartialStreamError) Is added in v1.6.9

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

type Previewer added in v1.5.1

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.

type ProgressPayload added in v1.5.7

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 added in v1.5.7

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 ReadOnlyer added in v1.6.0

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

ReadOnlyer 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 added in v1.6.9

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 RetryInfo

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

RetryInfo carries retry context for EventRetry events.

type Role

type Role string

Role defines message roles.

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

type RunSummary added in v1.5.7

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 added in v1.6.4

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 added in v1.6.4

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

StopGuard is consulted when the LLM would end a run without tool calls (i.e. the assistant produced a final text response and no more tool calls).

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; agentcore passes only the current turn index and the stopping assistant message.

type StopInfo added in v1.6.4

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
}

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"
	StopReasonAborted StopReason = "aborted"
)

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 added in v1.6.5

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).

The tool author is responsible for providing a strict-compatible schema: every property listed in `required`, no unsupported keywords. See the litellm provider docs for the exact subset.

type SwappableModel added in v1.5.7

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 added in v1.5.7

func NewSwappableModel(initial ChatModel) *SwappableModel

func (*SwappableModel) Current added in v1.5.7

func (m *SwappableModel) Current() ChatModel

func (*SwappableModel) Generate added in v1.5.7

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

func (*SwappableModel) GenerateStream added in v1.5.7

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

func (*SwappableModel) ProviderName added in v1.5.7

func (m *SwappableModel) ProviderName() string

func (*SwappableModel) SupportsTools added in v1.5.7

func (m *SwappableModel) SupportsTools() bool

func (*SwappableModel) Swap added in v1.5.7

func (m *SwappableModel) Swap(next ChatModel)

type SystemBlock added in v1.5.2

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 (
	ThinkingOff     ThinkingLevel = "off"
	ThinkingMinimal ThinkingLevel = "minimal"
	ThinkingLow     ThinkingLevel = "low"
	ThinkingMedium  ThinkingLevel = "medium"
	ThinkingHigh    ThinkingLevel = "high"
	ThinkingXHigh   ThinkingLevel = "xhigh"
)

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 added in v1.5.1

type ToolExecUpdateKind string

ToolExecUpdateKind distinguishes update payload semantics for tool_exec_update events.

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

type ToolExecuteFunc added in v1.5.1

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 added in v1.6.8

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 agent core 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 added in v1.5.1

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 added in v1.6.9

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 added in v1.6.9

func (e *ToolValidationError) Error() string

func (*ToolValidationError) Is added in v1.6.9

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 added in v1.6.9

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

ValidationIssue describes a single schema mismatch from tool arg validation.

type ValidationResult added in v1.6.9

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 agent core.

type Validator added in v1.6.9

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 strategy-driven context compression for agentcore: prompt projection, summary checkpoints, overflow recovery, and usage estimation.
Package context provides strategy-driven context compression for agentcore: prompt projection, summary checkpoints, overflow recovery, and usage estimation.
examples
multi command
single command
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 implements a Tool that delegates work to specialized sub-agents with isolated contexts.
Package subagent implements a Tool that delegates work to specialized sub-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.

Jump to

Keyboard shortcuts

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