gotato

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

Gotato

Gotato is a minimal, synchronous, Go-native agent runtime.

What Gotato is

  1. A minimal, synchronous, Go-native agent runtime. One loop per agent; Prompt returns when the Run settles; context.Context carries cancellation and deadlines.
  2. A valid project on its own. A Go program gets an agent with sessions, context construction, tools, events, a CLI, and deterministic tests from Gotato alone.
  3. Embeddable in a running Go service, across instances. Runs are cancellable, state lives behind session.Store, and multi-instance safety comes from a Session lease (planned).

Every feature passes one test: would a Go service embedding an agent want this on its own? Roles, orchestration, memory systems, databases, and tool catalogs belong to the applications built on Gotato (GOALS.md §2).

Cost

An idle agent costs a few kilobytes. A working agent's memory follows its transcript, with little on top. Many agents fit in one process.

Measurement: 100 concurrent agents, 25 tool rounds each, 4 KB of unique tool output per round Value
Heap per idle agent 4–6 KB
Heap per agent after the loop (100 KB of tool output each) 216–237 KB
Process footprint with all 100 agents active (macOS vmmap) 41 MB
Gotato time per model turn, including tool dispatch under 50 µs

For scale: one Claude Code session held 320–390 MB of private memory on the same machine. These numbers exclude model latency, provider clients, and tool subprocesses. Measured on 2026-10-09 with Go 1.26 on Apple silicon. Reproduce them with:

go test -run '^$' -bench AgentFootprint -benchtime 1x .

Quick start (library)

model := testkit.EchoModel{}                      // any gotato.Model; providers live in gateway/
s := session.New()                                // what happened

agent, err := gotato.NewAgent(
    gotato.WithModel(model),
    gotato.WithInstruction("You are a helpful assistant."),
    gotato.WithTranscript(s),                     // the agent commits to the Session
    gotato.WithContextBuilder(modelctx.WithStatic(modelctx.FullHistory(), modelctx.Resource("AGENTS.md", rules))),
    gotato.WithExtension(modelctx.AutoCompact(s, modelctx.CompactPolicy{Ceiling: 60000})),
    gotato.WithToolSource(toolregistry.MustNew(tools...)),
    gotato.WithExtension(session.Record(s)),      // runs, events, usage into the Session
)
if err != nil {
    return err
}
defer agent.Close(context.Background())

result, err := agent.Prompt(ctx, gotato.UserMessage("inspect this repository"))

gotato.NewAgent(gotato.WithModel(model)) alone runs against a private in-memory transcript with full-history context.

Embedding in a service

store, err := session.NewFileStore("/var/lib/myservice/sessions") // or your own session.Store
if err != nil {
    return err
}
runner, err := service.New(service.Config{
    Store: store,
    Specs: []service.AgentSpec{{Name: "default", Model: model, Tools: tools}},
})
if err != nil {
    return err
}
mux.Handle("/agent/", http.StripPrefix("/agent", httpapi.New(runner))) // optional HTTP surface

out, err := runner.Run(ctx, service.RunRequest{Prompt: "summarize the open tickets"})

Each Run loads the Session, builds an Agent, runs it, closes it, and saves the Session. Any instance holding the Store can serve any Session.

Quick start (CLI)

go build -o bin/gotato ./cmd/gotato
bin/gotato doctor --json
id=$(bin/gotato session create --json | jq -r .id)
bin/gotato run --session "$id" --model demo --json "use-tool"
bin/gotato context inspect "$id" --json
bin/gotato serve --addr 127.0.0.1:8787        # the same runner over HTTP

Stdout is data, stderr is diagnostics, exit codes are documented: cmd/gotato/README.md.

Feature status

Area Status
Agent loop, control (Steer, FollowUp, Abort), limits, extensions done
Session, session.Store (memory, file), fork done
Context: full history, static blocks, panel, compaction, inspection done
Tool contract, Tool Registry, staged ToolSet done
Events and streaming done; typed payloads and reasoning deltas planned
Providers: OpenAI Chat Completions and Responses, Anthropic Messages done
Service: Runner, HTTP, gRPC, admission, drain done
CLI: run, session, context, tools, events, doctor, serve done
Session lease for multi-instance safety planned
Tool effect classification (ToolSpec.Effect) done
MCP through ToolSet/ToolSource planned
Provider rate-limit information planned

FEATURES.md is the authoritative inventory.

Documents

Document Role
PHILOSOPHY.md the worldview
DESIGN.md durable engineering rules
GOALS.md goals, boundaries, tradeoffs, compatibility
FEATURES.md implementation inventory with status markers
PROPOSAL.md architecture, layers, and packages
AGENTS.md instructions for coding agents working here
GITFLOW.md Git policy
MIGRATION.md breaking changes and how to move

Development

gofmt -l . && go vet ./... && go test -race ./...
(cd adapter/grpc && go test ./...)

Origin

Inspired by Pi's agent kernel (@earendil-works/pi-agent-core, created by Mario Zechner and contributors, MIT-licensed), redesigned as a Go-native runtime. Gotato is an independent design: it expresses Pi's loop semantics (Prompt/Continue, streaming, tool batches, steering and follow-up, abort, interception) through goroutines, channels, context.Context cancellation, and explicit extensions. Attribution is retained wherever derived material requires it.

License

Gotato is licensed under the Apache License 2.0.

Documentation

Overview

Package gotato is the Gotato Core: the Agent loop and the contracts it runs against. It imports only the standard library.

NewAgent builds an Agent from options. Prompt runs one Run: the Agent commits Messages to a Transcript, asks a ContextBuilder for the ModelContext of each Turn, streams the Model, executes the requested Tools, and repeats until the Model stops calling Tools or a limit, a TurnStopper, or cancellation ends the Run. Extensions hook into the stages of a Turn, and every step is published as a runtime Event.

agent, err := gotato.NewAgent(gotato.WithModel(model), gotato.WithTools(tools...))
if err != nil {
    return err
}
defer agent.Close(ctx)
result, err := agent.Prompt(ctx, gotato.UserMessage("hello"))

Sessions, context strategies, Tool registries, providers, and the service layer live in sibling packages that build on these contracts.

Index

Constants

View Source
const ActivationToolName = "activate_toolset"

ActivationToolName is the local name of the built-in Tool that activates an inactive ToolSet. It is visible only while an inactive ToolSet remains.

View Source
const PanelTag = "panel"

PanelTag wraps the dynamic panel appended to the tail of a request.

Variables

This section is empty.

Functions

func IsCode

func IsCode(err error, code ErrorCode) bool

func RenderBlocks

func RenderBlocks(blocks []Block) string

RenderBlocks renders Blocks one per line, attributes in sorted order so the output is byte-stable for identical input.

func TextOf

func TextOf(m Message) string

Types

type AdvisoryExtension

type AdvisoryExtension interface {
	Advisory() bool
}

AdvisoryExtension marks an Extension whose failure is recorded rather than settling the owning Run. Extensions are blocking by default.

type Agent

type Agent interface {
	Prompt(context.Context, Message) (RunResult, error)
	Close(context.Context) error
}

type AgentID

type AgentID string

type AgentLifecycle

type AgentLifecycle interface {
	Agent
	Status() AgentStatus
	Done() <-chan struct{}
}

type AgentStatus

type AgentStatus string
const (
	AgentCreated AgentStatus = "created"
	AgentIdle    AgentStatus = "idle"
	AgentBusy    AgentStatus = "busy"
	AgentClosing AgentStatus = "closing"
	AgentClosed  AgentStatus = "closed"
)

type Block

type Block struct {
	Tag   string            `json:"tag,omitempty"`
	Attrs map[string]string `json:"attrs,omitempty"`
	Text  string            `json:"text"`
}

Block is one tagged piece of prompt content. It renders as

<tag attr="value">text</tag>

or as bare text when Tag is empty. XML tags mark boundaries and provenance; the text inside is whatever the content is: markdown for prose, JSON for structured data.

type CacheAnchor

type CacheAnchor string

CacheAnchor names where a CacheBreakpoint sits.

const (
	// CacheAfterSystem marks the end of the system prompt.
	CacheAfterSystem CacheAnchor = "system"
	// CacheAfterTools marks the end of the tool definitions.
	CacheAfterTools CacheAnchor = "tools"
	// CacheAfterMessage marks the end of Messages[Index].
	CacheAfterMessage CacheAnchor = "message"
)

type CacheBreakpoint

type CacheBreakpoint struct {
	After CacheAnchor `json:"after"`
	Index int         `json:"index,omitempty"`
}

CacheBreakpoint is one prompt-cache hint.

type ContentKind

type ContentKind string
const (
	ContentText      ContentKind = "text"
	ContentReasoning ContentKind = "reasoning"
	ContentImage     ContentKind = "image"
	ContentJSON      ContentKind = "json"
)

type ContentPart

type ContentPart struct {
	Kind     ContentKind `json:"kind"`
	Text     string      `json:"text,omitempty"`
	Data     []byte      `json:"data,omitempty"`
	MIMEType string      `json:"mime_type,omitempty"`
	// Signature is an opaque provider artifact, for example encrypted
	// reasoning content required when replaying a stateless response API.
	// Core carries it across runtime operations but never interprets it.
	Signature []byte            `json:"signature,omitempty"`
	Metadata  map[string]string `json:"metadata,omitempty"`
}

type ContextBuilder

type ContextBuilder interface {
	Build(context.Context, ContextSnapshot) (ModelContext, error)
}

ContextBuilder decides what the Model sees now. It receives a read-only snapshot of the committed Transcript and returns the layout for this Turn. The Agent applies ContextTransformer and MessageConverter Extensions to the builder's Messages afterwards, so a builder is the primary strategy and Extensions remain post-processing.

func FullHistoryContext

func FullHistoryContext() ContextBuilder

FullHistoryContext is the default ContextBuilder: the Model sees the whole committed Transcript, append-only. History shrinks only through an explicit compaction of the Session, never by selection here, so the request prefix stays byte-stable across Turns and provider prompt caches hit.

type ContextBuilderFunc

type ContextBuilderFunc func(context.Context, ContextSnapshot) (ModelContext, error)

ContextBuilderFunc adapts a function to ContextBuilder.

func (ContextBuilderFunc) Build

Build implements ContextBuilder.

type ContextSnapshot

type ContextSnapshot struct {
	AgentID            AgentID
	RunID              RunID
	Turn               TurnNumber
	SystemInstructions string
	Messages           []Message
}

ContextSnapshot is the read-only view a ContextTransformer receives. It carries copies: an Extension cannot mutate committed history through it.

type ContextTransformer

type ContextTransformer interface {
	Transform(context.Context, ContextSnapshot) ([]Message, error)
}

ContextTransformer selects, adds, prunes, or compacts the context sent to the Model. Its output is not stored as the Core transcript.

type ControllableAgent

type ControllableAgent interface {
	Agent
	Continue(context.Context) (RunResult, error)
	Steer(Message) error
	FollowUp(Message) error
	Abort()
}

ControllableAgent adds the control operations that share the one canonical Loop. They are additive: the minimal Agent path stays two methods.

Continue runs the Loop without appending a user Message and is valid only when the transcript already ends in a Model-continuable state.

Steer and FollowUp deliver a bounded control Message consumed at a defined safe boundary. Neither interrupts the Model or Tool work in flight. Steer is consumed at the next Turn boundary; FollowUp is consumed only where the Run would otherwise settle.

Abort cancels the current Run without closing the Agent.

type CoreLimits

type CoreLimits struct {
	MaxTurns               uint32
	MaxMessages            uint32
	MaxMessageBytes        uint64
	MaxTranscriptBytes     uint64
	MaxToolCalls           uint32
	MaxToolResultBytes     uint64
	MaxToolProgressBytes   uint64
	MaxToolProgressUpdates uint32
	MaxSteerMessages       uint32
	MaxFollowUpMessages    uint32
	// MaxParallelTools bounds the workers one Turn may run at once. Zero or
	// one keeps a batch sequential.
	MaxParallelTools  uint32
	MaxActiveToolSets uint32
	RunDeadline       time.Duration
	ModelCallDeadline time.Duration
	ToolCallDeadline  time.Duration
}

func DefaultLimits

func DefaultLimits() CoreLimits

DefaultLimits returns the limits NewAgent applies when WithLimits is not used. Adjust a copy and pass it to WithLimits to override some fields.

A zero field is interpreted per field, not uniformly. Once WithLimits makes the limits explicit, a zero MaxTurns, MaxMessages, MaxMessageBytes, MaxTranscriptBytes, MaxToolCalls, or MaxToolResultBytes admits no work of that kind, and a zero MaxToolProgressBytes or MaxToolProgressUpdates suppresses Tool progress reports. MaxActiveToolSets zero admits no ToolSet activation. MaxParallelTools zero or one keeps a batch sequential, and MaxSteerMessages or MaxFollowUpMessages zero leaves that control channel unbuffered. A zero deadline disables that deadline.

type ErrorCode

type ErrorCode string
const (
	ErrInvalidArgument       ErrorCode = "invalid_argument"
	ErrInvalidState          ErrorCode = "invalid_state"
	ErrBusy                  ErrorCode = "busy"
	ErrAgentClosed           ErrorCode = "agent_closed"
	ErrAgentClosing          ErrorCode = "agent_closing"
	ErrModelFailure          ErrorCode = "model_failure"
	ErrModelProtocolFailure  ErrorCode = "model_protocol_failure"
	ErrToolResolutionFailure ErrorCode = "tool_resolution_failure"
	ErrToolArgumentFailure   ErrorCode = "tool_argument_validation_failure"
	ErrToolExecutionFailure  ErrorCode = "tool_execution_failure"
	ErrExtensionFailure      ErrorCode = "extension_failure"
	ErrLimitExceeded         ErrorCode = "limit_exceeded"
	ErrCancelled             ErrorCode = "cancelled"
	ErrDeadlineExceeded      ErrorCode = "deadline_exceeded"
	ErrInternalInvariant     ErrorCode = "internal_invariant_failure"
	ErrNotSupported          ErrorCode = "not_supported"
)

type Event

type Event struct {
	AgentID    AgentID        `json:"agent_id"`
	RunID      RunID          `json:"run_id"`
	Sequence   uint64         `json:"sequence"`
	Kind       EventKind      `json:"kind"`
	Class      EventClass     `json:"event_class"`
	Turn       TurnNumber     `json:"turn,omitempty"`
	MessageID  MessageID      `json:"message_id,omitempty"`
	ToolCallID ToolCallID     `json:"tool_call_id,omitempty"`
	Payload    map[string]any `json:"payload,omitempty"`
	Timestamp  time.Time      `json:"timestamp"`
}

type EventAgent

type EventAgent interface {
	Agent
	Subscribe(context.Context) (EventStream, error)
}

type EventClass

type EventClass string
const (
	EventProtected   EventClass = "protected"
	EventCoalescable EventClass = "coalescable"
)

type EventKind

type EventKind string
const (
	EventAgentStart          EventKind = "agent_start"
	EventTurnStart           EventKind = "turn_start"
	EventMessageStart        EventKind = "message_start"
	EventMessageUpdate       EventKind = "message_update"
	EventMessageEnd          EventKind = "message_end"
	EventToolExecutionStart  EventKind = "tool_execution_start"
	EventToolExecutionUpdate EventKind = "tool_execution_update"
	EventToolExecutionEnd    EventKind = "tool_execution_end"
	EventToolResultCommitted EventKind = "tool_result_committed"
	EventToolSetActivated    EventKind = "toolset_activated"
	EventTurnEnd             EventKind = "turn_end"
	EventAgentEnd            EventKind = "agent_end"
	// EventContextBuilt is emitted once per Turn, after the ContextBuilder
	// and context Extensions ran and before the Model request is sent.
	EventContextBuilt EventKind = "context_built"
	// EventSessionCompacted is recorded when a Session prefix was replaced by
	// a summary. It is produced by the compaction operation, not by the Loop.
	EventSessionCompacted EventKind = "session_compacted"
)

type EventObserver

type EventObserver interface {
	Observe(context.Context, Event) error
}

EventObserver receives canonical Events in production order and is awaited before Core continues. It must be local, fast, Context-aware, and bounded.

type EventSource

type EventSource interface {
	Subscribe(context.Context) (EventStream, error)
}

type EventStream

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

type IdleWaiter

type IdleWaiter interface {
	WaitForIdle(context.Context) error
}

type LifecycleAgent

type LifecycleAgent interface {
	Agent
	SubscribeLifecycle(context.Context) (LifecycleStream, error)
}

type LifecycleEvent

type LifecycleEvent struct {
	Kind      LifecycleKind `json:"kind"`
	AgentID   AgentID       `json:"agent_id"`
	Reason    string        `json:"reason,omitempty"`
	Timestamp time.Time     `json:"timestamp"`
}

type LifecycleKind

type LifecycleKind string
const (
	LifecycleAgentCreated LifecycleKind = "agent_created"
	LifecycleAgentClosing LifecycleKind = "agent_closing"
	LifecycleAgentClosed  LifecycleKind = "agent_closed"
)

type LifecycleSource

type LifecycleSource interface {
	SubscribeLifecycle(context.Context) (LifecycleStream, error)
}

type LifecycleStream

type LifecycleStream interface {
	Next(context.Context) (LifecycleEvent, error)
	Close() error
}

type Message

type Message struct {
	ID         MessageID     `json:"id,omitempty"`
	Role       Role          `json:"role"`
	Parts      []ContentPart `json:"parts,omitempty"`
	ToolCalls  []ToolCall    `json:"tool_calls,omitempty"`
	ToolResult *ToolResult   `json:"tool_result,omitempty"`
	Usage      Usage         `json:"usage,omitempty"`
	StopReason StopReason    `json:"stop_reason,omitempty"`
}

func AssistantMessage

func AssistantMessage(text string) Message

func ForModel

func ForModel(messages []Message) []Message

ForModel reduces Messages to the fields a provider needs: role, parts (without runtime metadata), tool calls, and tool results. Runtime fields (IDs, usage, stop reasons) are dropped so they can never perturb the prompt bytes between Turns.

func UserMessage

func UserMessage(text string) Message

func (Message) Clone

func (m Message) Clone() Message

type MessageConverter

type MessageConverter interface {
	Convert(context.Context, []Message) ([]Message, error)
}

MessageConverter adapts committed Messages into the shape one Model expects. Converters run in installation order and preserve roles and Tool identity unless a provider representation change is explicit.

type MessageID

type MessageID string

type Model

type Model interface {
	Stream(context.Context, ModelRequest) (ModelStream, error)
}

type ModelContext

type ModelContext struct {
	SystemInstructions string    `json:"system_instructions,omitempty"`
	System             []Block   `json:"system,omitempty"`
	Messages           []Message `json:"messages"`
	Panel              []Block   `json:"panel,omitempty"`
	// Metadata describes how the Context was built so the decision can be
	// inspected. Builders set at least "strategy", "source_messages",
	// "selected_messages", and "dropped_messages".
	Metadata map[string]string `json:"metadata,omitempty"`
}

ModelContext is what the Model sees for one Turn, laid out for prompt caching: the most static content first, the most dynamic content last.

SystemInstructions + System  →  system prompt      (stable across Runs)
tools                        →  tool definitions   (stable across Turns)
Messages                     →  history            (append-only prefix)
Panel                        →  <panel> appended to the last Message (changes every Turn)

It is a projection built from the Transcript by a ContextBuilder; nothing in it is committed back. In particular the Panel never enters the Session.

func (ModelContext) Clone

func (c ModelContext) Clone() ModelContext

Clone returns a deep copy.

func (ModelContext) RenderPanel

func (c ModelContext) RenderPanel() string

RenderPanel returns the dynamic panel as one <panel> element, or "" when there is no panel.

func (ModelContext) RenderSystem

func (c ModelContext) RenderSystem() string

RenderSystem returns the system prompt: the instructions followed by the static Blocks.

type ModelEvent

type ModelEvent struct {
	Kind              ModelEventKind `json:"kind"`
	Text              string         `json:"text,omitempty"`
	ReasoningArtifact []byte         `json:"reasoning_artifact,omitempty"`
	ToolCall          *ToolCall      `json:"tool_call,omitempty"`
	Usage             Usage          `json:"usage,omitempty"`
	StopReason        StopReason     `json:"stop_reason,omitempty"`
}

type ModelEventKind

type ModelEventKind string
const (
	ModelTextDelta      ModelEventKind = "text_delta"
	ModelReasoningDelta ModelEventKind = "reasoning_delta"
	// ModelReasoningDone carries an opaque provider reasoning artifact. Core
	// stores it with the reasoning part but never interprets it.
	ModelReasoningDone ModelEventKind = "reasoning_done"
	ModelToolCall      ModelEventKind = "tool_call"
	ModelUsage         ModelEventKind = "usage"
	ModelDone          ModelEventKind = "done"
)

type ModelOptions

type ModelOptions struct {
	Temperature      *float64 `json:"temperature,omitempty"`
	MaxTokens        uint32   `json:"max_tokens,omitempty"`
	ReasoningEffort  string   `json:"reasoning_effort,omitempty"`
	ReasoningSummary string   `json:"reasoning_summary,omitempty"`
}

ModelOptions carries per-request sampling and reasoning hints. An Agent sends the options from WithModelOptions on every model call, and gateway adapters map the fields their provider supports.

type ModelRequest

type ModelRequest struct {
	SystemInstructions string       `json:"system_instructions,omitempty"`
	Messages           []Message    `json:"messages"`
	Tools              []ToolSpec   `json:"tools,omitempty"`
	Options            ModelOptions `json:"options,omitempty"`
	// CacheBreakpoints are provider-neutral prompt-cache hints in prefix
	// order. An adapter maps them to its provider's mechanism (for example
	// Anthropic cache_control) or ignores them when the provider caches
	// prefixes automatically. They never change the prompt content.
	CacheBreakpoints []CacheBreakpoint `json:"cache_breakpoints,omitempty"`
}

func AssembleRequest

func AssembleRequest(built ModelContext, tools []ToolSpec) (ModelRequest, string)

AssembleRequest lays out one ModelRequest from a built Context and the visible Tools, in cache-friendly order, and reports the prefix hash. It is what the Agent sends; it is exported so inspection tools show exactly the same bytes.

  • Messages are reduced to what a provider needs (ForModel).
  • The panel is appended as a text part to the last Message, so it never changes the prefix and never creates a second consecutive user turn.
  • Cache breakpoints are placed after the system prompt, after the tools, and before the last Message.
  • PrefixHash covers system + tools + every Message but the last: two consecutive Turns with the same PrefixHash present an identical cacheable prefix to the provider.

type ModelStream

type ModelStream interface {
	Recv(context.Context) (ModelEvent, error)
	Close() error
}

type Option

type Option func(*agentConfig) error

func WithActiveToolSet

func WithActiveToolSet(set ToolSet) Option

WithActiveToolSet installs a ToolSet whose Tools are visible immediately.

func WithContextBuilder

func WithContextBuilder(builder ContextBuilder) Option

WithContextBuilder selects the strategy that turns the Transcript into the Model's Context for each Turn. The default is FullHistoryContext.

func WithDeadlines

func WithDeadlines(run, model, tool time.Duration) Option

WithDeadlines overrides only the Run, Model, and Tool deadlines. A zero duration disables that deadline. Other local limits retain their defaults and are not made explicit by this option.

func WithExtension

func WithExtension(extension any) Option

WithExtension installs one Extension. A value may implement several Extension interfaces; it is installed into each of them. The resulting order is immutable for the Agent lifetime.

func WithExtensions

func WithExtensions(extensions ...any) Option

WithExtensions installs several Extensions in the given order.

func WithFunc

func WithFunc[In any, Out any](name, description string, fn func(context.Context, In) (Out, error)) Option

WithFunc registers an ordinary Go function as a Tool during construction. A type the schema generator cannot describe fails NewAgent instead of failing at the first Model request.

func WithInstruction

func WithInstruction(instruction string) Option

func WithLimits

func WithLimits(limits CoreLimits) Option

func WithModel

func WithModel(model Model) Option

func WithModelOptions

func WithModelOptions(options ModelOptions) Option

WithModelOptions sets the sampling and reasoning hints the Agent sends on every model call. Provider adapters map the fields they support.

func WithRootNamespace

func WithRootNamespace(namespace string) Option

WithRootNamespace qualifies individual root Tools under one namespace. The default is an empty namespace, where a root Tool keeps its declared ID.

func WithTool

func WithTool(tool Tool) Option

func WithToolSet

func WithToolSet(set ToolSet) Option

WithToolSet installs a ToolSet that starts inactive. The Model reaches its Tools by calling the activation Tool first.

func WithToolSource

func WithToolSource(source ToolSource) Option

WithToolSource installs a dynamic Tool source.

func WithTools

func WithTools(tools ...Tool) Option

func WithTranscript

func WithTranscript(transcript Transcript) Option

WithTranscript makes the Agent commit to an external Transcript instead of a private in-memory one. Use it to run an Agent against a Session.

type PostToolUse

type PostToolUse interface {
	After(context.Context, ToolResult) (ToolResult, error)
}

PostToolUse runs in reverse installation order over executed, blocked, failed, and cancelled outcomes. It must preserve identity and Executed truth.

type PreToolDecision

type PreToolDecision struct {
	Block  bool
	Reason string
	// Result optionally replaces the blocked outcome. Core forces its
	// identity, status, and Executed truth.
	Result *ToolResult
}

PreToolDecision blocks a Tool before its executor runs. A blocked use keeps its identity and settles with Executed false.

type PreToolUse

type PreToolUse interface {
	Before(context.Context, ToolUse) (PreToolDecision, error)
}

PreToolUse runs after complete assembly, resolution, and Schema validation. Components run in installation order until one blocks or fails.

type Role

type Role string
const (
	RoleUser       Role = "user"
	RoleAssistant  Role = "assistant"
	RoleToolResult Role = "tool_result"
)

type RunCanceler

type RunCanceler interface {
	Agent
	CancelRun(context.Context, RunID) error
}

RunCanceler lets a service layer cancel an active Run without closing the Agent or discarding its Session state.

type RunID

type RunID string

type RunMetrics

type RunMetrics struct {
	ElapsedMS      int64  `json:"elapsed_ms"`
	Turns          uint32 `json:"turns"`
	ToolCalls      uint32 `json:"tool_calls"`
	TextBytes      uint64 `json:"text_bytes"`
	ReasoningBytes uint64 `json:"reasoning_bytes"`
}

type RunPreparer

type RunPreparer interface {
	PrepareRun(context.Context, Transcript) error
}

RunPreparer is an Extension stage that runs inside the Agent goroutine at the start of every Run, before the prompt is committed. It is the one safe point to rewrite the Transcript (for example to compact a Session that has grown past a token budget), because no Turn is using it yet.

type RunResult

type RunResult struct {
	RunID        RunID         `json:"run_id"`
	Status       RunStatus     `json:"status"`
	FinalMessage *Message      `json:"final_message,omitempty"`
	Usage        Usage         `json:"usage,omitempty"`
	Metrics      RunMetrics    `json:"metrics"`
	Error        *RuntimeError `json:"error,omitempty"`
}

func (RunResult) Clone

func (r RunResult) Clone() RunResult

type RunStatus

type RunStatus string
const (
	RunRunning          RunStatus = "running"
	RunCompleted        RunStatus = "completed"
	RunCanceled         RunStatus = "cancelled"
	RunDeadlineExceeded RunStatus = "deadline_exceeded"
	RunFailed           RunStatus = "failed"
)

type RuntimeAgent

type RuntimeAgent interface {
	Agent
	ControllableAgent
	EventAgent
	LifecycleAgent
	RunCanceler
	IdleWaiter
	ToolInspector
	// Transcript returns the committed history this Agent runs against.
	Transcript() Transcript
}

RuntimeAgent is the complete surface NewAgent returns. The minimal Agent stays the two-method contract an implementation must satisfy; a RuntimeAgent additionally guarantees the control, lifecycle, event, and inspection operations the service layer and the CLI rely on, so callers do not have to discover them by type assertion at run time.

func NewAgent

func NewAgent(options ...Option) (RuntimeAgent, error)

NewAgent builds the standard runtime Agent.

type RuntimeError

type RuntimeError struct {
	Code      ErrorCode         `json:"code"`
	Operation string            `json:"operation,omitempty"`
	Message   string            `json:"message"`
	Retryable bool              `json:"retryable,omitempty"`
	Details   map[string]string `json:"details,omitempty"`
	Cause     error             `json:"-"`
}

func ErrorOf

func ErrorOf(code ErrorCode, message string) *RuntimeError

ErrorOf builds a typed error for a layer outside Core. The service layer, protocol adapters, and applications classify their own failures with the same codes so a caller reads one error vocabulary across the whole boundary.

func (*RuntimeError) Error

func (e *RuntimeError) Error() string

func (*RuntimeError) Is

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

Is reports whether target is a *RuntimeError with the same Code. It lets a caller match a sentinel built with ErrorOf through errors.Is without sharing the sentinel's pointer.

func (*RuntimeError) Unwrap

func (e *RuntimeError) Unwrap() error

type StopDecision

type StopDecision struct {
	Stop   bool
	Reason string
}

StopDecision ends the Run after the current Turn. The Turn is preserved.

type StopReason

type StopReason string

StopReason is why a model call or a Turn stopped. A Message's StopReason is the Model's: StopToolCalls when it called Tools, and StopMaxTokens when the output limit cut it off, even next to Tool calls.

const (
	StopNone      StopReason = "none"
	StopEndTurn   StopReason = "end_turn"
	StopToolCalls StopReason = "tool_calls"
	StopMaxTokens StopReason = "max_tokens"
	StopCanceled  StopReason = "cancelled"
	StopError     StopReason = "error"
)

type Tool

type Tool interface {
	Spec() ToolSpec
	Execute(context.Context, ToolUse, ToolProgress) (ToolResult, error)
}

func NewFuncTool

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

NewFuncTool exposes an ordinary Go function as a Tool. The input type must be a struct; its exported fields become the JSON Schema properties Core validates before the executor runs.

Field rules follow encoding/json: the `json` tag renames a field, `-` skips it, and `omitempty` marks it optional. A pointer field is optional too. The `description` tag documents a field and the `enum` tag lists its allowed string values, comma separated.

The returned Tool never sees provider objects: Core validates the arguments, unmarshals them into In, and turns a returned error into a failed Tool Result.

func NewFuncToolWithProgress

func NewFuncToolWithProgress[In any, Out any](name, description string, fn func(context.Context, In, ToolProgress) (Out, error)) (Tool, error)

NewFuncToolWithProgress is NewFuncTool for a function that reports bounded progress while it runs.

func ToolWithEffect

func ToolWithEffect(tool Tool, effect ToolEffect) Tool

ToolWithEffect returns tool with its spec's Effect set to effect. It suits tools built by NewFuncTool and tools from sources that leave Effect unset.

type ToolCall

type ToolCall struct {
	ID        ToolCallID `json:"id"`
	ToolID    string     `json:"tool_id"`
	Arguments []byte     `json:"arguments"`
}

type ToolCallID

type ToolCallID string

type ToolEffect

type ToolEffect string

ToolEffect classifies a tool's side effects, ordered by impact: EffectRead < EffectWriteLocal < EffectWriteExternal < EffectDestructive. An application uses it to choose the tools an agent loads, or to gate a call in a PreToolUse extension.

const (
	// EffectUnspecified ranks as EffectDestructive, so filters fail closed.
	EffectUnspecified ToolEffect = ""
	// EffectRead observes state and changes nothing.
	EffectRead ToolEffect = "read"
	// EffectWriteLocal changes local, recoverable state such as files in a
	// working directory.
	EffectWriteLocal ToolEffect = "write_local"
	// EffectWriteExternal changes state outside the host, such as a remote
	// API, a message, or a push.
	EffectWriteExternal ToolEffect = "write_external"
	// EffectDestructive deletes or overwrites state that is hard to recover.
	EffectDestructive ToolEffect = "destructive"
)

func (ToolEffect) Rank

func (e ToolEffect) Rank() int

Rank orders effects by impact, from 1 for EffectRead to 4 for EffectDestructive. EffectUnspecified and unknown values rank 4.

func (ToolEffect) Valid

func (e ToolEffect) Valid() bool

Valid reports whether e is EffectUnspecified or one of the named effects.

func (ToolEffect) Within

func (e ToolEffect) Within(ceiling ToolEffect) bool

Within reports whether e is at or below ceiling.

type ToolInspector

type ToolInspector interface {
	Tools() []ToolSpec
}

ToolInspector reports the Tools an Agent currently exposes to its Model. The core Agent implements it; the result is a snapshot sorted by ID.

type ToolProgress

type ToolProgress func(string)

type ToolResult

type ToolResult struct {
	CallID    ToolCallID        `json:"call_id"`
	Status    ToolResultStatus  `json:"status"`
	Content   []ContentPart     `json:"content,omitempty"`
	Metadata  map[string]string `json:"metadata,omitempty"`
	SafeError string            `json:"safe_error,omitempty"`
	Executed  bool              `json:"executed"`
}

func (ToolResult) Clone

func (r ToolResult) Clone() ToolResult

type ToolResultStatus

type ToolResultStatus string
const (
	ToolResultOK       ToolResultStatus = "ok"
	ToolResultBlocked  ToolResultStatus = "blocked"
	ToolResultFailed   ToolResultStatus = "failed"
	ToolResultCanceled ToolResultStatus = "cancelled"
)

type ToolSet

type ToolSet interface {
	Spec() ToolSetSpec
	Tools(context.Context) ([]Tool, error)
}

ToolSet stages capability discovery: its Tools stay hidden from the Model until the ToolSet is activated. Resolution must be deterministic, and a failure must not partially register the returned collection.

type ToolSetSpec

type ToolSetSpec struct {
	Name        string            `json:"name"`
	Description string            `json:"description,omitempty"`
	Metadata    map[string]string `json:"metadata,omitempty"`
}

ToolSetSpec identifies a ToolSet. The name must be stable and unique.

type ToolSource

type ToolSource interface {
	Tools() []Tool
}

ToolSource supplies Tools whose set may change between Turns. The Agent asks every installed ToolSource for its Tools at the start of each Turn and keeps that set for the whole Turn, so a Tool the Model was shown can always be resolved when the Model calls it.

Tools returned by a ToolSource are qualified under the root namespace like Tools installed with WithTool. Package toolregistry provides a registry with register/unregister/activate/deactivate that implements ToolSource.

type ToolSpec

type ToolSpec struct {
	ID           string            `json:"id"`
	Name         string            `json:"name,omitempty"`
	Description  string            `json:"description,omitempty"`
	InputSchema  []byte            `json:"input_schema,omitempty"`
	OutputSchema []byte            `json:"output_schema,omitempty"`
	Sequential   bool              `json:"sequential,omitempty"`
	Metadata     map[string]string `json:"metadata,omitempty"`
	// Effect classifies what the tool changes. Core records it, copies it
	// into ToolUse, and leaves enforcement to the application.
	Effect ToolEffect `json:"effect,omitempty"`
}

type ToolUse

type ToolUse struct {
	RunID         RunID      `json:"run_id"`
	Turn          TurnNumber `json:"turn"`
	CallID        ToolCallID `json:"call_id"`
	QualifiedID   string     `json:"qualified_id"`
	ArgumentsJSON []byte     `json:"arguments_json"`
	SourceIndex   uint32     `json:"source_index"`
	// Effect is the called tool's ToolSpec.Effect, so a PreToolUse extension
	// can gate the call by impact.
	Effect ToolEffect `json:"effect,omitempty"`
	// Executed and Result are reserved. The Core Loop does not assign them, so
	// a Tool or PreToolUse extension always sees Executed=false and Result=nil.
	// They are kept for a future PostToolUse view; removing them changes the
	// JSON shape and is tracked in TODO.md.
	Executed bool        `json:"executed"`
	Result   *ToolResult `json:"result,omitempty"`
}

type Transcript

type Transcript interface {
	Len() int
	Messages() []Message
	Append(Message) error
}

Transcript is the committed history an Agent runs against: it is the Session's record of what happened. The Agent appends every committed Message to it and reads it back when it builds the next Context.

The Agent goroutine is the only writer while a Run is in flight. A Transcript may be shared between successive Runs and between Agents, but it must not be mutated concurrently with a Run that is using it.

Messages returns the committed Messages in order. The Agent treats the returned slice as read-only and never mutates it. Len reports the committed Message count without materializing the history, so an Agent can enforce its Message bound without copying.

type TurnNumber

type TurnNumber uint32

type TurnSnapshot

type TurnSnapshot struct {
	AgentID     AgentID
	RunID       RunID
	Turn        TurnNumber
	Assistant   Message
	ToolResults []ToolResult
	StopReason  StopReason
}

TurnSnapshot is the read-only view a TurnStopper receives after turn_end. Assistant.StopReason is why the Model stopped, StopMaxTokens for a reply the output limit cut off. StopReason is why the Turn stopped.

type TurnStopper

type TurnStopper interface {
	Stop(context.Context, TurnSnapshot) (StopDecision, error)
}

TurnStopper runs after turn_end and before continuation selection.

type Usage

type Usage struct {
	InputTokens      uint64 `json:"input_tokens,omitempty"`
	OutputTokens     uint64 `json:"output_tokens,omitempty"`
	TotalTokens      uint64 `json:"total_tokens,omitempty"`
	CacheReadTokens  uint64 `json:"cache_read_tokens,omitempty"`
	CacheWriteTokens uint64 `json:"cache_write_tokens,omitempty"`
}

Usage counts tokens for one model call or an aggregate of calls. InputTokens covers every prompt token the provider processed, cached or not; CacheReadTokens and CacheWriteTokens are the parts of InputTokens served from and written to the provider's prompt cache.

Directories

Path Synopsis
adapter
bash module
mcp module
cmd
gotato command
Command gotato is the official Gotato CLI: a thin client over the runtime packages for humans, shell automation, and coding agents.
Command gotato is the official Gotato CLI: a thin client over the runtime packages for humans, shell automation, and coding agents.
Package gateway provides provider streaming Model adapters for Gotato: the OpenAI-compatible Chat Completions and Responses APIs and the Anthropic Messages API.
Package gateway provides provider streaming Model adapters for Gotato: the OpenAI-compatible Chat Completions and Responses APIs and the Anthropic Messages API.
Package modelctx builds what the Model sees now.
Package modelctx builds what the Model sees now.
Package service turns the runtime into a service: a store of Sessions and Agents that are created for one Run and discarded afterwards.
Package service turns the runtime into a service: a store of Sessions and Agents that are created for one Run and discarded afterwards.
httpapi
Package httpapi exposes a service.Runner over HTTP.
Package httpapi exposes a service.Runner over HTTP.
Package session is the Gotato Session primitive: the record of what happened in an interaction across Turns and Runs.
Package session is the Gotato Session primitive: the record of what happened in an interaction across Turns and Runs.
Package testkit provides deterministic doubles for testing Gotato runtimes without a network provider: scripted and replayed Models, a recording FakeTool, an EventRecorder Extension, Session fixtures, and the echo/demo Models used by the CLI and the reference service.
Package testkit provides deterministic doubles for testing Gotato runtimes without a network provider: scripted and replayed Models, a recording FakeTool, an EventRecorder Extension, Session fixtures, and the echo/demo Models used by the CLI and the reference service.
Package toolregistry is the Gotato Tool Registry: it owns Tool identity, visibility, and activation for a static or dynamic Tool surface.
Package toolregistry is the Gotato Tool Registry: it owns Tool identity, visibility, and activation for a static or dynamic Tool surface.

Jump to

Keyboard shortcuts

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