agentcore

package
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Sep 12, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

Documentation

Overview

Package agentcore is a small, provider-neutral toolkit for driving a streaming language model through a tool-use loop.

The package is deliberately self-contained: it reads no environment, performs no I/O of its own, and imports nothing outside the standard library and its own sub-packages. A concrete Model (see the anthropic, openaicompat, and mockmodel sub-packages), a set of executable tools (see the mcptool sub-package or any type implementing Tool), and a context are injected by the caller. This makes agentcore a candidate for extraction as a standalone "Go AI SDK core".

The two entry points are:

  • Model, the interface every provider adapter implements, and
  • Run, which drives a Model through the tool-use loop and streams back a unified sequence of StreamPart values.

Index

Constants

View Source
const (
	// DefaultMaxRetries is the number of ADDITIONAL attempts made after the
	// first on a retryable failure (so up to DefaultMaxRetries+1 attempts).
	DefaultMaxRetries = 2
	// DefaultRetryBaseDelay is the base of the exponential backoff.
	DefaultRetryBaseDelay = 500 * time.Millisecond
	// DefaultRetryMaxDelay caps a single backoff wait (before honoring a
	// server Retry-After, which is obeyed verbatim).
	DefaultRetryMaxDelay = 8 * time.Second
)

Retry defaults. They are deliberately conservative: a real provider's transient failures (rate limits, brief overloads) clear in seconds, and the per-turn deadline (see internal/agent) bounds the total wait regardless.

View Source
const DefaultStepLimit = 8

DefaultStepLimit is the number of model steps (tool-call rounds) Run allows before it stops gracefully. It is used when LoopOptions.StepLimit is zero.

Variables

This section is empty.

Functions

func RetryAfterFromHeader

func RetryAfterFromHeader(h http.Header) time.Duration

RetryAfterFromHeader extracts a Retry-After wait from a response header. It accepts both forms the RFC allows — a delay in seconds ("Retry-After: 30") and an HTTP-date — returning 0 when the header is absent or unparseable.

Types

type ContentPart

type ContentPart struct {
	Kind       ContentPartKind
	Text       string
	ToolCall   *ToolCall
	ToolResult *ToolResult
}

ContentPart is a single element of a Message's content. Exactly one of the typed fields is populated, selected by Kind.

func TextPart

func TextPart(text string) ContentPart

TextPart returns a text content part.

type ContentPartKind

type ContentPartKind string

ContentPartKind discriminates the variants of ContentPart.

const (
	// ContentText is a plain-text fragment; see ContentPart.Text.
	ContentText ContentPartKind = "text"
	// ContentToolCall is a model request to invoke a tool; see
	// ContentPart.ToolCall.
	ContentToolCall ContentPartKind = "tool-call"
	// ContentToolResult is the outcome of a tool execution; see
	// ContentPart.ToolResult.
	ContentToolResult ContentPartKind = "tool-result"
)

type ErrorClass

type ErrorClass int

ErrorClass categorizes a model failure so the loop can decide whether to retry it and how to surface it. Adapters classify their SDK/transport errors into these buckets (see ModelError); the loop never inspects a provider SDK type directly.

const (
	// ErrClassUpstream is a generic, non-retryable upstream failure (e.g. a
	// 500 the SDK did not attribute to overload). It is terminal.
	ErrClassUpstream ErrorClass = iota
	// ErrClassRateLimited is a 429 rate-limit response. Retryable.
	ErrClassRateLimited
	// ErrClassOverloaded is a provider-overloaded response (Anthropic 529,
	// or a 503 service-unavailable). Retryable.
	ErrClassOverloaded
	// ErrClassTransient is a transient transport failure (connection reset,
	// timeout, unexpected EOF before any output). Retryable.
	ErrClassTransient
	// ErrClassAuth is an authentication/authorization failure (401/403).
	// Terminal — retrying with the same credential cannot succeed.
	ErrClassAuth
	// ErrClassInvalidRequest is a malformed request (400). Terminal.
	ErrClassInvalidRequest
	// ErrClassContextLength means the prompt (usually a long history) exceeded
	// the model's context window (413, or a 400 the provider attributes to
	// length). Terminal — a real model WILL hit this and the caller must see a
	// clear message rather than a retry storm.
	ErrClassContextLength
)

func ClassifyStatus

func ClassifyStatus(status int, contextLengthHint bool) ErrorClass

ClassifyStatus maps an HTTP status code to an ErrorClass. It is the shared classifier both HTTP adapters use so 429/503/529/4xx are bucketed identically regardless of provider. contextLengthHint lets an adapter force ErrClassContextLength when it recognizes a length error the status code alone does not reveal (e.g. a 400 whose body says the prompt is too long).

func (ErrorClass) Retryable

func (c ErrorClass) Retryable() bool

Retryable reports whether a failure of this class is worth retrying with backoff. Only transient, provider-side conditions are retryable; client errors (auth, invalid request, context length) never are.

type FinishReason

type FinishReason string

FinishReason explains why a model step, or the whole run, stopped.

const (
	// FinishStop is a normal end of turn (the model chose to stop).
	FinishStop FinishReason = "stop"
	// FinishToolCalls means the step ended because the model emitted tool
	// calls. It appears on a per-step [StreamPartStepFinish]; the loop then
	// executes the tools and continues.
	FinishToolCalls FinishReason = "tool-calls"
	// FinishLength means the step hit its output-token limit. If tool calls
	// were also pending they may be truncated, so the loop finishes
	// explicitly rather than executing them (loop rule 1).
	FinishLength FinishReason = "length"
	// FinishStepLimit means the loop reached its configured step limit and
	// stopped gracefully (loop rule 3).
	FinishStepLimit FinishReason = "step-limit"
	// FinishCanceled means the run's context was canceled between or during
	// steps.
	FinishCanceled FinishReason = "canceled"
	// FinishError means the run ended because of an error; the terminal
	// [StreamPart] of kind [StreamPartError] carries the cause.
	FinishError FinishReason = "error"
)

type LoopOptions

type LoopOptions struct {
	// Model is the language model to drive. Required.
	Model Model
	// System is the system prompt applied to every step. Optional.
	System string
	// Messages is the initial conversation (typically a single user
	// message). The loop appends assistant and tool messages to a copy of
	// this slice as it iterates; the caller's slice is not mutated.
	Messages []Message
	// Tools are the executable tools available to the model. A nil or empty
	// set runs the model with no tools (it can then only produce text).
	Tools ToolSet
	// StepLimit caps the number of model steps. Zero means
	// [DefaultStepLimit]. Reaching the limit ends the run with
	// [FinishStepLimit] (loop rule 3).
	StepLimit int
	// MaxOutputTokens is forwarded to the model on every step. Zero lets the
	// adapter choose its provider default.
	MaxOutputTokens int
	// Headers are extra HTTP headers attached to every model request (used
	// for gateway attribution). Optional.
	Headers map[string]string
	// OnStep, if set, is called with each step's usage immediately after the
	// step finishes and before the next step starts. It exposes per-step
	// usage for billing (loop rule 4); the aggregate is also delivered on the
	// terminal [StreamPartFinish].
	OnStep func(Usage)
	// MaxRetries is the number of additional attempts made when a step fails
	// with a RETRYABLE error (rate limit, overload, transient transport) and
	// before any output has streamed. Zero means [DefaultMaxRetries]; a
	// negative value disables retries entirely.
	MaxRetries int
	// RetryBaseDelay is the base of the exponential retry backoff. Zero means
	// [DefaultRetryBaseDelay].
	RetryBaseDelay time.Duration
	// RetryMaxDelay caps a single backoff wait (a server Retry-After is still
	// honored verbatim). Zero means [DefaultRetryMaxDelay].
	RetryMaxDelay time.Duration
}

LoopOptions configures a single run of the tool-use loop. Model and the initial Messages are required; everything else has a sensible default.

type Message

type Message struct {
	Role    Role
	Content []ContentPart
}

Message is one turn in the conversation handed to a Model. A message's Content is a heterogeneous list of parts; a user or assistant message typically holds text and/or tool calls, while a tool message holds tool results.

func UserMessage

func UserMessage(text string) Message

UserMessage is a convenience constructor for a single-text user message.

type Model

type Model interface {
	// Stream runs one inference over req and returns a reader over the
	// resulting parts. Adapters emit [StreamPartTextDelta] and
	// [StreamPartToolCall] parts as they arrive and MUST emit exactly one
	// terminal [StreamPartStepFinish] carrying the step's [Usage] and
	// [FinishReason]. The caller closes the returned [StreamReader].
	Stream(ctx context.Context, req Request) (StreamReader, error)

	// ModelID reports the concrete model identifier, recorded on usage
	// attribution by callers.
	ModelID() string
}

Model is a streaming language model. It is the single seam every provider adapter implements. Implementations MUST be safe for the loop's usage pattern (one in-flight Stream at a time per run) and MUST NOT read environment or global state — everything they need is injected at construction.

type ModelError

type ModelError struct {
	// Class is the failure bucket used for the retry decision.
	Class ErrorClass
	// RetryAfter, when > 0, is the server's requested wait before retrying
	// (from a Retry-After header). It is honored verbatim over the computed
	// backoff.
	RetryAfter time.Duration
	// Err is the underlying provider/transport error.
	Err error
}

ModelError wraps a provider failure with its ErrorClass and an optional server-supplied Retry-After hint. Adapters return one of these from a failed Stream so the loop can classify the failure without importing any provider SDK. It unwraps to the underlying cause so errors.Is/As on the original error still work.

func NewModelError

func NewModelError(class ErrorClass, retryAfter time.Duration, err error) *ModelError

NewModelError builds a ModelError. It is the constructor adapters use once they have classified an SDK error.

func (*ModelError) Error

func (e *ModelError) Error() string

func (*ModelError) Unwrap

func (e *ModelError) Unwrap() error

type Request

type Request struct {
	// System is the system prompt. Empty means no system prompt.
	System string
	// Messages is the conversation so far, oldest first.
	Messages []Message
	// Tools are the model-facing tool definitions available this step.
	Tools []ToolDefinition
	// MaxOutputTokens caps the generated tokens. Zero lets the adapter use
	// its provider default.
	MaxOutputTokens int
	// Headers are extra HTTP headers to attach to this request (used for
	// gateway attribution). Adapters that are not HTTP-based ignore them.
	Headers map[string]string
}

Request is a single model inference request. It is what a Model receives for one step of the loop; the loop rebuilds it with the growing message history on each iteration.

type Role

type Role string

Role identifies the author of a Message in the conversation sent to a Model.

const (
	// RoleUser is a message from the end user.
	RoleUser Role = "user"
	// RoleAssistant is a message produced by the model, possibly carrying
	// tool calls.
	RoleAssistant Role = "assistant"
	// RoleTool carries the results of tool executions back to the model. The
	// loop emits exactly one RoleTool message per step (all results from a
	// step are batched into it).
	RoleTool Role = "tool"
)

type SendFunc

type SendFunc func(StreamPart) bool

SendFunc delivers one StreamPart to a stream's consumer. It returns false once the consumer has closed the stream, which a producer should treat as a signal to stop and release its resources.

type StreamPart

type StreamPart struct {
	Kind StreamPartKind

	Text         string
	ToolCall     *ToolCall
	ToolResult   *ToolResult
	Usage        Usage
	TotalUsage   Usage
	FinishReason FinishReason
	Err          error
}

StreamPart is one event in a unified model/loop stream. Exactly one group of fields is meaningful, selected by Kind:

TextDelta   -> Text
ToolCall    -> ToolCall
ToolResult  -> ToolResult
StepFinish  -> Usage, FinishReason
Finish      -> FinishReason, TotalUsage
Error       -> Err, FinishReason, TotalUsage

type StreamPartKind

type StreamPartKind string

StreamPartKind discriminates the variants of StreamPart. The set is unified across every provider adapter and across the loop itself: an adapter emits the TextDelta, ToolCall, and StepFinish kinds; Run additionally emits ToolResult, Finish, and Error.

const (
	// StreamPartTextDelta is an incremental fragment of assistant text; see
	// StreamPart.Text.
	StreamPartTextDelta StreamPartKind = "text-delta"
	// StreamPartToolCall is a fully-assembled tool call the model requested;
	// see StreamPart.ToolCall.
	StreamPartToolCall StreamPartKind = "tool-call"
	// StreamPartToolResult is the result of executing a tool, emitted by the
	// loop after it runs the tool; see StreamPart.ToolResult.
	StreamPartToolResult StreamPartKind = "tool-result"
	// StreamPartStepFinish marks the end of one model step and carries that
	// step's usage and finish reason; see StreamPart.Usage and
	// StreamPart.FinishReason.
	StreamPartStepFinish StreamPartKind = "step-finish"
	// StreamPartFinish is the terminal part of a successful run; it carries
	// the overall finish reason and the aggregated usage across all steps;
	// see StreamPart.FinishReason and StreamPart.TotalUsage.
	StreamPartFinish StreamPartKind = "finish"
	// StreamPartError is the terminal part of a failed or canceled run. It
	// carries the cause (StreamPart.Err), whether the run failed or was
	// canceled (StreamPart.FinishReason, one of [FinishError] or
	// [FinishCanceled]), and the usage accumulated over the steps that
	// completed before the failure (StreamPart.TotalUsage) so that work the
	// provider already billed is not lost.
	StreamPartError StreamPartKind = "error"
)

type StreamReader

type StreamReader interface {
	// Recv returns the next part, or [io.EOF] when the stream is done. Any
	// other error is a transport-level failure surfaced by the reader.
	Recv() (StreamPart, error)
	// Close stops the stream and releases its resources. It is safe to call
	// more than once.
	Close() error
}

StreamReader is a forward-only reader over a sequence of StreamPart values. Recv returns io.EOF once the stream is exhausted. Callers MUST call Close to release resources (for HTTP-backed adapters this closes the underlying response body; for the loop it cancels the driving goroutine).

func Run

func Run(ctx context.Context, opts LoopOptions) StreamReader

Run drives opts.Model through the tool-use loop and returns a StreamReader over the unified event stream. The stream begins emitting immediately from a background goroutine; the caller consumes it with Recv until io.EOF and MUST call Close when done (Close also cancels an in-progress run).

The stream always ends with exactly one terminal part: a StreamPartFinish on success (carrying the aggregated Usage) or a StreamPartError on a model/transport failure. The loop obeys these rules:

  1. It exits as soon as a step produces no tool calls — it does not key off the raw stop reason. A step that hits its token limit with tool calls still pending finishes explicitly (FinishLength) rather than executing possibly-truncated calls.
  2. All tool results from one step are batched into a single tool message fed back to the model.
  3. It stops gracefully at StepLimit with FinishStepLimit.
  4. It aggregates per-step usage into the total, never dropping cache read/write, and reports per-step usage through OnStep.
  5. A tool that errors, or an unknown tool name, becomes an error tool result fed back to the model — never a loop abort.
  6. It emits the unified StreamPart kinds (text delta, tool call, tool result, step finish, finish, error).

func StreamFunc

func StreamFunc(produce func(send SendFunc), onClose func()) StreamReader

StreamFunc adapts a push-style producer into a pull-style StreamReader. It runs produce in a background goroutine, handing it a SendFunc to emit parts; when produce returns, the stream ends (Recv reports io.EOF).

onClose, if non-nil, is invoked exactly once when the consumer calls Close — HTTP adapters use it to close the underlying response body, and the loop uses it to cancel its context. A producer that observes SendFunc returning false should stop promptly so the goroutine does not leak.

type Tool

type Tool interface {
	Definition() ToolDefinition
	Execute(ctx context.Context, input json.RawMessage) (string, error)
}

Tool is an executable capability the model may invoke. Definition is the model-facing schema; Execute runs the tool and returns its textual output. A non-nil error from Execute is converted by the loop into an error ToolResult fed back to the model rather than aborting the run (loop rule 5).

type ToolCall

type ToolCall struct {
	ID    string
	Name  string
	Input json.RawMessage
}

ToolCall is a model's request to invoke a named tool with the given JSON input. ID is the provider-assigned identifier that correlates the call with its ToolResult.

type ToolDefinition

type ToolDefinition struct {
	Name        string
	Description string
	InputSchema json.RawMessage
}

ToolDefinition is the model-facing description of a tool: the name the model uses to call it, a natural-language description, and a JSON Schema (as raw JSON) describing the input. It carries no executable behavior — that lives on Tool.

type ToolResult

type ToolResult struct {
	ToolCallID string
	Name       string
	Content    string
	IsError    bool
}

ToolResult is the outcome of executing a ToolCall. Content is the textual payload fed back to the model; IsError marks the result as an error so the model can react (see loop rule 5). ToolCallID and Name correlate the result with the originating call.

type ToolSet

type ToolSet map[string]Tool

ToolSet is the collection of tools available to a run, keyed by tool name (the same name that appears in ToolDefinition.Name and in a ToolCall). A nil or empty ToolSet runs the model with no tools.

func (ToolSet) Definitions

func (ts ToolSet) Definitions() []ToolDefinition

Definitions returns the model-facing definitions of every tool in the set. Order is unspecified.

type Usage

type Usage struct {
	Input      int64
	Output     int64
	CacheRead  int64
	CacheWrite int64
}

Usage is a token-accounting record. It is used both for a single model step and, once aggregated, for the whole run (the loop sums per-step usage into a running total; see Run).

Field semantics are normalized across providers so that callers never have to know which model produced the numbers:

  • Input is the total number of prompt tokens billed as input, INCLUSIVE of any tokens served from the prompt cache (CacheRead) and EXCLUSIVE of tokens written to the cache (CacheWrite). This matches the convention that a cache read still counts toward input while a cache write is billed on its own axis.
  • Output is the number of generated (completion) tokens.
  • CacheRead is the subset of prompt tokens served from the cache.
  • CacheWrite is the number of tokens written to the cache on this request (Anthropic "cache creation"); providers without a cache-write concept leave it zero.

Every field is billing-critical: adapters MUST populate CacheRead and CacheWrite whenever the provider reports them, and the loop MUST NOT drop them when aggregating.

func (Usage) Add

func (u Usage) Add(other Usage) Usage

Add returns the element-wise sum of u and other. It is used by the loop to aggregate per-step usage into a run total without mutating either operand.

Directories

Path Synopsis
Package anthropic adapts the official anthropics/anthropic-sdk-go into an agentcore.Model.
Package anthropic adapts the official anthropics/anthropic-sdk-go into an agentcore.Model.
Package mcptool adapts a Model Context Protocol (MCP) server into agentcore.Tool values, using the official modelcontextprotocol/go-sdk client over the Streamable HTTP transport.
Package mcptool adapts a Model Context Protocol (MCP) server into agentcore.Tool values, using the official modelcontextprotocol/go-sdk client over the Streamable HTTP transport.
Package mockmodel provides a scriptable, in-process agentcore.Model that needs no API key and no network.
Package mockmodel provides a scriptable, in-process agentcore.Model that needs no API key and no network.
Package openaicompat adapts the official openai/openai-go v3 client, speaking the Chat Completions API, into an agentcore.Model.
Package openaicompat adapts the official openai/openai-go v3 client, speaking the Chat Completions API, into an agentcore.Model.

Jump to

Keyboard shortcuts

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