agent

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 4, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package agent is the engine that turns one awake ant into work: the explicit state-machine loop that drives model turns, schedules tool calls, compacts the context when the window fills, and recovers from provider errors (doc 03).

The loop is one for-loop over one State struct, and every retry, compaction, recovery, and fallback is a named transition that reassigns the state and continues the loop, never a recursion (D6). Given the same model responses and the same tool outputs, the same transitions fire in the same order, so a recorded session replays byte-identically for the eval harness (D23).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AntBody

type AntBody struct {
	Text     string              `json:"text,omitempty"`
	Thinking string              `json:"thinking,omitempty"`
	Calls    []provider.ToolCall `json:"calls,omitempty"`
}

AntBody is the session body of one assistant message.

type CompactBody

type CompactBody struct {
	Trigger    string `json:"trigger"`
	PreTokens  int    `json:"pre_tokens"`
	Summarized int    `json:"summarized"`
}

CompactBody is the session body of a compaction boundary (D9).

type DecideFunc

type DecideFunc func(ctx context.Context, t tool.Tool, input json.RawMessage, callID string) Verdict

DecideFunc is the permission pipeline seam. Nil means every call is allowed, which only the tests use; the ant always wires the pipeline.

type HookResult

type HookResult struct {
	Block   bool   // a hook blocked (exit 2)
	Message string // block or warning text, fed to the model on a block
	Context string // additional context to append to the result
}

HookResult is the loop-facing outcome of a hook fire.

type Hooks

type Hooks interface {
	// PostTool runs the post-tool hooks after the tool returns. A blocked
	// result feeds Message back to the model; otherwise Context is appended to
	// the tool result. isErr routes to the post-tool-failure event.
	PostTool(ctx context.Context, tool string, input json.RawMessage, result string, isErr bool) HookResult
	// PromptSubmit runs UserPromptSubmit as a new human prompt enters the run.
	// A block rejects the prompt; Context is injected before the model runs.
	PromptSubmit(ctx context.Context, prompt string) HookResult
	// SessionStart runs once at the start of a session and again after a
	// compaction; reason is "startup" or "compact". Context is injected.
	SessionStart(ctx context.Context, reason string) HookResult
	// SessionEnd runs when the run reaches its terminal reason. It cannot
	// change the outcome; it is for cleanup and notification side effects.
	SessionEnd(ctx context.Context, reason string)
	// Stop runs when the model finishes with no tool calls. A block asks the
	// loop to keep working; the loop bounds the number of blocks so a Stop
	// hook that always blocks cannot spin the loop.
	Stop(ctx context.Context) HookResult
}

Hooks is the loop's seam to the lifecycle and post-tool hooks. The ant wires it to the trusted hook dispatcher; nil means no hooks, which is the case for an untrusted workspace and for tests. The loop stays free of the hook package: it knows only these calls and the small result they return.

PreToolUse does not live here: it steers the permission decision and so runs inside the pipeline seam (DecideFunc), whose Verdict carries any context a PreToolUse hook contributed.

type Limits

type Limits struct {
	// MaxTurns is the model-turn ceiling. Zero means the foreground
	// default of 100.
	MaxTurns int

	// MaxToolConcurrency bounds a parallel batch. Zero means 10, the
	// Claude Code default (doc 03 section 7).
	MaxToolConcurrency int

	// Window is the model's context size in tokens. Zero means 200000.
	Window int

	// MaxOut is the output-token cap sent with each request. Zero lets
	// the provider default stand.
	MaxOut int

	// KeepToolResults is how many recent tool results rung one of the
	// compaction ladder preserves. Zero means 5 (doc 03 section 18
	// leaves the exact count to tuning; this is the seam).
	KeepToolResults int

	// AutoCompactAt and BlockingAt override the derived thresholds when
	// nonzero, so a test triggers compaction without a huge transcript.
	AutoCompactAt int
	BlockingAt    int

	// Sleep is the retry backoff sleeper. Nil means time.Sleep; tests
	// inject a recorder.
	Sleep func(time.Duration)
}

Limits is the per-run configuration: ceilings, budgets, and the injected clock and sleep for deterministic tests.

type Loop

type Loop struct {
	Provider provider.Provider
	Model    string // resolved model id
	Fallback string // fallback model id, "" means none
	System   []provider.Block

	// Prefix is block two of the cache-aligned prompt (D14): the pinned
	// index and project memory as one synthetic system-reminder user
	// message, prepended before the task tail on every request. The ant
	// builds it once per session so it stays byte-stable across turns;
	// its last block carries the second cache breakpoint.
	Prefix []provider.Message

	Tools  *tool.Registry
	TC     *tool.ToolContext
	Decide DecideFunc

	// Hooks is the tool-adjacent hook seam. Nil means no hooks, which is the
	// case for an untrusted workspace and for tests; the ant wires it to the
	// trusted dispatcher only when a workspace has hooks that pass the trust
	// gate (doc 05 section 12).
	Hooks Hooks

	// Emit publishes one event on the stream; the colony's TurnHandle
	// satisfies it. Nil drops events, for isolated transition tests.
	Emit func(t event.Type, payload any) error

	// Append writes one transcript entry. Nil drops them.
	Append func(e session.Entry) error

	// Record accounts one completed model turn. Nil skips the meter.
	Record func(r ledger.Row)

	// OverBudget is the between-turn budget gate (D5). Nil means no
	// budget. Checked after each ledger flush, never mid-turn.
	OverBudget func() bool

	Session string
	Turn    string // the user turn id events are stamped with
	Tier    string // for the ledger row
	Limits  Limits

	// Now is injected for deterministic tests. Nil means time.Now.
	Now func() time.Time
	// contains filtered or unexported fields
}

Loop drives one ant through model turns to a terminal reason. The fields are the run's fixed dependencies; everything that changes across iterations lives in State.

func (*Loop) Run

func (l *Loop) Run(ctx context.Context, prompt string) (Outcome, error)

Run drives the loop from one user prompt to a terminal reason. It never recurses and never returns without a TermReason (D6). A non-nil error is a genuine bug, never a turn that ended badly; every model and tool failure is a reason, not an error (doc 03 section 13).

The conversation survives Run: a second Run on the same Loop appends its prompt to the transcript the first one built, because the ant owns one context window for the whole session (doc 01 section 2.2). Per-run counters and recovery guards reset; msgs, the boundary, the part counter, and the compaction count carry over.

func (*Loop) Submit

func (l *Loop) Submit(text string)

Submit queues a human prompt to be folded in at the next drain point. Safe to call while a turn runs; the prompt joins the transcript after the current turn's tool results, never in the middle of them.

type Outcome

type Outcome struct {
	Reason TermReason
	Turns  int
}

Outcome is what a run hands back: the terminal reason and how many model turns it took. Tokens live in the ledger, not here.

type State

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

State is the entire cross-iteration state of one run. Handlers mutate it in place and set next; nothing important lives in a handler-local variable, because a local is invisible to the journal and dies at the continue (doc 03 section 4).

type TermReason

type TermReason string

TermReason is the single typed reason a loop run ends with. Every Run returns exactly one; there is no untyped exit (doc 03 section 2).

const (
	// TermCompleted is the model finishing with no tool calls. The only
	// success reason.
	TermCompleted TermReason = "completed"

	// TermMaxTurns is the per-run turn ceiling. Foreground runs set a
	// high one; worker ants get a tight one from their card (M3).
	TermMaxTurns TermReason = "max_turns"

	// TermBudgetExhausted is the ledger refusing another model call
	// because the budget is spent (D5). A between-turn gate, never a
	// mid-turn kill.
	TermBudgetExhausted TermReason = "budget_exhausted"

	// TermCanceled is cancellation arriving between turns: nothing was
	// running, nothing was lost.
	TermCanceled TermReason = "canceled"

	// TermToolsCanceled is cancellation arriving while tools were in
	// flight; kept distinct so the transcript can show which results
	// landed and which were abandoned.
	TermToolsCanceled TermReason = "tools_canceled"

	// TermPromptTooLong is a prompt the full compaction ladder could not
	// shrink under the blocking limit.
	TermPromptTooLong TermReason = "prompt_too_long"

	// TermModelError is an unrecoverable provider error after retries
	// and fallback were exhausted.
	TermModelError TermReason = "model_error"

	// TermCompactionFailed is the circuit breaker opening after
	// consecutive compaction failures. Separate from TermPromptTooLong
	// so the journal shows the breaker, not the symptom.
	TermCompactionFailed TermReason = "compaction_failed"
)

type Thresholds

type Thresholds struct {
	AutoCompact int // auto-compaction fires when live tokens cross this
	Blocking    int // crossing this with recovery spent is TermPromptTooLong
}

Thresholds are the absolute-token trigger points, derived from the window, never percentages: a fixed buffer in tokens is what actually protects the output budget (doc 03 section 11).

type ToolBody

type ToolBody struct {
	Call    string `json:"call"`
	Tool    string `json:"tool"`
	Content string `json:"content"`
	IsErr   bool   `json:"is_err,omitempty"`
}

ToolBody is the session body of one tool result.

type Verdict

type Verdict struct {
	Allow        bool
	UpdatedInput json.RawMessage // non-nil replaces the call's input
	Reason       string          // model-facing sentence when not allowed
	Context      string          // a PreToolUse hook's note to surface with the result
}

Verdict is the permission seam's answer for one tool call. The ant (slice 9) adapts the doc 05 pipeline to this; the loop only needs to know whether to run the call and with what input.

Jump to

Keyboard shortcuts

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