agentkit

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Sep 19, 2026 License: MIT Imports: 28 Imported by: 0

README

agentkit

Go Reference CI

Website: https://richardwooding.github.io/agentkit/

An agent framework for Go on top of llmkit: tools typed as Go structs, a budgeted tool-calling loop that streams events, conversation memory, and multi-agent composition. Any model llmkit can reach, by name.

type weatherArgs struct {
	City string `json:"city" jsonschema:"city name"`
}

weather := agentkit.Func("weather", "Current weather for a city",
	func(ctx context.Context, in weatherArgs) (string, error) {
		return lookup(in.City), nil
	})

agent, err := agentkit.New("claude-sonnet-4-5",
	agentkit.WithInstructions("You are a concise assistant."),
	agentkit.WithTools(weather),
	agentkit.WithBudget(agentkit.Budget{MaxSteps: 8, Timeout: time.Minute}),
)
res, err := agent.Run(ctx, "What's the weather in Cape Town?")
fmt.Println(res.Output, res.Usage.TotalTokens)

Pure Go 1.27, no cgo. The root module depends on llmkit and github.com/google/jsonschema-go only. MCP support lives in a nested module so the MCP SDK stays opt-in.

Why

  • Tools are Go functions. Func turns func(ctx, In) (Out, error) into a tool: the schema comes from In (json and jsonschema:"description" tags, omitempty means optional), arguments are validated against it before your code runs, and Out is marshalled for the model.
  • The loop is bounded and observable. Steps, tokens, tool calls, handoffs and wall time are budgets; every model call, tool call, retry and compaction fires a hook or a stream event. Errors always come with the partial Result.
  • Capabilities are honest. Tool call/result pairs are never orphaned, even when a budget or cancellation stops a step half way, so transcripts stay valid for every provider.
  • Composition over framework. Agents are immutable and concurrency-safe. Sub-agents are tools, handoffs swap the active agent mid-transcript, Map fans work out.

Install

go get github.com/richardwooding/agentkit
go get github.com/richardwooding/agentkit/mcp   # optional, MCP servers as tools

Usage

Streaming
for e, err := range agent.Stream(ctx, "Plan my week") {
	if err != nil {
		return err // e.Result holds the partial transcript
	}
	switch e.Kind {
	case agentkit.EventText:
		fmt.Print(e.Text)
	case agentkit.EventToolCall:
		fmt.Printf("\n→ %s %s\n", e.ToolCall.Name, e.ToolCall.Arguments)
	case agentkit.EventFinish:
		fmt.Printf("\n%s in %d steps\n", e.Result.StopReason, e.Result.Steps)
	}
}

The model is streamed when the provider supports it; otherwise one text event carries the whole reply. Breaking out of the loop cancels the run. Events always arrive on the goroutine that ranges over the stream, even with WithParallel. EventUsage follows every model call with that call's own token usage (a context meter needs Usage.InputTokens); tools can report interim output with Progress(ctx, text) or ProgressWriter(ctx), which surface as EventToolProgress without touching the transcript.

Typed output
type Forecast struct {
	City  string `json:"city"`
	TempC int    `json:"temp_c"`
}
forecast, res, err := agentkit.Run[Forecast](ctx, agent, "Forecast for Cape Town?")

Agents without tools get a JSON-schema response format. Agents with tools get a synthetic final_answer tool instead, because most providers reject a response format alongside tool definitions. If the model stops without answering it is nudged once, then ErrNoFinalAnswer is returned.

Middleware and approval
agent, _ := agentkit.New("gpt-5",
	agentkit.WithTools(deleteFile, sendMail),
	agentkit.WithMiddleware(
		agentkit.Recover(),
		agentkit.Timeout(30*time.Second),
		agentkit.ApproveWith(agentkit.ApproverFunc(func(ctx context.Context, c agentkit.Call) (agentkit.Decision, error) {
			switch c.Call.Name {
			case "delete_file":
				return askUser(ctx, c) // Allow(), AllowWith(rewrittenArgs) or Deny(reason)
			default:
				return agentkit.Allow(), nil
			}
		})),
	),
	agentkit.WithParallel(4),
)

ApproveWith may block for as long as a person takes to answer: in a streaming run an EventApprovalRequest precedes the Approver and an EventApprovalResult carries its Decision, so a UI can show the pending call and answer it asynchronously. A denial is fed back to the model as an error result wrapping ErrApprovalDenied and the run continues; canceling ctx while approval is pending stops the run as StopCancelled. Rewritten arguments reach the tool but the transcript keeps what the model wrote. Approve(fn) is the error-only shorthand. Wrap a tool in Serial() to keep it out of parallel batches.

Steering a running agent
inbox := agentkit.NewInbox()
go func() { inbox.Post(core.Text("also run the tests")) }()
res, err := agent.Run(ctx, "fix the bug", agentkit.WithInbox(inbox))

Messages posted while the run executes are appended at the next step boundary (after the current tool results, before the next model call); if the model has already answered, the run continues with them instead of finishing. Cancel ctx to interrupt instead.

Switching models on a session
fast, err := agent.With(agentkit.WithName("fast")) // same options, replayed; middleware wraps once
client := agent.Client()                            // the underlying llmkit Chatter
Memory
store := agentkit.NewFileStore("./sessions")
agent, _ := agentkit.New("gemini-2.5-pro",
	agentkit.WithStore(store),
	agentkit.WithCompactor(agentkit.Summarize(summariser, 6)),
	agentkit.WithContextWindow(120_000),
	agentkit.WithRetriever(memory, 4), // adds a "recall" tool
)
res, _ := agent.Run(ctx, "Where did we leave off?", agentkit.WithSession("richard"))

Stores are lossless. FileStore is append-only JSON Lines (one {"t":…,"m":…} per message, fsync'd, torn tails tolerated; older single-array files are migrated on first write), and both stores implement Lister (ListSessionInfo{ID, Created, Updated, Messages, Title}) for a session picker. Compaction only shapes what the model sees, whole turns at a time, and runs proactively near the window or reactively when a provider reports the context is too long; Summarize(c, 0) replays nothing but the summary and StripReasoning() drops reasoning blocks before replaying a transcript to another model. A tool that implements Pinned keeps its results visible: once compaction drops the turn holding one, the content is re-sent in the system prompt of the outgoing request, fenced as data. VectorMemory builds on any llmkit Embedder (and optionally a Reranker).

Multi-agent
researcher, _ := agentkit.New("gpt-5", agentkit.WithName("researcher"), ...)
billing, _ := agentkit.New("claude-sonnet-4-5", agentkit.WithName("billing"), ...)

front, _ := agentkit.New("gpt-5-mini",
	agentkit.WithTools(
		agentkit.AsTool(researcher, "research", "Research a topic in depth"),
		agentkit.Handoff(billing),
	),
)

AsTool runs the child with its own budget and returns only its final text; its usage is added to the parent. With agentkit.WithForwardEvents() the child's events appear in the parent's stream too, carrying the child's RunID, Depth and Parent (the enclosing run ID). Handoff switches instructions, tools and model while carrying the transcript. Map runs any function over inputs with bounded concurrency.

MCP servers as tools
import agentmcp "github.com/richardwooding/agentkit/mcp"

fs, err := agentmcp.Connect(ctx, agentmcp.Command("npx", "-y", "@modelcontextprotocol/server-filesystem", "."),
	agentmcp.WithPrefix("fs_"))
defer fs.Close()
tools, err := fs.Tools(ctx)
agent, _ := agentkit.New("gpt-5", agentkit.WithTools(tools...))

Each tool implements agentmcp.Annotated, exposing the server's readOnlyHint, destructiveHint and friends for a consent UI. They are the server's own claims; never use them to skip approval.

Skills
import "github.com/richardwooding/agentkit/skills"

set, err := skills.LoadAll(os.DirFS(".agents/skills")) // any fs.FS: os.DirFS, embed.FS, ...
agent, _ := agentkit.New("claude-sonnet-4-5",
	agentkit.WithInstructions("You are a helpful assistant."),
	skills.Use(set), // catalog in the system prompt + "skill" and "skill_file" tools
)

Agent Skills are folders with a SKILL.md. Use lists their names and descriptions in the system prompt; the model activates one by calling skill and gets the full body (pinned, so it survives compaction), then reads bundled files with skill_file. Merge gives project skills precedence over user skills; Set.Problems reports what was skipped or loaded with reservations. Running a skill's scripts is left to your own tools.

Prompt caching
agentkit.WithCache(core.CacheConfig{System: true, Turns: 1})

Providers with explicit prompt caching (Anthropic) get cache_control breakpoints after the instructions and tool definitions and on the trailing user turn, so the stable prefix is served from cache on every step. Other providers ignore the hint. Usage reports CachedInputTokens and CacheWriteTokens.

Observability
agentkit.WithHooks(slogx.Hooks(slog.Default(), slogx.WithLevel(slog.LevelDebug)))

Hooks is a struct of optional functions; slogx is one implementation. Tool hooks may fire from worker goroutines.

Errors

ErrBudgetExceeded (with *BudgetError naming the limit), ErrNoFinalAnswer, ErrToolNotFound, ErrApprovalDenied, ErrHandoffLoop, ErrDuplicateTool, ErrInvalidArgs. Provider errors come through unchanged from llmkit; rate limits and 5xx responses are retried with jitter, honouring Retry-After.

Examples

examples/tools, examples/typed and examples/multiagent run offline against an in-process fake provider: go run ./examples/tools.

What this is not

  • Not a prompt library or a planner. Instructions are a string; planning is the model's job or yours.
  • Not a workflow engine. Runs are single processes with a context.Context; persist the Result yourself if you need durability across restarts.
  • Not tied to one vendor. Everything that talks to a model goes through llmkit's Chatter/Streamer interfaces, so fakes drop in for tests.

Changelog

See CHANGELOG.md.

License

MIT © 2026 Richard Wooding

Documentation

Overview

Package agentkit runs LLM agents on top of llmkit: typed tools generated from Go structs, a budgeted tool-calling loop with streaming events and hooks, conversation memory, and multi-agent composition.

weather := agentkit.Func("weather", "Current weather for a city",
	func(ctx context.Context, in struct {
		City string `json:"city" jsonschema:"city name"`
	}) (string, error) {
		return lookup(in.City), nil
	})
agent, err := agentkit.New("claude-sonnet-4-5",
	agentkit.WithInstructions("You are a concise assistant."),
	agentkit.WithTools(weather))
res, err := agent.Run(ctx, "What's the weather in Cape Town?")
fmt.Println(res.Output)

Index

Constants

View Source
const FinalAnswerTool = "final_answer"

FinalAnswerTool is the name of the tool Run[T] injects in OutputTool mode.

View Source
const HandoffToolName = "handoff"

HandoffToolName is the name of the tool Handoff builds.

View Source
const RecallToolName = "recall"

RecallToolName is the name of the tool Recall builds.

Variables

View Source
var (
	ErrBudgetExceeded = errors.New("agentkit: budget exceeded")
	ErrNoFinalAnswer  = errors.New("agentkit: model finished without final answer")
	ErrToolNotFound   = errors.New("agentkit: tool not found")
	ErrApprovalDenied = errors.New("agentkit: tool call not approved")
	ErrHandoffLoop    = errors.New("agentkit: handoff limit reached")
	ErrDuplicateTool  = errors.New("agentkit: duplicate tool name")
	ErrInvalidArgs    = errors.New("agentkit: tool arguments failed schema validation")
)

Sentinel errors. Match them with errors.Is; a partial *Result accompanies every run error.

View Source
var DefaultRetry = RetryPolicy{MaxAttempts: 3, BaseDelay: 500 * time.Millisecond, MaxDelay: 30 * time.Second, Jitter: 0.2}

DefaultRetry is the policy agents use unless WithRetry overrides it.

Functions

func Map

func Map[In, Out any](ctx context.Context, parallel int, items []In, fn func(context.Context, In) (Out, error)) ([]Out, error)

Map runs fn over items with bounded concurrency and returns results in input order. Errors are joined; on cancellation unrun items stay zero and the joined error includes ctx.Err().

func Progress added in v0.3.0

func Progress(ctx context.Context, text string)

Progress reports interim output from inside a tool call as an EventToolProgress carrying the current ToolCall. It never touches the transcript and is a no-op outside a streaming run.

func ProgressWriter added in v0.3.0

func ProgressWriter(ctx context.Context) io.Writer

ProgressWriter returns a Writer whose every Write becomes one Progress call, for streaming command output; it discards outside a streaming run.

func SchemaFor

func SchemaFor[T any](opts ...FuncOption) (json.RawMessage, error)

SchemaFor derives the JSON Schema Func would use for T.

func WithCall

func WithCall(ctx context.Context, c Call) context.Context

WithCall attaches Call metadata to ctx, for invoking tools outside a run.

Types

type Agent

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

Agent is an immutable configuration: a model, instructions, tools and limits. One Agent serves any number of concurrent runs.

func New

func New(model string, opts ...Option) (*Agent, error)

New resolves model through llmkit and builds an Agent.

func NewFromClient

func NewFromClient(c core.Chatter, opts ...Option) (*Agent, error)

NewFromClient builds an Agent around an existing client (or a fake).

func (*Agent) Client added in v0.3.0

func (a *Agent) Client() core.Chatter

Client returns the llmkit client (or fake) the agent talks to.

func (*Agent) Name

func (a *Agent) Name() string

Name returns the agent's name (the model name unless WithName was used).

func (*Agent) Run

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

Run sends input as a user turn and drives the tool loop to completion.

func (*Agent) RunMessages

func (a *Agent) RunMessages(ctx context.Context, msgs []core.Message, opts ...RunOption) (*Result, error)

RunMessages is Run with caller-built messages appended to the session.

func (*Agent) Stream

func (a *Agent) Stream(ctx context.Context, input string, opts ...RunOption) iter.Seq2[Event, error]

Stream is Run yielding events as they happen. The final event is EventFinish.

func (*Agent) StreamMessages

func (a *Agent) StreamMessages(ctx context.Context, msgs []core.Message, opts ...RunOption) iter.Seq2[Event, error]

StreamMessages is Stream with caller-built messages.

func (*Agent) Tools

func (a *Agent) Tools() Toolset

Tools returns the agent's tools after middleware has been applied.

func (*Agent) With added in v0.3.0

func (a *Agent) With(opts ...Option) (*Agent, error)

With returns a new Agent built like a — the same model or client, the original options — with opts applied afterwards, so tools are wrapped by their middleware exactly once. a is unchanged.

type Approver added in v0.3.0

type Approver interface {
	Approve(ctx context.Context, c Call) (Decision, error)
}

Approver decides whether a tool call may run. Approve may block for as long as it needs (a person may be asked); return ctx.Err() when ctx ends. It is called from a tool worker goroutine when WithParallel is above one.

type ApproverFunc added in v0.3.0

type ApproverFunc func(ctx context.Context, c Call) (Decision, error)

ApproverFunc adapts a function to Approver.

func (ApproverFunc) Approve added in v0.3.0

func (f ApproverFunc) Approve(ctx context.Context, c Call) (Decision, error)

Approve implements Approver.

type AsToolOption

type AsToolOption func(*subAgentTool)

AsToolOption configures AsTool.

func WithForwardEvents

func WithForwardEvents() AsToolOption

WithForwardEvents surfaces the child's events in the parent's stream. Every child event, its EventFinish included, is forwarded with the child's own RunID and Depth and with Parent set to the enclosing run's ID. Without the option, or when the parent is not streaming, the child runs silently.

type Budget

type Budget struct {
	MaxSteps     int           // model calls; default 10
	MaxTokens    int           // cumulative Usage.TotalTokens; 0 = unlimited
	MaxToolCalls int           // 0 = unlimited
	MaxHandoffs  int           // default 5
	Timeout      time.Duration // 0 = none
}

Budget bounds a run. Zero values take the documented defaults.

type BudgetError

type BudgetError struct {
	Limit string
	Used  int
	Max   int
}

BudgetError reports which Budget limit stopped a run.

func (*BudgetError) Error

func (e *BudgetError) Error() string

Error formats the exhausted limit.

func (*BudgetError) Unwrap

func (e *BudgetError) Unwrap() error

Unwrap lets errors.Is match ErrBudgetExceeded.

type Call

type Call struct {
	RunID   string
	Agent   string
	Session string
	Step    int
	Depth   int
	Call    core.ToolCall
}

Call is the per-invocation context a tool can read via CallFrom.

func CallFrom

func CallFrom(ctx context.Context) (Call, bool)

CallFrom returns the Call metadata the loop attached to ctx.

type CharEstimator

type CharEstimator struct {
	CharsPerToken int
	PerMessage    int
	PerBinaryPart int
}

CharEstimator divides text length by CharsPerToken and charges flat costs per message and per binary part. Zero fields take the defaults 4, 4, 800.

func (CharEstimator) Estimate

func (e CharEstimator) Estimate(msgs []core.Message) int

Estimate implements Estimator.

type CompactInfo

type CompactInfo struct {
	RunID  string
	Agent  string
	Reason string
	Before int
	After  int
}

CompactInfo describes a history compaction; Reason is "proactive" or "context_length".

type Compactor

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

Compactor shrinks the history sent to the model to roughly budgetTokens. Implementations must keep tool calls with their results.

func Chain

func Chain(cs ...Compactor) Compactor

Chain applies compactors in order.

func StripReasoning added in v0.3.0

func StripReasoning() Compactor

StripReasoning drops reasoning parts from every message and any message left empty by that. Use it in a Chain when a transcript must be replayed to a model that rejects another model's reasoning blocks (for example after switching models on a session); text and tool calls are untouched.

func Summarize

func Summarize(c core.Chatter, keepTurns int, opts ...SummarizeOption) Compactor

Summarize replaces turns older than keepTurns with one model-written summary, inserted as a user message after the system prompt. keepTurns 0 replays nothing: the model sees only the system prompt and the summary. Summaries are memoised per dropped prefix so repeated runs in one process do not pay twice.

func Window

func Window(keepTurns int) Compactor

Window keeps the system messages and the last keepTurns turns, dropping further whole turns while the estimate exceeds the budget.

type CompactorFunc

type CompactorFunc func(ctx context.Context, msgs []core.Message, budgetTokens int) ([]core.Message, error)

CompactorFunc adapts a function to Compactor.

func (CompactorFunc) Compact

func (f CompactorFunc) Compact(ctx context.Context, msgs []core.Message, budget int) ([]core.Message, error)

Compact implements Compactor.

type Decision added in v0.3.0

type Decision struct {
	Allow     bool
	Arguments json.RawMessage
	Reason    string
}

Decision is an Approver's verdict on one tool call. Arguments, when set, replace the arguments handed to the tool; the assistant message in the transcript keeps what the model wrote.

func Allow added in v0.3.0

func Allow() Decision

Allow approves the call as the model made it.

func AllowWith added in v0.3.0

func AllowWith(args json.RawMessage) Decision

AllowWith approves the call with rewritten arguments.

func Deny added in v0.3.0

func Deny(reason string) Decision

Deny refuses the call; reason is fed back to the model.

type Deleter

type Deleter interface {
	Delete(ctx context.Context, sessionID string) error
}

Deleter is implemented by stores that can forget a session.

type Estimator

type Estimator interface {
	Estimate(msgs []core.Message) int
}

Estimator approximates the prompt tokens a message list will cost.

type Event

type Event struct {
	Kind   EventKind
	RunID  string
	Agent  string
	Step   int
	Depth  int
	Parent string

	Text         string         // EventText, EventReasoning, EventToolProgress
	ToolCall     *core.ToolCall // tool events and approval events
	ToolResult   *core.ToolResult
	Duration     time.Duration // EventToolResult, EventUsage
	Err          error         // EventRetry, EventToolResult (the tool's Go error)
	Usage        *core.Usage   // EventUsage: this model call only
	FinishReason core.FinishReason
	Decision     *Decision // EventApprovalResult
	Attempt      int
	Delay        time.Duration
	Before       int
	After        int
	Handoff      *HandoffInfo
	Result       *Result
}

Event is one item of Agent.Stream. Events are always delivered on the goroutine that ranges over the stream, whatever WithParallel is set to. Exactly one EventFinish for the run itself ends a stream and carries the Result; on failure it is paired with the error. Events forwarded from a sub-agent (AsTool with WithForwardEvents) carry the child's RunID, a Depth greater than the run's own and Parent set to the enclosing run's ID; they include the child's own EventFinish.

type EventKind

type EventKind uint8

EventKind discriminates streamed run events.

const (
	EventText EventKind = iota + 1
	EventReasoning
	EventStep
	EventToolCall
	EventToolResult
	EventRetry
	EventCompact
	EventHandoff
	EventFinish
	EventUsage
	EventToolProgress
	EventApprovalRequest
	EventApprovalResult
)

Event kinds.

func (EventKind) String

func (k EventKind) String() string

String returns the kind's name.

type FileStore

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

FileStore keeps one append-only JSON Lines file per session under a directory: each line is {"t":"<RFC3339Nano>","m":<Message>} and Append writes with O_APPEND and fsync, so an interrupted write costs at most the line being written, which Load drops. Sessions written by earlier versions as one JSON array (<id>.json) are still readable and are migrated to the line format on their first Append. The directory is created on first Append.

func NewFileStore

func NewFileStore(dir string) *FileStore

NewFileStore returns a FileStore rooted at dir; no I/O happens here.

func (*FileStore) Append

func (s *FileStore) Append(_ context.Context, sessionID string, msgs ...core.Message) error

Append adds msgs to the session file.

func (*FileStore) Delete

func (s *FileStore) Delete(_ context.Context, sessionID string) error

Delete removes the session file (and any legacy file).

func (*FileStore) List added in v0.3.0

func (s *FileStore) List(_ context.Context) ([]SessionInfo, error)

List implements Lister by scanning the directory; a missing directory is an empty store. Created comes from the first record (or the file's mtime for legacy files), Updated from the mtime.

func (*FileStore) Load

func (s *FileStore) Load(_ context.Context, sessionID string) ([]core.Message, error)

Load reads the session file; a missing file is an empty session.

type FuncOption

type FuncOption func(*funcConfig)

FuncOption tunes schema generation for Func and NewFunc.

func WithEnum

func WithEnum[T ~string](values ...T) FuncOption

WithEnum constrains every field of type T to the given values.

func WithForOptions

func WithForOptions(o *jsonschema.ForOptions) FuncOption

WithForOptions passes options through to jsonschema.For.

func WithSchema

func WithSchema(params json.RawMessage) FuncOption

WithSchema supplies the full parameter schema instead of deriving it.

func WithStrict

func WithStrict() FuncOption

WithStrict marks the tool for OpenAI strict mode. Every property must then be required, so avoid omitempty fields.

func WithoutValidation

func WithoutValidation() FuncOption

WithoutValidation skips schema validation of incoming arguments.

type HandoffInfo

type HandoffInfo struct {
	RunID  string
	From   string
	To     string
	Reason string
}

HandoffInfo describes control passing to another agent.

type Hooks

type Hooks struct {
	OnRunStart      func(RunInfo)
	OnStep          func(StepInfo)
	OnModelCall     func(ModelCallInfo)
	OnModelResponse func(ModelResponseInfo)
	OnToolCall      func(ToolCallInfo)
	OnToolResult    func(ToolResultInfo)
	OnRetry         func(RetryInfo)
	OnCompact       func(CompactInfo)
	OnHandoff       func(HandoffInfo)
	OnRunEnd        func(*Result, error)
}

Hooks receive callbacks during a run. Every field is optional. Tool hooks may fire from worker goroutines when WithParallel is above one.

func (Hooks) Join

func (h Hooks) Join(o Hooks) Hooks

Join returns hooks that call h then o.

type Inbox added in v0.3.0

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

Inbox queues user messages for a run that is already in progress. The loop drains it at step boundaries: before every model call and, when the model stops with plain text, instead of finishing, so a message posted while tools run lands after their results. Drained messages are appended to the transcript and the session store like any user turn. Safe for concurrent use; one Inbox may serve several runs but each message goes to one of them.

func NewInbox added in v0.3.0

func NewInbox() *Inbox

NewInbox returns an empty Inbox.

func (*Inbox) Len added in v0.3.0

func (b *Inbox) Len() int

Len reports how many messages are waiting.

func (*Inbox) Post added in v0.3.0

func (b *Inbox) Post(parts ...core.Part)

Post queues a user message built from parts; no parts is a no-op.

func (*Inbox) PostMessage added in v0.3.0

func (b *Inbox) PostMessage(m core.Message)

PostMessage queues a caller-built message.

type Lister added in v0.3.0

type Lister interface {
	List(ctx context.Context) ([]SessionInfo, error)
}

Lister is implemented by stores that can enumerate their sessions, most recently updated first.

type MemoryStore

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

MemoryStore keeps sessions in process memory.

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore returns an empty MemoryStore.

func (*MemoryStore) Append

func (s *MemoryStore) Append(_ context.Context, sessionID string, msgs ...core.Message) error

Append adds messages to the session.

func (*MemoryStore) Delete

func (s *MemoryStore) Delete(_ context.Context, sessionID string) error

Delete forgets the session.

func (*MemoryStore) List added in v0.3.0

func (s *MemoryStore) List(_ context.Context) ([]SessionInfo, error)

List implements Lister.

func (*MemoryStore) Load

func (s *MemoryStore) Load(_ context.Context, sessionID string) ([]core.Message, error)

Load returns a copy of the session's messages.

type Middleware

type Middleware func(Tool) Tool

Middleware wraps a Tool. Wrap applies the first middleware outermost.

func Approve

func Approve(fn func(ctx context.Context, c Call) error) Middleware

Approve gates each call; a non-nil error from fn is fed back to the model as an error result wrapping ErrApprovalDenied. Cancel ctx inside fn to abort the run instead. It is ApproveWith over an Approver that denies on error.

func ApproveWith added in v0.3.0

func ApproveWith(ap Approver) Middleware

ApproveWith gates each call through ap. In a streaming run an EventApprovalRequest precedes the Approver and an EventApprovalResult carries its Decision, so a UI can show the pending call and answer it asynchronously. A denial (or an Approver error) becomes an error result wrapping ErrApprovalDenied and the run continues; when ctx ends while approval is pending, ctx.Err() is returned so the run stops as canceled. Rewritten arguments reach the tool but are not written back into the transcript.

func Observe

func Observe(fn func(c Call, r Output, err error, d time.Duration)) Middleware

Observe reports every call's outcome and duration.

func Recover

func Recover() Middleware

Recover turns a panic inside the tool into an error result.

func Serial

func Serial() Middleware

Serial marks the tool as never running concurrently with other tool calls.

func Timeout

func Timeout(d time.Duration) Middleware

Timeout bounds each call of the tool.

type ModelCallInfo

type ModelCallInfo struct {
	RunID   string
	Agent   string
	Step    int
	Attempt int
	Request *core.Request
}

ModelCallInfo describes a request about to be sent; treat Request as read-only.

type ModelResponseInfo

type ModelResponseInfo struct {
	RunID    string
	Agent    string
	Step     int
	Response *core.Response
	Duration time.Duration
	Err      error
}

ModelResponseInfo describes a completed model call.

type Option

type Option func(*Agent) error

Option configures an Agent.

func WithAdditionalInstructions added in v0.2.0

func WithAdditionalInstructions(sections ...string) Option

WithAdditionalInstructions appends sections to the system prompt after the text set by WithInstructions, whatever the option order. Empty sections are dropped; sections are separated by a blank line.

func WithBudget

func WithBudget(b Budget) Option

WithBudget bounds each run.

func WithCache added in v0.3.0

func WithCache(c core.CacheConfig) Option

WithCache asks providers with explicit prompt caching to place cache breakpoints on every request; see core.CacheConfig. {System: true, Turns: 1} keeps the instructions and tool definitions cached across a whole session while the trailing turn moves.

func WithClientOptions

func WithClientOptions(opts ...core.Option) Option

WithClientOptions passes options to the llmkit client (New only).

func WithCompactor

func WithCompactor(c Compactor) Option

WithCompactor shapes the history sent to the model when it grows too large.

func WithContextWindow

func WithContextWindow(tokens int) Option

WithContextWindow enables proactive compaction when the estimated prompt exceeds 90% of tokens.

func WithEstimator

func WithEstimator(e Estimator) Option

WithEstimator replaces the token estimator used for proactive compaction.

func WithHooks

func WithHooks(h Hooks) Option

WithHooks adds observers; multiple calls are joined.

func WithInstructions

func WithInstructions(s string) Option

WithInstructions sets the system prompt.

func WithMaxTokens

func WithMaxTokens(n int) Option

WithMaxTokens caps output tokens per model call.

func WithMiddleware

func WithMiddleware(mws ...Middleware) Option

WithMiddleware wraps every tool of the agent, including Recall and sub-agents.

func WithName

func WithName(name string) Option

WithName sets the agent's name, used in events, hooks and handoffs.

func WithParallel

func WithParallel(n int) Option

WithParallel allows up to n tool calls of one step to run concurrently.

func WithProviderOptions

func WithProviderOptions(po map[string]map[string]any) Option

WithProviderOptions merges provider-keyed raw fields into every request.

func WithReasoning

func WithReasoning(r core.ReasoningConfig) Option

WithReasoning enables extended thinking where supported.

func WithRegistry

func WithRegistry(r *llmkit.Registry) Option

WithRegistry resolves the model against a specific llmkit registry (New only).

func WithRequestExtra

func WithRequestExtra(extra map[string]any) Option

WithRequestExtra merges raw fields into every request body.

func WithRetriever

func WithRetriever(r Retriever, k int) Option

WithRetriever adds a "recall" tool returning the k best matches.

func WithRetry

func WithRetry(p RetryPolicy) Option

WithRetry sets the model-call retry policy.

func WithStore

func WithStore(s Store) Option

WithStore persists conversations for runs that pass WithSession.

func WithTemperature

func WithTemperature(t float64) Option

WithTemperature sets the sampling temperature.

func WithToolChoice

func WithToolChoice(tc core.ToolChoice) Option

WithToolChoice sets the request tool choice.

func WithTools

func WithTools(tools ...Tool) Option

WithTools adds tools; names must be unique across the agent.

type Output

type Output struct {
	Content []core.Part
	IsError bool
	// contains filtered or unexported fields
}

Output is what a tool hands back to the model. IsError marks a tool-level failure the model should see; a Go error from Call means the same and is also reported to Hooks.

func Errorf

func Errorf(format string, args ...any) Output

Errorf builds an error Output the model will see.

func JSON

func JSON(v any) (Output, error)

JSON builds a Output whose text is v encoded as JSON.

func Text

func Text(s string) Output

Text builds a Output with one text part.

func (Output) Text

func (r Output) Text() string

Text returns the concatenated text parts of the result.

type OutputMode

type OutputMode uint8

OutputMode selects how Run[T] obtains structured output.

const (
	OutputAuto   OutputMode = iota // schema for tool-less agents, tool otherwise
	OutputSchema                   // provider json_schema response format
	OutputTool                     // synthetic final_answer tool
)

Output modes.

type Pinned added in v0.2.0

type Pinned interface {
	Pinned() bool
}

Pinned is implemented by tools whose successful results must stay visible to the model after compaction. When a compactor drops the turn holding such a result, the loop re-sends its content inside the system prompt of every later request; the transcript and the session store are not changed. Pinned content can never be compacted away, so keep it small.

type Result

type Result struct {
	RunID        string
	Agent        string
	Output       string
	Message      core.Message
	Messages     []core.Message
	New          []core.Message
	Usage        core.Usage
	Steps        int
	ToolCalls    int
	Handoffs     int
	FinishReason core.FinishReason
	StopReason   StopReason
	Duration     time.Duration
}

Result is the outcome of a run. On error it is partial but always present.

func Run

func Run[T any](ctx context.Context, a *Agent, input string, opts ...RunOption) (T, *Result, error)

Run drives the agent and decodes its final answer into T. Agents with tools receive a final_answer tool; tool-less agents get a JSON-schema response format, falling back to the tool when the provider lacks it.

func RunMessages

func RunMessages[T any](ctx context.Context, a *Agent, msgs []core.Message, opts ...RunOption) (T, *Result, error)

RunMessages is Run[T] with caller-built messages.

type Retriever

type Retriever interface {
	Retrieve(ctx context.Context, query string, k int) ([]string, error)
}

Retriever returns the k texts most relevant to a query.

type RetryInfo

type RetryInfo struct {
	RunID   string
	Agent   string
	Step    int
	Attempt int
	Delay   time.Duration
	Err     error
}

RetryInfo describes a model-call retry.

type RetryPolicy

type RetryPolicy struct {
	MaxAttempts int           // total attempts; default 3, 1 disables retries
	BaseDelay   time.Duration // default 500ms
	MaxDelay    time.Duration // default 30s
	Jitter      float64       // fraction of the delay, default 0.2
}

RetryPolicy governs retries of model calls. Tools are never retried.

func (RetryPolicy) Delay

func (p RetryPolicy) Delay(attempt int, err error) time.Duration

Delay returns how long to wait before the given 1-based retry attempt, honoring a Retry-After hint when the error carries one.

func (RetryPolicy) Retryable

func (p RetryPolicy) Retryable(err error) bool

Retryable reports whether err is transient: rate limits, 5xx and transport failures. Context errors, other 4xx and ErrContextLength are not.

type RunInfo

type RunInfo struct {
	RunID   string
	Agent   string
	Session string
	Depth   int
}

RunInfo describes a starting run.

type RunOption

type RunOption func(*runConfig)

RunOption configures one run.

func WithInbox added in v0.3.0

func WithInbox(b *Inbox) RunOption

WithInbox lets messages be posted into the run while it executes.

func WithOutputMode

func WithOutputMode(m OutputMode) RunOption

WithOutputMode overrides how Run[T] obtains structured output.

func WithParts

func WithParts(parts ...core.Part) RunOption

WithParts adds parts (images, files) to the user turn built from input.

func WithRunHooks

func WithRunHooks(h Hooks) RunOption

WithRunHooks adds observers for this run only.

func WithSession

func WithSession(id string) RunOption

WithSession loads history from the agent's Store before the run and appends the new messages afterwards.

type Sequential

type Sequential interface {
	Sequential() bool
}

Sequential is implemented by tools that must not run concurrently with other tool calls from the same step. Serial adds it; MCP tools may declare it.

type SessionInfo added in v0.3.0

type SessionInfo struct {
	ID       string
	Created  time.Time
	Updated  time.Time
	Messages int
	Title    string // first line of the first user message, at most 80 runes
}

SessionInfo summarizes a stored session without loading it.

type StepInfo

type StepInfo struct {
	RunID string
	Agent string
	Step  int
}

StepInfo describes the start of one model call.

type StopReason

type StopReason string

StopReason says why a run ended.

const (
	StopCompleted    StopReason = "completed"
	StopFinalAnswer  StopReason = "final_answer"
	StopMaxSteps     StopReason = "max_steps"
	StopMaxTokens    StopReason = "max_tokens"
	StopMaxToolCalls StopReason = "max_tool_calls"
	StopDeadline     StopReason = "deadline"
	StopCancelled    StopReason = "canceled"
	StopError        StopReason = "error"
)

Stop reasons.

type Store

type Store interface {
	Load(ctx context.Context, sessionID string) ([]core.Message, error)
	Append(ctx context.Context, sessionID string, msgs ...core.Message) error
}

Store persists conversations by session ID. Load returns nil for an unknown session. Stores hold the lossless transcript; compaction never touches them.

type SummarizeOption

type SummarizeOption func(*summarizer)

SummarizeOption configures Summarize.

func WithSummaryEstimator

func WithSummaryEstimator(e Estimator) SummarizeOption

WithSummaryEstimator replaces the estimator used to decide how much to drop.

func WithSummaryPrompt

func WithSummaryPrompt(prompt string) SummarizeOption

WithSummaryPrompt replaces the instruction sent to the summarizing model.

type Tool

type Tool interface {
	Definition() core.Tool
	Call(ctx context.Context, args json.RawMessage) (Output, error)
}

Tool is anything the model may call.

func AsTool

func AsTool(a *Agent, name, description string, opts ...AsToolOption) Tool

AsTool exposes an agent as a tool taking {"input": string}. The child runs with its own budget, one level deeper, and only its final text comes back; its token usage is added to the parent's.

func Func

func Func[In, Out any](name, description string, fn func(context.Context, In) (Out, error), opts ...FuncOption) Tool

Func builds a Tool whose arguments are decoded into In and whose result is Out. The schema is derived from In; arguments are validated against it before decoding. Func panics when In cannot be represented; use NewFunc for a recoverable error.

func Handoff

func Handoff(targets ...*Agent) Tool

Handoff builds a tool that transfers the conversation to one of the target agents: the system prompt, tools and model switch while the transcript is carried over. Give targets their own Handoff tool to allow handing back.

func NewFunc

func NewFunc[In, Out any](name, description string, fn func(context.Context, In) (Out, error), opts ...FuncOption) (Tool, error)

NewFunc is Func returning an error instead of panicking.

func Raw

func Raw(name, description string, params json.RawMessage, fn func(context.Context, json.RawMessage) (Output, error)) Tool

Raw builds a Tool from a hand-written JSON Schema.

func Recall

func Recall(r Retriever, k int) Tool

Recall builds a tool that lets the model search a Retriever.

func Rename

func Rename(t Tool, name string) Tool

Rename returns t exposed under a different name.

func Wrap

func Wrap(t Tool, mws ...Middleware) Tool

Wrap applies mws to t; nil middleware is skipped.

type ToolCallInfo

type ToolCallInfo struct {
	Call Call
}

ToolCallInfo describes a tool about to run.

type ToolResultInfo

type ToolResultInfo struct {
	Call     Call
	Output   Output
	Err      error
	Duration time.Duration
}

ToolResultInfo describes a finished tool call.

type Toolset

type Toolset []Tool

Toolset is an ordered list of tools.

func Merge

func Merge(sets ...Toolset) (Toolset, error)

Merge concatenates toolsets, failing on duplicate names.

func (Toolset) Definitions

func (ts Toolset) Definitions() []core.Tool

Definitions returns the core.Tool definitions in order.

func (Toolset) Lookup

func (ts Toolset) Lookup(name string) (Tool, bool)

Lookup finds a tool by name.

func (Toolset) Names

func (ts Toolset) Names() []string

Names lists tool names in order.

func (Toolset) Prefix

func (ts Toolset) Prefix(p string) Toolset

Prefix returns a Toolset whose tool names carry the given prefix.

type VectorMemory

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

VectorMemory is an in-memory store of texts searched by cosine similarity.

func NewVectorMemory

func NewVectorMemory(e core.Embedder, opts ...VectorOption) *VectorMemory

NewVectorMemory builds an empty memory over the embedder.

func (*VectorMemory) Add

func (m *VectorMemory) Add(ctx context.Context, texts ...string) error

Add embeds and stores texts.

func (*VectorMemory) Len

func (m *VectorMemory) Len() int

Len returns the number of stored texts.

func (*VectorMemory) Retrieve

func (m *VectorMemory) Retrieve(ctx context.Context, query string, k int) ([]string, error)

Retrieve returns up to k texts, best first.

type VectorOption

type VectorOption func(*VectorMemory)

VectorOption configures VectorMemory.

func WithDimensions

func WithDimensions(n int) VectorOption

WithDimensions requests a fixed embedding size from the embedder.

func WithReranker

func WithReranker(r core.Reranker) VectorOption

WithReranker reorders the top candidates with a cross-encoder.

Directories

Path Synopsis
examples
internal/fake
Package fake registers an in-process OpenAI-compatible provider so the examples run offline.
Package fake registers an in-process OpenAI-compatible provider so the examples run offline.
multiagent command
Command multiagent shows a sub-agent exposed as a tool and a parallel Map.
Command multiagent shows a sub-agent exposed as a tool and a parallel Map.
skills command
Command skills shows Agent Skills loaded from an embedded filesystem.
Command skills shows Agent Skills loaded from an embedded filesystem.
tools command
Command tools shows a typed tool driven by the agent loop.
Command tools shows a typed tool driven by the agent loop.
typed command
Command typed shows Run[T]: the agent must return a value matching a Go struct.
Command typed shows Run[T]: the agent must return a value matching a Go struct.
internal
atomicfile
Package atomicfile writes files via a temporary sibling and rename.
Package atomicfile writes files via a temporary sibling and rename.
pool
Package pool runs functions over a slice with bounded concurrency, preserving input order in the results.
Package pool runs functions over a slice with bounded concurrency, preserving input order in the results.
mcp module
Package skills loads Agent Skills (https://agentskills.io) and exposes them to an agent with progressive disclosure: the catalog of names and descriptions goes into the system prompt, the full SKILL.md body is returned by the "skill" tool when the model activates a skill, and bundled files are read on demand through the "skill_file" tool.
Package skills loads Agent Skills (https://agentskills.io) and exposes them to an agent with progressive disclosure: the catalog of names and descriptions goes into the system prompt, the full SKILL.md body is returned by the "skill" tool when the model activates a skill, and bundled files are read on demand through the "skill_file" tool.
Package slogx adapts agentkit.Hooks to log/slog.
Package slogx adapts agentkit.Hooks to log/slog.

Jump to

Keyboard shortcuts

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