agent

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 11 Imported by: 0

README

go-agent

CI Go Reference Go Report Card

A Go library for building AI agents against any inference provider through one strongly-typed, idiomatic interface — with Claude, OpenAI, and Gemini as first-class citizens and a minimal-effort path to add more.

provider := claude.NewFromEnv()

a := agent.New(
    agent.WithProvider(provider),
    agent.WithModel("claude-opus-4-8"),
    agent.WithTools(GetWeather),
)

result, err := a.Run(ctx, "What's the weather in Paris?")

The same Agent code runs unchanged against OpenAI or Gemini — swap the Provider and nothing else.

Full architecture and design rationale: docs/DESIGN.md.


Why

Most multi-provider Go SDKs either wrap every vendor's API 1:1 (so you still write provider-specific code) or flatten everything to map[string]any (so you lose type safety exactly where bugs are most expensive — tool call arguments). go-agent takes a different position:

  • One small Provider interface. Name() + Generate() — that's the entire contract a backend must satisfy. Streaming, capability declaration, and token counting are separate, optional interfaces a provider can add incrementally, mirroring io.Reader / io.ReaderAt.
  • Tools are Go structs, not JSON blobs. Tool[TIn] derives the JSON Schema from your struct's tags and hands your handler a fully-typed, already-unmarshalled TIn — no map[string]any, no manual type assertions, no schema/implementation drift.
  • A real agent loop, not just a client. Tool execution, retries with backoff, bounded iterations, human-in-the-loop approval hooks, and a unified streaming model are built in, not bolted on.
  • Pay for what you import. The root module has effectively zero third-party dependencies. Each provider adapter lives in its own subpackage, so importing provider/claude never pulls in the OpenAI or Gemini SDKs.

Install

go get github.com/prasenjit-net/go-agent

Each provider adapter is a separate subpackage — import only the ones you use:

go get github.com/prasenjit-net/go-agent/provider/claude
go get github.com/prasenjit-net/go-agent/provider/openai
go get github.com/prasenjit-net/go-agent/provider/gemini

Requires Go 1.22+.

Using go-agent with an AI coding agent

This repo ships a Skill that teaches Claude Code, OpenAI Codex, and GitHub Copilot the library's real API — tool registration, streaming, provider differences, and the pitfalls a coding agent would otherwise guess wrong from generic training data. Installing it is optional but recommended if you're building against go-agent with one of these agents.

The simplest path, one command for any of the three (requires GitHub CLI ≥2.90):

gh skill install prasenjit-net/go-agent go-agent --agent claude-code
gh skill install prasenjit-net/go-agent go-agent --agent codex
gh skill install prasenjit-net/go-agent go-agent --agent copilot

Or install the native plugin directly in each agent:

# Claude Code
/plugin marketplace add prasenjit-net/go-agent
/plugin install go-agent@go-agent

# Codex — adds this repo as a plugin source, then install via the /plugins panel
codex plugin marketplace add prasenjit-net/go-agent

# GitHub Copilot
copilot plugin marketplace add prasenjit-net/go-agent
copilot plugin install go-agent@go-agent

This is always an explicit, one-time step you take in your own agent — nothing about go get-ing the library installs it automatically.

Quickstart

package main

import (
    "context"
    "fmt"
    "log"

    agent "github.com/prasenjit-net/go-agent"
    "github.com/prasenjit-net/go-agent/provider/claude"
)

func main() {
    ctx := context.Background()
    provider := claude.NewFromEnv() // reads ANTHROPIC_API_KEY

    a := agent.New(
        agent.WithProvider(provider),
        agent.WithModel("claude-opus-4-8"),
        agent.WithSystemPrompt(agent.NewSystemPrompt().Add("You are a concise, helpful assistant.")),
        agent.WithMaxTokens(1024),
    )

    result, err := a.Run(ctx, "Explain the CAP theorem in two sentences.")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result.FinalResponse.Message.Text())
}

Runnable versions of every example below live in examples/.

Strongly-typed tools

Define a tool's input as a plain struct. json tags drive field naming; jsonschema tags drive the description/required/enum the model sees. The handler receives WeatherInput already parsed — no map, no type assertion.

type WeatherInput struct {
    City  string `json:"city" jsonschema:"required,description=City name, e.g. Paris"`
    Units string `json:"units,omitempty" jsonschema:"enum=celsius;fahrenheit"`
}

var GetWeather = agent.NewTool(
    "get_weather",
    "Get the current weather for a city. Call this when the user asks about current conditions.",
    func(ctx context.Context, in WeatherInput) (agent.ToolResult, error) {
        return agent.TextResult(fmt.Sprintf("72°F and sunny in %s", in.City)), nil
    },
)

a := agent.New(
    agent.WithProvider(provider),
    agent.WithModel("claude-opus-4-8"),
    agent.WithTools(GetWeather),
)

The Agent.Run loop executes every tool call the model makes, feeds the result back, and repeats until the model produces a final answer — bounded by agent.WithMaxIterations (default 25) so a runaway loop can't run forever.

Streaming

Agent.RunStream returns one logical event stream for the whole run, including any tool round-trips:

stream, err := a.RunStream(ctx, "Write a haiku about Go generics.")
if err != nil {
    log.Fatal(err)
}
defer stream.Close()

for {
    event, err := stream.Next(ctx)
    if errors.Is(err, io.EOF) {
        break
    }
    if err != nil {
        log.Fatal(err)
    }
    switch event.Type {
    case agent.EventTextDelta:
        fmt.Print(event.TextDelta)
    case agent.EventToolCallStart:
        fmt.Printf("\n[calling %s]\n", event.ToolCall.Name)
    }
}

Providers that don't implement native streaming still work with RunStream via a documented fallback (agent.WithStreamingFallback) — one blocking call synthesized into a single-burst stream, rather than an error.

System instructions

SystemPrompt composes static, cacheable, and per-request-dynamic sections, and translates to each provider's native mechanism (including prompt-caching hints where supported):

sp := agent.NewSystemPrompt().
    Add("You are a customer support agent for Acme Corp.").
    AddCacheable(knowledgeBaseDump). // hinted for prompt caching on providers that support it
    AddFunc(func(ctx context.Context) (string, error) {
        return "Current user: " + userFromContext(ctx).Name, nil
    })

Multi-turn conversations

Agent itself is stateless (safe to share across goroutines/requests). Session adds persisted history via a pluggable ConversationStore (in-memory by default):

session := a.NewSession("user-123")
result, err := session.Send(ctx, "My name is Alice.")
result, err = session.Send(ctx, "What's my name?") // remembers "Alice"

Persist sessions to disk with filestore instead of the default in-memory store, and bound how long a conversation can grow with agent.WithCompactor (off by default — compaction is lossy, so it's an explicit opt-in):

store, _ := filestore.New("./sessions")
a := agent.New(
    agent.WithProvider(provider),
    agent.WithConversationStore(store),
    agent.WithCompactor(agent.NewWindowCompactor(50), 100_000), // keep last 50 messages once ~100k tokens
)

Tracing

otelagent wires Agent's existing Hooks to emit OpenTelemetry spans — one per model call, one per tool call — without adding an OpenTelemetry dependency to the root module:

a := agent.New(
    agent.WithProvider(provider),
    agent.WithHooks(otelagent.NewHooks(tracer, "claude")),
)

Adding a new provider

Implementing agent.Provider requires exactly one method:

type EchoProvider struct{}

func (EchoProvider) Name() string { return "echo" }

func (EchoProvider) Generate(ctx context.Context, req *agent.Request) (*agent.Response, error) {
    last := req.Messages[len(req.Messages)-1]
    return &agent.Response{
        Message:    agent.AssistantMessage(agent.TextBlock{Text: "echo: " + last.Text()}),
        StopReason: agent.StopEndTurn,
    }, nil
}

That's a fully working agent.Provideragent.New(agent.WithProvider(EchoProvider{})) already gets the tool loop, hooks, and retries. Add Stream to unlock RunStream, and Capabilities to unlock capability-aware validation — both optional, both additive. See examples/customprovider and docs/DESIGN.md for the full recipe, including the shared conformance test suite.

Testing your own agent code

agenttest.MockProvider scripts responses with zero network calls:

mock := &agenttest.MockProvider{
    Responses: []*agent.Response{
        {Message: agent.AssistantMessage(agent.TextBlock{Text: "hello"}), StopReason: agent.StopEndTurn},
    },
}
a := agent.New(agent.WithProvider(mock), agent.WithTools(GetWeather))
result, err := a.Run(context.Background(), "hi")

Package layout

go-agent/                  package agent — core types, Agent, tools, streaming
├── schema/                JSON Schema generation (reflection-based)
├── provider/
│   ├── claude/            wraps anthropic-sdk-go
│   ├── openai/            wraps openai-go (Chat Completions API)
│   └── gemini/             wraps google.golang.org/genai
├── agenttest/             MockProvider for testing application code
├── filestore/             ConversationStore backed by one JSON file per session
├── otelagent/             OpenTelemetry tracing agent.Hooks (opt-in, separate dependency)
├── examples/
├── skills/go-agent/       coding-agent Skill content (source of truth; see AGENTS.md)
├── internal/skilltool/    syncs & drift-checks the skill against the real API
└── docs/DESIGN.md         full design document

Status

Core agent loop, tool calling, streaming, system prompts, retries, all three first-class providers (non-streaming + streaming), session compaction, and an OpenTelemetry tracing helper are implemented and tested. See docs/DESIGN.md for the phased roadmap of what's next (declarative config, multi-agent delegation).

A coding-agent Skill for Claude Code, Codex, and Copilot is built and installable (see Using go-agent with an AI coding agent above) — plan and design notes in docs/AGENT-SKILL-PLAN.md. The actual /plugin install / gh skill install flows haven't been exercised end-to-end against a live agent session yet (not something scriptable from a shell), so treat the install commands as verified-by-schema, not verified-by-use, until someone runs them for real.

Development

go build ./...
go vet ./...
go test ./... -race
gofmt -l .              # should print nothing
golangci-lint run ./... # errcheck, govet, ineffassign, staticcheck, unused

Benchmarks cover the two hot paths most likely to regress silently — the reflection-based JSON Schema generator (schema/reflect.go) and the tool dispatch/agent-loop path — and are a manual pre-release check, not a CI gate (perf on shared runners is noisy enough that a hard threshold would false-positive more often than it'd catch a real regression):

go test ./schema/... . -bench=. -benchmem -run='^$'

CI (.github/workflows/ci.yml) runs on every push/PR to main: format check, vet, build, race-enabled tests with coverage, golangci-lint, govulncheck, a cross-compile check across linux/darwin/windows × amd64/arm64, and a GoReleaser config/snapshot validation.

Releases (.github/workflows/release.yml) are manual — trigger it from the Actions tab and choose a version bump (patch / minor / major), or supply an exact version to override the bump. The workflow re-verifies build/vet/test as a gate, tags, and runs GoReleaser (config: .goreleaser.yaml) to publish a GitHub Release with an auto-generated changelog. The library needs no build step to "release" — go get github.com/prasenjit-net/go-agent@vX.Y.Z resolves directly from the git tag — so GoReleaser doesn't build or attach any binaries; it only tags and writes release notes.

License

MIT

Documentation

Index

Constants

View Source
const DefaultMaxIterations = 25

DefaultMaxIterations bounds how many model round-trips a single Run may take before it gives up with ErrMaxIterations. It exists specifically so a misbehaving tool or an unexpectedly chatty model can't loop forever; combine with a context deadline for a wall-clock bound as well.

Variables

This section is empty.

Functions

func IsRetryable

func IsRetryable(err error) bool

IsRetryable reports whether err is (or wraps) an *Error marked Retryable.

Types

type Agent

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

Agent binds a Provider, model, system prompt, and tool set into a bounded tool-use loop. Agent is safe for concurrent use by multiple goroutines once constructed (no field is mutated after New returns) — the common case of one *Agent shared across many HTTP requests works without external locking.

func New

func New(opts ...Option) *Agent

New builds an Agent from the given options. WithProvider is effectively required — an Agent with no provider returns an error from every Run call rather than panicking.

func (*Agent) NewSession

func (a *Agent) NewSession(id string) *Session

NewSession returns a Session bound to id, using a.store for persistence.

func (*Agent) Note

func (a *Agent) Note(_ context.Context, note string) (Message, error)

Note delivers an operator instruction mid-conversation via Session.Send's next call. If the provider implements SystemUpdater, the instruction uses the provider's native mechanism (preserving any cached prefix); otherwise it is queued as a synthetic reminder block prepended to the next user turn, so the same call works across every provider with best-available fidelity.

Note is a placeholder for the mid-conversation-system-message feature described in the design document (Phase 4); the current implementation covers the SystemUpdater-aware path and is expected to grow the synthetic-fallback queuing in a follow-up change.

func (*Agent) Run

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

Run sends input as a single user turn (with no prior history) and runs the tool-use loop until the model produces a final answer.

func (*Agent) RunMessages

func (a *Agent) RunMessages(ctx context.Context, msgs ...Message) (*Result, error)

RunMessages runs the tool-use loop starting from an explicit message history (e.g. a prior conversation loaded from a Session/ConversationStore).

func (*Agent) RunMessagesStream

func (a *Agent) RunMessagesStream(ctx context.Context, msgs ...Message) (EventStream, error)

RunMessagesStream is the streaming counterpart to RunMessages.

func (*Agent) RunStream

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

RunStream is the streaming counterpart to Run: it starts from a single user turn and returns one logical EventStream for the entire run, including any tool round-trips — the caller sees a single stream from first token to final answer no matter how many provider calls or tool executions happen underneath.

type Capabilities

type Capabilities struct {
	Streaming             bool
	Tools                 bool
	ParallelToolCalls     bool
	Vision                bool
	Documents             bool
	Thinking              bool
	SystemCaching         bool
	MidConversationSystem bool
	MaxContextTokens      int // 0 = unknown/unbounded
	MaxOutputTokens       int // 0 = unknown
}

Capabilities describes what a Provider supports. Zero values are always conservative ("not supported" / "unknown"), matching the Capabilities{} returned by CapabilitiesOf for a Provider that doesn't implement Capable.

func CapabilitiesOf

func CapabilitiesOf(p Provider) Capabilities

CapabilitiesOf returns p's declared capabilities, or a zero-value Capabilities{} if p does not implement Capable. Callers should always go through this function rather than a raw type assertion, so behavior stays consistent for minimal providers that skip Capable entirely.

type Capable

type Capable interface {
	Capabilities() Capabilities
}

Capable is implemented by providers that want to declare what they support, so Agent can validate configuration early — e.g. reject WithThinking() against a provider with no thinking support at construction time, instead of surfacing a confusing error from the wire on the first call.

type Compactor added in v0.1.1

type Compactor interface {
	Compact(ctx context.Context, msgs []Message) ([]Message, error)
}

Compactor reduces a conversation's message history, e.g. to stay under a token budget. Implementations are free to summarize, truncate, or drop messages; the returned slice replaces history for the upcoming turn and is what Session.Send persists afterward. Left pluggable deliberately: some providers have native server-side compaction (out of scope to reimplement here), others need a client-side summarization pass — the interface accommodates either without Session knowing which. See NewWindowCompactor for a dependency-free reference implementation.

func NewWindowCompactor added in v0.1.1

func NewWindowCompactor(maxMessages int) Compactor

NewWindowCompactor returns a Compactor that keeps only the most recent maxMessages messages, dropping the rest. It is tool-pairing-aware: if the naive cut point would keep a ToolResultBlock whose originating ToolUseBlock falls outside the window, that message (and any others like it at the front of the kept slice) is dropped too — every first-class provider rejects a tool result with no matching call in the same request, so keeping an orphaned one would break the next turn rather than merely lose context.

This is a blunt strategy: dropped messages are gone, not summarized. It needs no extra model call and no dependencies, making it a reasonable default and a template for a smarter (e.g. summarizing) Compactor.

type ContentBlock

type ContentBlock interface {
	// contains filtered or unexported methods
}

ContentBlock is a closed sum type covering every content shape a provider-agnostic Message can carry: text, images, documents, tool calls, tool results, and extended-thinking content. New concrete types are added only inside this package; application code consumes ContentBlock through a type switch.

type ConversationStore

type ConversationStore interface {
	Load(ctx context.Context, sessionID string) ([]Message, error)
	Save(ctx context.Context, sessionID string, msgs []Message) error
}

ConversationStore persists a session's message history between calls. Implement this to back Session with Redis, Postgres, a file, etc.; the zero-config default is NewInMemoryStore.

func NewInMemoryStore

func NewInMemoryStore() ConversationStore

NewInMemoryStore returns a ConversationStore backed by a process-local map. Fine for CLIs, tests, and single-process use; not shared across processes or persisted across restarts.

type DocumentBlock

type DocumentBlock struct {
	Source ImageSource
	Title  string
}

DocumentBlock carries a document (e.g. a PDF) for models that support document understanding.

type Error

type Error struct {
	Code     ErrorCode
	Provider string // the Provider.Name() that produced this error, if any
	Message  string

	// Retryable indicates whether retrying the same request may succeed.
	Retryable bool
	// RetryAfter is honored when the provider supplied an explicit delay
	// (e.g. a 429 Retry-After header); zero means "use the configured
	// RetryPolicy's computed backoff instead".
	RetryAfter time.Duration

	// Cause is the wrapped original error, including the provider SDK's own
	// error type where applicable.
	Cause error
}

Error is the unified error type returned by Provider implementations and by the Agent run loop. Use errors.As to recover one from a wrapped error.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ErrorCode

type ErrorCode string

ErrorCode is a provider-agnostic classification of what went wrong. Every first-class provider adapter maps its vendor-specific error taxonomy onto this common set so application code can write one switch regardless of which provider is behind an Agent.

const (
	ErrAuthentication    ErrorCode = "authentication_error"
	ErrPermission        ErrorCode = "permission_error"
	ErrInvalidRequest    ErrorCode = "invalid_request_error"
	ErrRateLimited       ErrorCode = "rate_limit_error"
	ErrOverloaded        ErrorCode = "overloaded_error"
	ErrContextExceeded   ErrorCode = "context_length_exceeded"
	ErrRefusal           ErrorCode = "refusal"
	ErrProviderInternal  ErrorCode = "provider_error"
	ErrMaxIterations     ErrorCode = "max_iterations_exceeded"
	ErrStreamUnsupported ErrorCode = "streaming_unsupported"
	ErrNotFound          ErrorCode = "not_found_error"
	ErrUnknown           ErrorCode = "unknown_error"
)

func CodeOf

func CodeOf(err error) ErrorCode

CodeOf returns the ErrorCode of err if it is (or wraps) an *Error, and ErrUnknown otherwise.

type Event

type Event struct {
	Type EventType

	TextDelta     string
	ThinkingDelta string

	ToolCall   *ToolCall   // set on tool_call_* and tool_result events
	ToolResult *ToolResult // set on tool_result

	Response *Response // set on message_done
	Result   *Result   // set on run_done
}

Event is one item in an EventStream. Only the fields relevant to Type are populated; the rest are zero values.

type EventStream

type EventStream interface {
	Next(ctx context.Context) (Event, error)
	Close() error
}

EventStream is returned by StreamingProvider.Stream and Agent.RunStream. Next returns io.EOF once the stream is exhausted, mirroring sql.Rows / bufio.Scanner conventions: the caller drives iteration, and ctx cancellation is checked exactly where the caller expects it.

func NewSliceStream

func NewSliceStream(events ...Event) EventStream

NewSliceStream returns an EventStream that yields events in order, then io.EOF. Exported for use by provider adapters and tests that need to hand back a ready-made stream without implementing EventStream themselves.

type EventType

type EventType string

EventType identifies the kind of a streamed Event.

const (
	EventTextDelta     EventType = "text_delta"
	EventThinkingDelta EventType = "thinking_delta"
	EventToolCallStart EventType = "tool_call_start"
	EventToolCallDelta EventType = "tool_call_delta" // streamed partial JSON input
	EventToolCallEnd   EventType = "tool_call_end"
	EventToolResult    EventType = "tool_result"  // emitted after Agent executes a tool
	EventMessageDone   EventType = "message_done" // one full assistant turn complete
	EventRunDone       EventType = "run_done"     // the whole Run/RunStream finished
)

type Hooks

type Hooks struct {
	// BeforeGenerate runs before every provider call. Returning a non-nil
	// error aborts the run without calling the provider.
	BeforeGenerate func(ctx context.Context, req *Request) error

	// AfterGenerate runs after every successful provider call.
	AfterGenerate func(ctx context.Context, resp *Response)

	// BeforeToolCall runs before a tool is invoked. Returning allow=false
	// skips the tool entirely; if override is non-nil, its ToolResult is
	// used as the tool's result (e.g. to synthesize a "denied by policy"
	// response the model can react to). If override is nil and allow is
	// false, a generic denial result is used.
	BeforeToolCall func(ctx context.Context, call ToolCall) (allow bool, override *ToolResult)

	// AfterToolCall runs after a tool has been invoked (whether it
	// succeeded, returned a model-recoverable error, or was denied by
	// BeforeToolCall).
	AfterToolCall func(ctx context.Context, call ToolCall, result ToolResult)

	// OnError runs whenever the run loop is about to return a fatal error
	// (a provider error that exhausted retries, a tool handler's Go error,
	// max iterations exceeded, etc.).
	OnError func(ctx context.Context, err error)
}

Hooks are optional callbacks the Agent run loop invokes at well-defined points, covering the practical needs of a production agent: structured logging/metrics, tracing, human-in-the-loop tool approval, and error observation. Every field is optional; a nil hook is simply skipped.

type ImageBlock

type ImageBlock struct {
	Source ImageSource
}

ImageBlock carries an image for vision-capable models.

type ImageSource

type ImageSource struct {
	Kind      SourceKind
	MediaType string // e.g. "image/png"; ignored when Kind == SourceURL
	Data      string // base64 payload, or the URL, depending on Kind
}

ImageSource describes where image or document bytes come from.

type Message

type Message struct {
	Role    Role
	Content []ContentBlock
}

Message is one turn in a conversation.

func AssistantMessage

func AssistantMessage(blocks ...ContentBlock) Message

AssistantMessage builds an assistant Message out of arbitrary content blocks. Mainly useful in tests and when hand-constructing few-shot examples in conversation history.

func UserMessage

func UserMessage(text string) Message

UserMessage builds a single-block plain-text user Message.

func UserMessageBlocks

func UserMessageBlocks(blocks ...ContentBlock) Message

UserMessageBlocks builds a user Message out of arbitrary content blocks (e.g. text plus an image).

func (Message) Text

func (m Message) Text() string

Text concatenates every TextBlock in the message, in order. Convenience for the common case of reading a plain-text response.

func (Message) ToolUses

func (m Message) ToolUses() []ToolUseBlock

ToolUses returns every ToolUseBlock in the message, in order.

type Option

type Option func(*Agent)

Option configures an Agent. Adding a new knob to Agent never breaks an existing call site, since options are applied by name, not position.

func WithCompactor added in v0.1.1

func WithCompactor(c Compactor, thresholdTokens int) Option

WithCompactor enables automatic history compaction on Session.Send: when the provider implements TokenCounter and a session's estimated token count is at or above thresholdTokens, c is invoked to shrink history before the turn runs, and the compacted history is what gets persisted. Unset by default — compaction is lossy, so it's an explicit opt-in rather than an automatic behavior change. See Compactor and NewWindowCompactor.

func WithConversationStore

func WithConversationStore(s ConversationStore) Option

WithConversationStore sets the backing store used by Agent.NewSession. Defaults to an in-memory store.

func WithHooks

func WithHooks(h Hooks) Option

WithHooks attaches observability/approval callbacks. See Hooks.

func WithMaxIterations

func WithMaxIterations(n int) Option

WithMaxIterations bounds how many model round-trips a single Run/RunStream call may take before it gives up with ErrMaxIterations. Defaults to DefaultMaxIterations. This is the hard backstop against a runaway tool loop; combine with a context deadline for a wall-clock bound as well.

func WithMaxParallelTools

func WithMaxParallelTools(n int) Option

WithMaxParallelTools bounds how many tool calls from a single model turn run concurrently. Zero (the default) means unbounded — every tool call in a turn runs at once, which is fine for typical single-digit tool-call counts but worth bounding when individual tools are resource-heavy (e.g. each spawns a subprocess).

func WithMaxTokens

func WithMaxTokens(n int) Option

WithMaxTokens sets the maximum tokens the model may generate per turn.

func WithModel

func WithModel(model string) Option

WithModel sets the model identifier passed to the provider on every request (e.g. "claude-opus-4-8", "gpt-4.1", "gemini-2.5-pro").

func WithProvider

func WithProvider(p Provider) Option

WithProvider sets the inference backend. Effectively required — an Agent built without one returns an error from every Run/RunStream call.

func WithRetryPolicy

func WithRetryPolicy(rp RetryPolicy) Option

WithRetryPolicy overrides the default retry/backoff policy applied to retryable provider errors. See RetryPolicy and IsRetryable.

func WithStreamingFallback

func WithStreamingFallback(mode StreamingFallbackMode) Option

WithStreamingFallback controls RunStream's behavior against a provider that does not implement StreamingProvider. Defaults to FallbackSingleShot.

func WithSystemPrompt

func WithSystemPrompt(sp *SystemPrompt) Option

WithSystemPrompt sets the agent's system instructions. See SystemPrompt for composing static, cacheable, and dynamic sections.

func WithThinking

func WithThinking(cfg ThinkingConfig) Option

WithThinking enables extended reasoning per cfg. Leave unset (the default) to run without extended thinking.

func WithToolChoice

func WithToolChoice(tc ToolChoice) Option

WithToolChoice controls whether/how the model must use a tool. Defaults to ToolChoiceAuto.

func WithTools

func WithTools(tools ...RegisteredTool) Option

WithTools registers the tools available to the model. Calling WithTools more than once appends to, rather than replaces, the existing tool set.

type Provider

type Provider interface {
	// Name identifies the provider, e.g. "claude", "openai", "gemini". Used
	// in error messages and observability hooks.
	Name() string

	// Generate performs a single, non-streaming inference call.
	Generate(ctx context.Context, req *Request) (*Response, error)
}

Provider is the only interface a backend must implement to be usable by Agent. Everything else in this file is additive: a minimal provider that implements just this interface already gets a working Run loop, tool execution, hooks, and retries. Streaming, capability negotiation, and token counting are unlocked by implementing the optional interfaces below as needed — see StreamingProvider, Capable, and TokenCounter.

type RegisteredTool

type RegisteredTool interface {
	Name() string
	Description() string
	Schema() *schema.Schema
	Invoke(ctx context.Context, input json.RawMessage) (ToolResult, error)
}

RegisteredTool is the type-erased interface the Agent run loop and every provider adapter operate on. Tool[TIn] implements it; so can any other type — e.g. a bridge to a tool whose schema is discovered at runtime rather than known at compile time (an MCP server, a plugin registry).

type Request

type Request struct {
	Model      string
	System     []SystemBlock
	Messages   []Message
	Tools      []RegisteredTool
	ToolChoice ToolChoice
	MaxTokens  int
	Thinking   *ThinkingConfig
	// Metadata is free-form and provider-specific; adapters may ignore keys
	// they don't understand.
	Metadata map[string]string
}

Request is the provider-agnostic shape of a single inference call. Every Provider.Generate/Stream implementation translates a Request into that vendor's wire format and back.

type Response

type Response struct {
	ID         string
	Model      string
	Message    Message // Role is always RoleAssistant
	StopReason StopReason
	Usage      Usage

	// Raw is the provider-native response object (e.g. *anthropic.Message,
	// *openai.ChatCompletion, *genai.GenerateContentResponse). It is an
	// escape hatch for provider-specific fields not yet promoted into the
	// unified model; code that reads it is coupled to that provider by
	// definition, and the core Agent run loop never reads it.
	Raw any
}

Response is the provider-agnostic shape of a single inference result.

type Result

type Result struct {
	FinalResponse *Response
	Messages      []Message
	Usage         Usage
	Iterations    int
}

Result is what Run/RunMessages return: the final response plus the full transcript (including any tool round-trips) appended during this run.

type RetryPolicy

type RetryPolicy struct {
	MaxRetries int
	BaseDelay  time.Duration
	MaxDelay   time.Duration
	Jitter     bool
}

RetryPolicy controls how Agent retries a Provider.Generate/Stream call that failed with a retryable error (see IsRetryable). Errors that are not retryable (invalid request, authentication, refusal, ...) are never retried regardless of policy.

func DefaultRetryPolicy

func DefaultRetryPolicy() RetryPolicy

DefaultRetryPolicy returns a conservative default: 2 retries, exponential backoff starting at 500ms, capped at 20s, with jitter.

type Role

type Role string

Role identifies who produced a Message.

const (
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
)

type Session

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

Session binds an Agent to a persistent conversation identified by id, loading and saving history via the Agent's ConversationStore around every Send/SendStream call. Session is not safe for concurrent Send calls on the same session ID — conversation history has an inherent sequential dependency; synchronize at the application layer (e.g. a per-session mutex or single-writer queue) if concurrent turns on one session are possible.

func (*Session) History

func (s *Session) History(ctx context.Context) ([]Message, error)

History returns the session's current stored messages.

func (*Session) ID

func (s *Session) ID() string

ID returns the session identifier.

func (*Session) Reset

func (s *Session) Reset(ctx context.Context) error

Reset clears the session's stored history.

func (*Session) Send

func (s *Session) Send(ctx context.Context, input string) (*Result, error)

Send appends input as a user turn to the session's history, runs the agent, persists the updated history (including tool round-trips), and returns the result. If a Compactor is configured (see WithCompactor), the loaded history is compacted first whenever it crosses the configured token threshold, and the compacted (not the original) history is what gets persisted.

type SourceKind

type SourceKind string

SourceKind describes how ImageBlock/DocumentBlock data is encoded.

const (
	SourceBase64 SourceKind = "base64"
	SourceURL    SourceKind = "url"
)

type StopReason

type StopReason string

StopReason explains why the model stopped generating.

const (
	StopEndTurn       StopReason = "end_turn"
	StopMaxTokens     StopReason = "max_tokens"
	StopToolUse       StopReason = "tool_use"
	StopRefusal       StopReason = "refusal"
	StopContentFilter StopReason = "content_filter"
	StopUnknown       StopReason = "unknown"
)

type StreamingFallbackMode

type StreamingFallbackMode string

StreamingFallbackMode controls Agent.RunStream's behavior against a Provider that does not implement StreamingProvider.

const (
	// FallbackSingleShot (the default) performs one blocking Generate call
	// per turn and emits its content as a single burst of events, so
	// RunStream still returns a working, if non-incremental, stream rather
	// than an error.
	FallbackSingleShot StreamingFallbackMode = "single_shot"
	// FallbackError makes RunStream return an ErrStreamUnsupported error
	// immediately against a non-streaming provider, for callers that need
	// to know streaming is unavailable rather than silently degrading.
	FallbackError StreamingFallbackMode = "error"
)

type StreamingProvider

type StreamingProvider interface {
	Provider
	Stream(ctx context.Context, req *Request) (EventStream, error)
}

StreamingProvider is implemented by providers that can stream a response incrementally. If a Provider does not implement it, Agent.RunStream still works via a documented fallback (see WithStreamingFallback).

type SystemBlock

type SystemBlock struct {
	Text string
	// Cacheable hints that a provider with prompt-caching support should
	// cache this block. Providers without caching support ignore it.
	Cacheable bool
}

SystemBlock is one section of a rendered system prompt. See SystemPrompt in system.go for the composable builder that produces these.

type SystemPrompt

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

SystemPrompt is a composable, ordered builder for system instructions. Each section is added independently (and can be unit-tested independently) and rendered fresh on every Agent.Run/RunStream call, so AddFunc/AddTemplate sections always reflect current state.

Ordering matters for providers with prompt caching: caching is a prefix match, so put static/cacheable sections first (via Add/AddCacheable) and per-request dynamic sections last (via AddFunc/AddTemplate) — that way a change in the dynamic tail never invalidates the cached prefix.

func NewSystemPrompt

func NewSystemPrompt() *SystemPrompt

NewSystemPrompt returns an empty SystemPrompt ready for chaining.

func (*SystemPrompt) Add

func (s *SystemPrompt) Add(text string) *SystemPrompt

Add appends a static, non-cacheable instruction block.

func (*SystemPrompt) AddCacheable

func (s *SystemPrompt) AddCacheable(text string) *SystemPrompt

AddCacheable appends a static instruction block hinted as cacheable — use this for large, stable content (few-shot examples, a knowledge-base dump, tool-usage policy) that doesn't change between requests. Providers without prompt-caching support simply ignore the hint.

func (*SystemPrompt) AddFunc

func (s *SystemPrompt) AddFunc(fn func(ctx context.Context) (string, error)) *SystemPrompt

AddFunc appends a block computed at render time — e.g. the current date, the authenticated user's name, feature flags. Evaluated fresh on every Run/RunStream call.

func (*SystemPrompt) AddTemplate

func (s *SystemPrompt) AddTemplate(tmpl string, data any) *SystemPrompt

AddTemplate renders a text/template with data at render time. Convenience wrapper over AddFunc for the common "instructions with placeholders" case. The template is parsed once (at first render) and cached.

func (*SystemPrompt) Render

func (s *SystemPrompt) Render(ctx context.Context) ([]SystemBlock, error)

Render evaluates every section, in order, into the final list of SystemBlock a Request carries.

type SystemUpdater

type SystemUpdater interface {
	// SystemUpdateMessage returns the Message to append to history in order
	// to deliver note using this provider's native mechanism.
	SystemUpdateMessage(note string) (Message, error)
}

SystemUpdater is implemented by providers that support injecting an operator instruction mid-conversation without rebuilding the system prompt (and, on providers with prompt caching, without invalidating the cached prefix). Agent.Note uses it when available and falls back to a synthetic reminder block otherwise, so the same call works everywhere.

type TextBlock

type TextBlock struct {
	Text string
}

TextBlock is plain text content, the most common block type.

type ThinkingBlock

type ThinkingBlock struct {
	Text      string
	Signature string
}

ThinkingBlock carries extended-reasoning content produced by the model. Signature is opaque and provider-specific; when a provider requires it echoed back unmodified on a later turn, the Agent run loop does so automatically and application code never needs to inspect it.

type ThinkingConfig

type ThinkingConfig struct {
	Mode   ThinkingMode
	Budget int // consulted only when Mode == ThinkingBudgeted
}

ThinkingConfig configures extended/deep reasoning for a request. Providers that don't support thinking simply ignore it; providers whose only mode is adaptive treat ThinkingBudgeted as ThinkingAdaptive.

type ThinkingMode

type ThinkingMode string

ThinkingMode selects how a model should use extended reasoning.

const (
	// ThinkingOff disables extended thinking (the default when Thinking is nil).
	ThinkingOff ThinkingMode = "off"
	// ThinkingAdaptive lets the provider decide when and how much to think.
	ThinkingAdaptive ThinkingMode = "adaptive"
	// ThinkingBudgeted requests a fixed thinking-token budget, for providers
	// that only support the legacy fixed-budget form.
	ThinkingBudgeted ThinkingMode = "budgeted"
)

type TokenCounter

type TokenCounter interface {
	CountTokens(ctx context.Context, req *Request) (int, error)
}

TokenCounter is implemented by providers with a token-counting endpoint. Agent uses it, when present, for pre-flight context-window checks and cost-estimation helpers; it is never required.

type Tool

type Tool[TIn any] struct {
	// contains filtered or unexported fields
}

Tool[TIn] is the primary, strongly-typed way to define a tool. TIn is a plain Go struct describing the tool's input; its JSON Schema is derived once via reflection (see the schema package) from `json` and `jsonschema` struct tags and cached. The handler receives a fully-typed, already unmarshalled TIn — there is no map[string]any and no manual type assertion anywhere in a tool's implementation.

func NewTool

func NewTool[TIn any](name, description string, handler func(ctx context.Context, in TIn) (ToolResult, error)) *Tool[TIn]

NewTool defines a tool named name, described by description (which the model uses to decide when to call it — be specific about *when*, not just what the tool does), backed by handler.

func (*Tool[TIn]) Description

func (t *Tool[TIn]) Description() string

func (*Tool[TIn]) Invoke

func (t *Tool[TIn]) Invoke(ctx context.Context, raw json.RawMessage) (ToolResult, error)

Invoke unmarshals raw into TIn and calls the handler. Malformed input (the model sent something that doesn't match the schema) never panics and never returns a Go error — it becomes a model-recoverable error ToolResult, since a Go error return is reserved for handler-level failures that should abort the run (see Hooks and the Agent run loop).

func (*Tool[TIn]) Name

func (t *Tool[TIn]) Name() string

func (*Tool[TIn]) Schema

func (t *Tool[TIn]) Schema() *schema.Schema

Schema returns the JSON Schema for TIn, computed once via reflection and cached for the lifetime of the Tool.

type ToolCall

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

ToolCall describes a single tool invocation, passed to Hooks.

type ToolChoice

type ToolChoice struct {
	Mode ToolChoiceMode
	Name string // required when Mode == ToolChoiceOne
}

ToolChoice controls tool-use behavior for a single request.

type ToolChoiceMode

type ToolChoiceMode string

ToolChoiceMode controls whether/how the model must use tools.

const (
	// ToolChoiceAuto lets the model decide whether to use a tool (default).
	ToolChoiceAuto ToolChoiceMode = "auto"
	// ToolChoiceAny forces the model to use some tool, any tool.
	ToolChoiceAny ToolChoiceMode = "any"
	// ToolChoiceOne forces the model to use the tool named by ToolChoice.Name.
	ToolChoiceOne ToolChoiceMode = "tool"
	// ToolChoiceNone disables tool use for this request even if tools are
	// attached (useful for a final "summarize" turn).
	ToolChoiceNone ToolChoiceMode = "none"
)

type ToolResult

type ToolResult struct {
	Content []ContentBlock
	IsError bool
}

ToolResult is what a tool handler returns. It becomes a ToolResultBlock in the next request sent to the model.

func ErrorResultf

func ErrorResultf(format string, args ...any) ToolResult

ErrorResultf builds an error ToolResult with a formatted message. This is a model-recoverable error (the model sees it and can try something else, or explain the failure to the user) — distinct from a Go error returned from a tool handler, which the Agent run loop treats as fatal.

func JSONResult

func JSONResult(v any) ToolResult

JSONResult marshals v and returns it as a successful ToolResult. If marshaling fails, an error ToolResult is returned instead (never a Go error — a tool that can't format its own output should be recoverable by the model, not fatal to the run).

func TextResult

func TextResult(text string) ToolResult

TextResult builds a successful, plain-text ToolResult.

type ToolResultBlock

type ToolResultBlock struct {
	ToolUseID string
	Content   []ContentBlock
	IsError   bool
}

ToolResultBlock carries a tool's output back to the model. It is sent in a user-role Message, addressed to a specific ToolUseBlock by ID.

type ToolSet

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

ToolSet is a small ordered collection of RegisteredTool, primarily useful when an application assembles its tool list from more than a couple of literals (e.g. built up conditionally, or shared across multiple agents).

func NewToolSet

func NewToolSet(tools ...RegisteredTool) *ToolSet

NewToolSet builds a ToolSet from the given tools, in order. Duplicate names overwrite earlier entries but keep their original position.

func (*ToolSet) Add

func (ts *ToolSet) Add(t RegisteredTool) *ToolSet

Add registers t, replacing any existing tool with the same name.

func (*ToolSet) Get

func (ts *ToolSet) Get(name string) (RegisteredTool, bool)

Get returns the tool registered under name, if any.

func (*ToolSet) List

func (ts *ToolSet) List() []RegisteredTool

List returns every registered tool, in registration order.

type ToolUseBlock

type ToolUseBlock struct {
	ID    string
	Name  string
	Input []byte // raw JSON object, as sent by the model
}

ToolUseBlock is emitted by the assistant when it wants a tool invoked.

type Usage

type Usage struct {
	InputTokens         int
	OutputTokens        int
	CacheReadTokens     int
	CacheCreationTokens int
}

Usage reports token accounting for a single Generate/Stream call.

func (*Usage) Add

func (u *Usage) Add(u2 Usage) *Usage

Add accumulates u2 into u in place and returns u for chaining.

Directories

Path Synopsis
Package agenttest provides a scriptable agent.Provider for testing application code built on go-agent — agent wiring, tool registration, hooks, and run-loop behavior — without any network calls or API cost.
Package agenttest provides a scriptable agent.Provider for testing application code built on go-agent — agent wiring, tool registration, hooks, and run-loop behavior — without any network calls or API cost.
examples
customprovider command
Command customprovider shows the minimum needed to bring up a new inference backend: implement agent.Provider's single required method, Generate.
Command customprovider shows the minimum needed to bring up a new inference backend: implement agent.Provider's single required method, Generate.
multiprovider command
Command multiprovider shows that Agent code is identical regardless of which first-class provider backs it — only construction differs.
Command multiprovider shows that Agent code is identical regardless of which first-class provider backs it — only construction differs.
quickstart command
Command quickstart is the smallest possible go-agent program: construct a provider, build an Agent, and run a single turn.
Command quickstart is the smallest possible go-agent program: construct a provider, build an Agent, and run a single turn.
streaming command
Command streaming shows Agent.RunStream: a single logical event stream for a whole run, including any tool round-trips.
Command streaming shows Agent.RunStream: a single logical event stream for a whole run, including any tool round-trips.
tools command
Command tools demonstrates strongly-typed tool registration: the model's tool input is unmarshalled directly into a plain Go struct — no map[string]any, no manual JSON Schema.
Command tools demonstrates strongly-typed tool registration: the model's tool input is unmarshalled directly into a plain Go struct — no map[string]any, no manual JSON Schema.
Package filestore implements agent.ConversationStore backed by one JSON file per session on local disk.
Package filestore implements agent.ConversationStore backed by one JSON file per session on local disk.
internal
providererr
Package providererr holds the HTTP-status-to-agent.ErrorCode mapping shared verbatim by every first-class provider adapter's errors.go.
Package providererr holds the HTTP-status-to-agent.ErrorCode mapping shared verbatim by every first-class provider adapter's errors.go.
providererrtest
Package providererrtest holds assertions shared by every first-class provider's errors_test.go.
Package providererrtest holds assertions shared by every first-class provider's errors_test.go.
skilltool command
Command skilltool keeps the go-agent Skill in sync across the vendor directories that need a physical copy of it (Claude Code, Codex — see docs/AGENT-SKILL-PLAN.md) and checks the skill's Go code fences for drift against the real module.
Command skilltool keeps the go-agent Skill in sync across the vendor directories that need a physical copy of it (Claude Code, Codex — see docs/AGENT-SKILL-PLAN.md) and checks the skill's Go code fences for drift against the real module.
Package otelagent provides an OpenTelemetry tracing agent.Hooks implementation.
Package otelagent provides an OpenTelemetry tracing agent.Hooks implementation.
provider
claude
Package claude adapts the official Anthropic Go SDK (github.com/anthropics/anthropic-sdk-go) to the agent.Provider interface family, giving it first-class status alongside the openai and gemini adapters: Generate, Stream, CountTokens, and Capabilities are all implemented.
Package claude adapts the official Anthropic Go SDK (github.com/anthropics/anthropic-sdk-go) to the agent.Provider interface family, giving it first-class status alongside the openai and gemini adapters: Generate, Stream, CountTokens, and Capabilities are all implemented.
gemini
Package gemini adapts the official Google GenAI Go SDK (google.golang.org/genai) to the agent.Provider interface family, giving it first-class status alongside the claude and openai adapters.
Package gemini adapts the official Google GenAI Go SDK (google.golang.org/genai) to the agent.Provider interface family, giving it first-class status alongside the claude and openai adapters.
openai
Package openai adapts the official OpenAI Go SDK (github.com/openai/openai-go/v3) to the agent.Provider interface family, giving it first-class status alongside the claude and gemini adapters.
Package openai adapts the official OpenAI Go SDK (github.com/openai/openai-go/v3) to the agent.Provider interface family, giving it first-class status alongside the claude and gemini adapters.
Package schema implements the small JSON Schema subset used to describe tool inputs to Claude, OpenAI, and Gemini.
Package schema implements the small JSON Schema subset used to describe tool inputs to Claude, OpenAI, and Gemini.

Jump to

Keyboard shortcuts

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