agent

package
v2.9.0-dev.1 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2026 License: Apache-2.0 Imports: 36 Imported by: 0

Documentation

Overview

Package agent wraps the Google ADK runner with sensible defaults (streaming mode, in-memory session service, app name) so consumers hit the same shape regardless of whether they're driving the agent from a one-shot CLI, a REPL, or an HTTP handler.

Multi-turn conversation history is preserved automatically when Run() is called repeatedly with the same userID + sessionID — by default ADK's session.InMemoryService accumulates events. Pass WithSessionService to plug in a durable backend (e.g. an eventlog-backed Service for SQLite/Postgres persistence + audit log + crash-resume).

Index

Constants

View Source
const (
	// MetricGenAIInvocationDuration is the ADK-schema histogram of
	// end-to-end prompt→response turn latency.
	MetricGenAIInvocationDuration = "gen_ai.agent.invocation.duration"

	// AttrGenAIAgentName carries the agent's configured name
	// (WithName; "core_agent" by default) — distinguishes the daemon
	// root agent from spawned subagents in shared dashboards.
	AttrGenAIAgentName = "gen_ai.agent.name"

	// AttrErrorType marks failed turns. Only present on error; the
	// values are the stable attach.ClassifyTurnError kind enum
	// (config_error, auth_error, rate_limited, ...), never raw
	// error text.
	AttrErrorType = "error.type"
)
View Source
const (
	MetricAgentCompactions  = "core_agent.agent.compactions"
	MetricAgentCheckpoints  = "core_agent.agent.checkpoints"
	MetricAgentSubtasks     = "core_agent.agent.subtasks"
	MetricAgentInboxPending = "core_agent.agent.inbox_pending"
	MetricWatchdogAlerts    = "core_agent.watchdog.alerts"

	// AttrMetricSessionID mirrors pkg/usage's session.id key (kept
	// local to avoid an import for one string).
	AttrMetricSessionID  = "session.id"
	AttrWatchdogSignal   = "signal"
	AttrWatchdogSeverity = "severity"
)

core_agent.* instruments for agent-lifecycle state (#338 Phase 3).

View Source
const (
	SubtaskDefaultMaxTurns     = 5
	SubtaskDefaultMaxWallclock = 60 * time.Second
)

Defaults for SubtaskBudgets. Aimed at "narrow tool wrapper" shape (AgenticReadFile etc.); broader-research wrappers can override.

View Source
const AutoContinueOriginator = "core-agent/auto-continue"

AutoContinueOriginator is the turn-originator identity stamped on synthesized continuation turns (via Agent.InjectAs), so eventlog metadata and audit logs distinguish them from human turns. (When the note drains into one batch with a concurrently-injected human message, the last-message-caller rule makes the human the turn originator; the note text itself still marks the turn.)

View Source
const AutonomousOverlay = `` /* 738-byte string literal not displayed */

AutonomousOverlay is layer 3b — selected by WithMode(ModeAutonomous). The narrate-before-acting line is deliberately here: the eventlog/OTel trace is a runtime property of every autonomous deployment, and stated intent is what makes a burst of tool calls legible in that record. The no-clarification line is scoped to questions in OUTPUT TEXT; a deliberately installed ask channel (tools.NewAskUserTool with a live prompter) carries its own exception in its tool description.

View Source
const CheckpointEventTag = "checkpoint"

CheckpointEventTag is the value stored under session.Event.CustomMetadata["compaction"] for a task-boundary checkpoint event. Distinct from CompactionEventTag ("summary") so the audit log + telemetry can distinguish "we hit the token wall and summarized" from "the model said this task was done."

View Source
const CheckpointNoteKey = "checkpoint_note"

CheckpointNoteKey carries the operator/model-supplied task note (the `detail` arg of mark_task_done, or the operator's /done argument) on the checkpoint event's CustomMetadata. Parallel to CompactionFocusKey for compaction events.

View Source
const CompactionEventTag = "summary"

CompactionEventTag is the value stored under session.Event.CustomMetadata["compaction"] to mark an event as a compaction summary. The history-slicing path in Run scans for the most recent event carrying this marker and drops everything before it from the LLM request. See docs/context-management-design.md (Mechanism A) for the rationale.

View Source
const CompactionFocusKey = "compaction_focus"

CompactionFocusKey carries the operator-supplied focus hint (from `/compact <focus>` or Agent.Compact(ctx, focus)) on the summary event's CustomMetadata, so it survives in the audit log alongside the summary text.

View Source
const CompactionMetadataKey = "compaction"

CompactionMetadataKey is the key under which CompactionEventTag is stored on session.Event.CustomMetadata. Exported so consumers querying the audit log can find summaries deterministically.

View Source
const CoreInstruction = `` /* 1009-byte string literal not displayed */

CoreInstruction is layer 1 — the always-on core. Two items, both harness contract: the dispatch fact (read-only tools run concurrently; the runtime serializes state-mutating tools — the #460 enforcement whose landing DELETED the old edit-sequencing prompt rule, exactly per its marked exit path) and the compaction/handover contract (the paragraph spawned subagents were silently losing pre-#459).

View Source
const DefaultAppName = "core-agent"

DefaultAppName tags this process in the ADK runner. Telemetry and session stores key off this; override with WithAppName when embedding in a host that wants its own identity.

View Source
const DefaultCompactionThreshold = 0.85

DefaultCompactionThreshold is the fallback for DefaultCompactor.Threshold when no per-tier entry matches. 0.85 leaves headroom for one more full turn before hitting the actual context wall, and matches the historical universal default so frontier sessions see no behavior change.

View Source
const DefaultInstruction = CoreInstruction + "\n\n" + InteractiveOverlay

DefaultInstruction is the pre-#459 monolithic prompt, retained as a compositional alias through the v2.8.x series (deleted at the next breaking window alongside WithSystemInstructionPrefix). Close to today's semantics minus the persona and plan-sketch lines — see the disposition table in docs/system-prompt-layering-design.md.

Deprecated: build with the layer options (WithMode, WithExtraInstruction, WithUserInstruction) instead of composing against this constant.

View Source
const DefaultSchedulingInstruction = `` /* 1137-byte string literal not displayed */

DefaultSchedulingInstruction is the composable system-instruction constant for autonomous loops that have a tools.Scheduler installed (via RunAutonomous's WithScheduler option, or per-subagent via BackgroundAgentManager). It covers the cross-cutting cadence and state-persistence guidance that doesn't fit in the schedule_next_turn tool's per-call description.

Opt-in by composition — the autonomous driver does NOT inject this automatically. Recommended consumer usage:

agent.New(m,
    agent.WithInstruction(
        agent.DefaultInstruction + "\n\n" +
        agent.DefaultSchedulingInstruction + "\n\n" +
        myConsumerInstruction,
    ),
    agent.WithTools(...),
)

See docs/scheduled-monitoring-design.md for the design rationale and the matching tool-description text (Layer 1 of the steering pattern).

View Source
const GeminiParallelismQuirk = `` /* 320-byte string literal not displayed */

GeminiParallelismQuirk is a layer-2 provider quirk applied to Gemini-family models (model identifier containing "gemini").

Probe evidence (dev/parallel-probe/): Gemini-3.1-pro-preview- customtools without this mandate never batched across 65 search turns; Claude models are "less affected, marginal benefit" and get no quirks. Retire this when a probe rerun shows the provider no longer needs the exhortation.

View Source
const InteractiveOverlay = `` /* 416-byte string literal not displayed */

InteractiveOverlay is layer 3a — the default mode overlay. Disposition only: a present user can redirect cheaply, so narrate before non-trivial work and ask focused questions when genuinely blocked. No tool names (tool mechanics live in tool descriptions).

Variables

View Source
var ErrInboxClosed = errors.New("agent: inbox closed")

ErrInboxClosed is returned by Agent.Inject when the agent's inbox has been closed (typically because the agent has been shut down via its run context cancelling). Callers that publish messages on an unrelated lifecycle (e.g. a stdin scanner) should treat this as "stop publishing."

View Source
var ErrNoCheckpointer = errors.New("agent: no checkpointer wired (pass WithCheckpointer at agent.New)")

ErrNoCheckpointer is returned by Agent.Checkpoint when the agent was constructed without WithCheckpointer. Callers should check for this sentinel before treating it as a hard failure.

View Source
var ErrNoCompactor = errors.New("agent: no compactor wired (pass WithCompactor at agent.New)")

ErrNoCompactor is returned by Agent.Compact when the agent was constructed without WithCompactor. Callers should check for this sentinel before treating it as a hard failure.

View Source
var ErrSubtaskSpecInvalid = errors.New("agent: invalid SubtaskSpec")

ErrSubtaskSpecInvalid is returned when SubtaskSpec fails validation (empty Name / SystemPrompt / UserMessage). Pre- flight check so the caller gets a clear error rather than a confusing downstream ADK failure.

View Source
var ErrTurnInFlight = errors.New("agent: cannot compact/checkpoint while a turn is in flight; retry after the turn completes")

ErrTurnInFlight is returned by Compact / Checkpoint when a Run turn is currently in flight on this agent. Both operations append a boundary event directly to the parent session row; doing so mid-turn races the runner's own AppendEvent (ADK optimistic-concurrency failure against the runner's stale snapshot) and can wedge a boundary between a persisted functionCall and its functionResponse, which the history-slicing path (sliceFromBoundary) would then emit as an orphaned functionResponse that Gemini rejects. Callers (AttachCompact, the TUI /compact + /done slashes) should retry once the turn ends. See #355. The internal pre-turn drivers (runPendingCompaction / runPendingCheckpoint) run before the turn's cancel is registered, so this guard never blocks the automatic threshold-driven path.

Functions

func AutoContinueNote added in v2.8.0

func AutoContinueNote(interruptedAt time.Time) string

AutoContinueNote renders the synthesized continuation prompt. It is a system note, not impersonated user text: the model is told what was DETECTED and asked to pick the task back up. It describes what the classifier actually observed — an unfinished turn — and does NOT claim a cause (e.g. a daemon restart) it cannot verify: the same note fires from lazy-resume, boot scan, startup-session, and the in-lifetime retry loop, and interruption is inferred from tail shape alone (#615). Must contain autoContinueMarker (guarded by test).

func AutoContinueNoteFor added in v2.9.0

func AutoContinueNoteFor(interruptedAt time.Time, interruptedCalls []string) string

AutoContinueNoteFor selects the continuation note appropriate to what was interrupted (#624). When the tail died mid-tool and EVERY interrupted call classifies read-only (tools.IsReadOnlyToolName), it returns the read-only variant that does NOT nudge re-issuing them; otherwise (a mutating or unknown interrupted call, or an interrupted shape with no calls — a bare user message or a committed tool response) it returns the default note, which still tells the model it MAY re-issue. Classification is by tool metadata, never a note-local name list: an unrecognized name falls to the default (keep the nudge), the conservative direction.

func ClassifyInterruptedTail added in v2.8.0

func ClassifyInterruptedTail(events []*session.Event) (interruptedAt time.Time, interrupted bool)

ClassifyInterruptedTail reports whether a session's committed history ends in an interrupted turn, and when the interruption happened (the timestamp of the last committed event of the broken turn). Detection is derived entirely from eventlog state — no write-ahead marker exists or is needed; per-event persistence means the tail shape IS the marker (docs/auto-continue-design.md §Detection). The one place tail shape is insufficient is a MAX_TOKENS output-cap truncation that still emitted text — indistinguishable from a normal completion by shape alone — so the eventlog overlay stamps the genai FinishReason into CustomMetadata and the hasText arm consults it (#582).

The classifier walks backward to the last *conversational* event on the parent branch, skipping annotation rows: subagent branches, streaming partials, contentless/role-less events (autonomous checkpoints and notes, interrupt-audit rows), and compaction summaries. That event then classifies as:

  • user message (no tool parts) → interrupted: the question was committed, no answer ever came.
  • carries a functionResponse part → interrupted: a tool result (possibly a #537 tail-repair synthesis) was committed but no model turn consumed it.
  • carries a functionCall part → interrupted mid-tool (the #537 repair target, seen before repair has run) — UNLESS every call in the event is long-running (LongRunningToolIDs): ADK treats a turn ending in only long-running calls as final, with responses legitimately arriving in a later user turn.
  • anything else (model text) → completed turn, UNLESS a stamped MAX_TOKENS finish reason marks it truncated mid-task (then: interrupted — see incompleteFinish).

Additional terminal shapes (never continued): an operator interrupt-audit row anywhere after the tail (deliberate kill), an ErrorCode final (the turn ended with an error the user already saw), an empty-parts agent-authored final (Gemini streaming can close a completed turn with an empty aggregate), a turn parked on ADK's tool-confirmation flow, a SkipSummarization response final, and a tail that IS a prior committed continuation note (one automatic attempt per interruption — the lazy-path crash-loop bound).

func ClassifyInterruptedTailWithCalls added in v2.9.0

func ClassifyInterruptedTailWithCalls(events []*session.Event) (interruptedAt time.Time, interrupted bool, interruptedCalls []string)

ClassifyInterruptedTailWithCalls is ClassifyInterruptedTail plus the tool-call names of the interrupted tail (#624). interruptedCalls is populated ONLY for a mid-tool interruption (the functionCall arm) — the shape whose continuation note carries a "re-issue interrupted tool calls" nudge — and lists every call name in that tail event. It is nil for the other interrupted shapes (a bare unanswered user message, or a committed functionResponse whose result is already in history), and nil when the tail is not interrupted. Callers scope the continuation note by classifying these names (see AutoContinueNoteFor).

func EventlogMetadataExtractor

func EventlogMetadataExtractor() eventlog.MetadataExtractor

EventlogMetadataExtractor returns an eventlog.MetadataExtractor that pulls the per-event auth.Caller identity (and proxy attribution, when present) from the request context onto the eventlog row's sidecar metadata.

Pass to eventlog.Open via eventlog.WithMetadataExtractor — typically at daemon startup in cmd/core-agent. The extractor is a pure function of the per-event context, so the eventlog package itself stays auth-agnostic; only the binary that wires both packages together depends on both.

Returns nil entries when no Caller is on context (legacy / single-user / out-of-band Run callers); the eventlog package then stores no sidecar JSON for that row.

func FormatAutoContinueInbox

func FormatAutoContinueInbox(messages []string) string

FormatAutoContinueInbox renders messages with the system-note framing used by the TUI's "auto-continue from queued input" flow (see docs/operator-input-design.md). The framing tells the model these notes arrived while it was working and gives it explicit branches for the cases that show up in practice — most importantly the dedup case (operators frequently re-phrase the same ask while waiting) and the post-completion case (the prior turn ended with mark_task_done, so there's no "current task" to adapt).

Iteration history:

  • v1 (PR-α): "If any of these change the current task, adapt your next step. If they're separate requests, use the `todo` tool to capture them and continue what you were doing." Both branches assumed an active task to "continue" — false after mark_task_done. Bundle of similar asks got handled as N separate tasks (issue #144's read_file loop pattern).
  • v2 (#144): explicit branches for (a) variants-of-the-same-ask dedup, (b) mid-task adjustments, (c) separate asks during an active task, (d) post-completion bundles. The post-completion branch tells the model to treat the bundle as the next operator request and respond once.

Returns "" when messages is empty (caller should not auto- continue in that case). Format intentionally differs from formatInboxForPrompt (which prepends to an operator-typed prompt) because auto-continue turns have no operator prompt to prepend to — the formatted block IS the user message for the new turn.

func IsCostCeilingExceeded

func IsCostCeilingExceeded(err error) bool

IsCostCeilingExceeded returns true when err was returned by Run because a previous turn tripped a configured cost ceiling. Operators / hosts use this to distinguish "operator must reset the ceiling" from other Run errors that may warrant retry.

func IsWatchdogTripped added in v2.9.0

func IsWatchdogTripped(err error) bool

IsWatchdogTripped reports whether err was returned by Run because a prior turn tripped the behavioral watchdog under --watchdog=enforce. Hosts use this to distinguish "operator must reset the watchdog" from other Run errors that may warrant retry.

func NewMarkTaskDoneTool

func NewMarkTaskDoneTool(getter func() *Agent) tool.Tool

NewMarkTaskDoneTool returns the model-facing tool that signals task completion. The handler doesn't fire the checkpoint inline — that would require synchronous LLM I/O from inside a tool call, which ADK's runner doesn't expect. Instead the handler stashes the detail on the agent and flips a pending flag; Agent.Run's post-turn hook picks it up and fires Checkpoint before the next turn.

Takes a getter rather than a *Agent directly because we register this tool BEFORE the agent struct is constructed (llmagent.New snapshots its tool list at construction time, so registration has to happen up front). The getter resolves lazily — agent.New sets the agent pointer after llmagent.New returns, and the getter walks the closure to find it. A nil return from the getter is treated as "registration race not yet completed" and the call is a silent no-op (defensive — shouldn't happen in practice because the model never sees the tool before agent.New returns).

Registered automatically in agent.New when a Checkpointer is wired via WithCheckpointer.

func NewSubagentTool

func NewSubagentTool(opts SubagentOptions) (tool.Tool, error)

NewSubagentTool wraps an *agent.Agent as a tool the parent's model can call. The subagent runs through ADK's runner using the parent's session.Service (so its events stream live into the same audit log as the parent), with session.Event.Branch set to "<parent_branch>.<this>" so the audit log stays distinguishable and ADK's contents-processor branch filter keeps the subagent's events from leaking into the parent's next-turn LLM request.

The parent's session.Service is captured from Inner — Inner is expected to have been constructed with the same WithEventLog (or WithSessionService) the parent uses. The agent.WithSubagents convenience option handles this wiring automatically; consumers who construct subagent tools directly via NewSubagentTool need to share the session.Service themselves.

func RegisterMetrics added in v2.8.0

func RegisterMetrics(mp metric.MeterProvider, src AgentSource) (metric.Registration, error)

RegisterMetrics wires the per-agent lifecycle observers against mp: compactions, checkpoints, subtasks (counters) and inbox_pending (gauge), each dimensioned by session.id. Call once at boot with the process-global MeterProvider.

Counter sources are the in-memory per-process fields on Agent — NOT the eventlog-derived ContextStats, which is an O(events) scan per call and resumes across restarts (a restart would step an ObservableCounter backward or forward arbitrarily).

func SubagentReturnContract added in v2.9.0

func SubagentReturnContract(returnTool string) string

SubagentReturnContract renders the instruction block that tells a delegated subagent its output is a value returned to another agent (#727).

returnTool is the name of the tool that hands a value back and ends the run. Pass "" for delegation paths that have no such tool — a subagent invoked synchronously as a tool returns its last message and nothing else — so the instruction never names a gesture the runtime hasn't registered.

Why this is an instruction and not a tool description. [#641] put the same framing on the done tool's description, which is only read by a model that goes looking for that tool. The contract has to hold on every termination path — a natural stop, a budget cap, a watchdog halt, a sync invocation with no done tool at all — and on all of those the subagent's *last assistant text* is what the delegating agent receives. A subagent that thinks it is writing a status update for a human writes "standing by in a healthy, inactive state"; one that knows it is returning a value writes the findings.

Install with WithExtraInstruction (layer 5), which appends whether or not the caller replaced layers 1–3 — the contract is a property of being a delegation, not of the harness baseline.

Types

type Agent

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

Agent is the wrapper around an ADK llmagent + runner. One Agent represents one configured LLM-driven role.

func New

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

New constructs an Agent backed by model. Returns a clear error if the underlying ADK constructors reject the configuration.

func (*Agent) AddSessionCostBudget added in v2.9.0

func (a *Agent) AddSessionCostBudget(usd float64) (CostCeiling, error)

AddSessionCostBudget raises the per-session ceiling by usd and returns the ceilings that result. This is the ONLY mutation the reset surface offers, and deliberately so: the alternatives — zeroing the accumulator, or restarting a spend "window" — would make Agent.SessionCostUSD, /usage and the eventlog-derived cost disagree about what the session actually spent. Raising the bar keeps every dollar counted and still hands the operator runway.

usd must be > 0. Raising a disabled (0) session ceiling is refused: that would silently ARM a bound the operator never configured, which is a tighter posture than they asked for, not a looser one.

func (*Agent) AgentName

func (a *Agent) AgentName() string

AgentName returns the configured agent name (the WithName value) stored on construction. Used by NewSubagentTool to derive a default tool name.

func (*Agent) AppName

func (a *Agent) AppName() string

AppName returns the AppName the agent was constructed with (the value passed to runner.Config). Used by callers that need to identify the session triple (app, user, session) for queries against the event log or session.Service.

func (*Agent) AskSideQuestion

func (a *Agent) AskSideQuestion(ctx context.Context, question string) (string, error)

AskSideQuestion runs one tool-less LLM call that sees the agent's current conversation history plus the supplied question, and returns the model's answer as a single string. Intended for the TUI's /btw side-question flow (docs/operator-input-design.md layer C): the operator asks a quick context-grounded question ("what was that file again?") without polluting the main conversation. The call bypasses Agent.Run entirely — no inbox drain, no permission gating, no event-log writeback, no tools.

The question is appended to the existing history as a transient user turn that exists only for this one call; nothing about the agent's persisted session state changes. The model can therefore reference prior tool output, prior assistant turns, prior user messages — but cannot call any tool to do new work.

Errors:

  • context cancellation: returns ctx.Err().
  • no session.Service wired: returns a clear error (defensive; agent.New always installs one, but hand-constructed Agents used in tests don't).
  • GenerateContent failures bubble up unchanged so callers can distinguish transport vs API vs model errors via errors.Is.

func (*Agent) BackgroundManager

func (a *Agent) BackgroundManager() SubagentManager

BackgroundManager returns the SubagentManager the agent was constructed with via WithBackgroundManager, or nil when none was wired. Used by spawn tools + the runner's REPL alert display to reach the manager without keeping a separate reference. Callers that need the concrete *background.Manager recover it with background.ManagerOf.

func (*Agent) Checkpoint

func (a *Agent) Checkpoint(ctx context.Context, taskNote string) (CheckpointResult, error)

Checkpoint writes a task-boundary checkpoint event to the session and clears any pending checkpoint flag. Like Compact, the event becomes the slicing boundary for the next turn's model request.

taskNote is the operator/model-supplied detail (the mark_task_done `detail` arg, or /done's argument). Empty is fine — the prompt still produces a useful summary, just without the leading completion note.

Errors:

  • ErrNoCheckpointer when no checkpointer was wired.
  • Context cancellation: ctx.Err().
  • Model errors propagate wrapped so callers can errors.Is on transport vs API failures.

func (*Agent) CloseInbox added in v2.8.0

func (a *Agent) CloseInbox()

CloseInbox marks the agent's inbox closed so every subsequent Inject / InjectAs fails with ErrInboxClosed instead of queuing into a mailbox no live turn will ever drain. The wake-driven surfaces (runner.WakeLoop) call this when their loop exits — both daemon shutdown and per-session eviction cancel that loop's context — so a post-death inject that raced past handler-level gating fails loudly at the source rather than being acknowledged and silently lost (#566). Idempotent and nil-safe.

func (*Agent) Compact

func (a *Agent) Compact(ctx context.Context, focus string) (CompactionResult, error)

Compact runs an out-of-band summarizer LLM call against the current session's history and writes the result as a "summary" marker event the history-slicing path in Run picks up on the next turn. Used both programmatically and by the TUI's /compact slash. See Agent.Checkpoint for the task-boundary variant that shares this machinery.

focus is an optional hint for the summarizer ("focus on the auth-rewrite thread"). Empty is fine — the default prompt instructs the model to produce a balanced handover.

Errors:

  • ErrNoCompactor when no compactor was wired.
  • Context cancellation: ctx.Err().
  • Model errors propagate wrapped so callers can errors.Is on transport vs API failures.

func (*Agent) CompactIfNeeded

func (a *Agent) CompactIfNeeded(ctx context.Context, focus string) (CompactionResult, error)

CompactIfNeeded fires Compact when the wired Compactor's ShouldCompact returns true, otherwise reports Skipped=true. Useful for hosts that want to drive automatic compaction on a turn-end hook without re-implementing the threshold check.

func (*Agent) ContextStats

func (a *Agent) ContextStats() ContextStats

ContextStats returns a snapshot of compaction/checkpoint/subtask activity for this session. Safe to call from any goroutine. Errors fetching the session (e.g. session.Service hiccup) are swallowed and result in zero boundary fields — the subtask fields are populated regardless since they're in-memory.

Cost: one session.Service.Get() call + O(events) scan. Designed for operator-driven /context slash usage, not hot-path telemetry — cache or sample if you need it per-turn.

func (*Agent) CostCeilingLimits added in v2.9.0

func (a *Agent) CostCeilingLimits() CostCeiling

CostCeilingLimits returns the ceilings currently in force, including any runway added since construction via AddSessionCostBudget. Zero fields mean that bound is disabled.

func (*Agent) CostCeilingTripped

func (a *Agent) CostCeilingTripped() (bool, string)

CostCeilingTripped reports whether the agent is currently blocking new turns because a configured ceiling was exceeded. Exposed for /stats and similar UI surfaces; operators surface this alongside the totals so the "why is the agent refusing my prompts?" question has an obvious answer.

Returns (true, reason) when blocked; (false, "") otherwise.

func (*Agent) Description

func (a *Agent) Description() string

Description returns the one-line description set via WithDescription. Empty when unset. Satisfies attach.DescriptionProvider — the /.well-known/agent-card.json handler falls back to this when no explicit AgentCardConfig.Description override is supplied.

func (*Agent) DrainInbox

func (a *Agent) DrainInbox() []string

DrainInbox pulls every currently-queued message off the inbox and returns them in arrival order. The queue is emptied as a side effect — repeat calls return nil until new messages arrive.

Exposed so harnesses that want to format inbox messages differently from the built-in `[Inbox]` block (e.g. the TUI's auto-continue flow uses FormatAutoContinueInbox to add system-note framing) can pull the messages themselves and pass the formatted result to Run as a regular prompt. Run's internal drain then finds an empty inbox and is a no-op, so there's no double-injection.

Returns nil when no agent or no inbox is wired — both are defensive cases for hand-constructed Agent structs in tests.

Emits an `inbox`/dequeued event for each drained message with the message's prompt_id, so SSE consumers can correlate the dequeue with the earlier queued event.

Callers that need the per-message originator identity (multi-session turn threading) should use drainInboxFull instead (internal). The public DrainInbox shape is preserved for external harnesses that only care about the text payloads.

func (*Agent) Emit added in v2.8.0

func (a *Agent) Emit(eventType string, payload any)

Emit is the exported seam over emit() for the packages that will split out of pkg/agent (pkg/agent/background's inbox lives outside the core type — see docs/agent-package-split-design.md). It pushes a typed event onto the attach SSE stream, or is a no-op when no subscriber is connected. Safe on a nil receiver.

func (*Agent) EventLog

func (a *Agent) EventLog() *eventlog.Handle

EventLog returns the *eventlog.Handle the agent was constructed with via WithEventLog, or nil when no event log was wired. Use to reach back to Stream.Since / Stream.Watch for replay or live tail without keeping a separate reference.

func (*Agent) Gate added in v2.8.0

func (a *Agent) Gate() *permissions.Gate

Gate returns the permissions gate wired via WithGate, or nil when none was configured. Read-only seam for the split-out packages (pkg/attachadapter projects gate state onto the attach wire format); mutations go through the gate's own methods.

func (*Agent) HasCheckpointer

func (a *Agent) HasCheckpointer() bool

HasCheckpointer reports whether a Checkpointer was wired via WithCheckpointer. Hosts use this to gate `/done` (and the `/checkpoint` alias) out of `/help` and the slash palette when --no-checkpoint was passed. Same shape as HasCompactor.

func (*Agent) HasCompactor

func (a *Agent) HasCompactor() bool

HasCompactor reports whether a Compactor was wired via WithCompactor. Hosts use this to gate operator-facing surfaces: don't list `/compact` in `/help` when there's nothing to invoke. Same idea as nil-checking a.compactor directly, but exported so adapters living outside the agent package don't need a reflection trick.

func (*Agent) HasPendingOperatorInput added in v2.9.0

func (a *Agent) HasPendingOperatorInput() bool

HasPendingOperatorInput reports whether the inbox holds a queued message from someone OTHER than auto-continue itself — real operator/external input waiting to drive the next turn. Auto-continue consults this to STAND DOWN (#624): if an operator has already queued input (e.g. `stop`) while a turn was interrupted, that input must drive the next turn on its own — injecting a "continue the task" note into the same drained batch lets the note outrank the operator's stop (the #624 race). Messages injected by auto-continue itself (AutoContinueOriginator) are excluded so it never sees its own note as operator input. Zero-identity messages (legacy Inject / single-user) DO count — they are operator input without an attached identity.

Returns false for nil agent / inbox.

func (*Agent) InboxArrived

func (a *Agent) InboxArrived() <-chan struct{}

InboxArrived returns a channel that fires when a new message has been injected. The harness can use this to decide when to start the next turn instead of polling — typical pattern:

for {
    select {
    case <-ctx.Done():
        return
    case <-ag.InboxArrived():
        runOneTurn("continue") // inbox prepended automatically
    }
}

The channel has a 1-buffer signal-style semantics: multiple pushes between consumer wake-ups coalesce into one notification (the consumer drains the queue and sees them all).

func (*Agent) Inject

func (a *Agent) Inject(message string) error

Inject queues message on the agent's inbox. The next call to Agent.Run will drain pending messages, format them as an "[Inbox]" block, and prepend the block to the prompt the model sees.

Inject is safe to call concurrently from any goroutine — typical usage is a harness goroutine reading from stdin, an HTTP handler, or an orchestrator's gRPC stream. Drop-oldest backpressure kicks in when the queue exceeds the soft cap (256) so a stuck consumer can't deadlock the agent.

Emits an `inbox`/queued event on the attach event stream (if one is wired) with the assigned prompt_id, so SSE consumers can surface the queued state to operators. The same prompt_id flows out as the `inbox`/dequeued event when the inbox is drained inside Run.

Returns ErrInboxClosed once the agent has been shut down; callers publishing on an unrelated lifecycle should treat that as "stop."

Inject queues without an originator identity. Callers that have a per-request auth.Caller (typically attach handlers in a multi-session deployment) should use InjectAs instead — the caller is threaded into the turn context so the eventlog metadata sidecar and per-caller MCP path see who triggered the turn.

func (*Agent) InjectAs

func (a *Agent) InjectAs(message string, caller auth.Caller) error

InjectAs is Inject with a per-message originator identity. The caller is stored on the queued message and used as the turn originator when the inbox drains: the agent loop wraps the turn context with auth.WithCaller(caller) so eventlog metadata, MCP outbound context, and other caller-aware substrates see the identity that triggered the turn.

Zero-value caller (Identity == "") is equivalent to Inject — no identity is threaded.

When multiple injects queue between turns, the LAST message's caller wins as the turn originator (per docs/multi-session-design.md "the turn answers the most recent ask"). Same prompt_id correlation, same SSE events as Inject.

func (*Agent) Inner added in v2.8.0

func (a *Agent) Inner() adkagent.Agent

Inner returns the underlying ADK agent the turn loop drives. It is the read-only seam the split-out driver package (pkg/agent/autonomous, see docs/agent-package-split-design.md) uses instead of reaching the unexported field directly; returns nil if the agent is nil or was constructed without an inner agent.

func (*Agent) Interrupt

func (a *Agent) Interrupt() bool

Interrupt cancels the in-flight turn (if any) by invoking the stored cancel func. Returns true if there was something to cancel (a turn was in flight when called), false if the agent was idle (no-op). Safe for concurrent callers; the cancel is single-shot per turn — a second Interrupt during the same turn is a no-op.

Cancellation propagates through context.Canceled to the in-flight model call. The agent's tools (bash, fetch_url, etc.) cancel their I/O when they see the cancel; the model call returns immediately with a partial response; the run loop emits any already-accumulated content and exits. Sessions, the event log, background subagents, and the attach registry all survive untouched.

func (*Agent) MarkInterruptPending added in v2.8.0

func (a *Agent) MarkInterruptPending()

MarkInterruptPending is the agent side of attach.InterruptSelfAuditor. The attach /interrupt handler calls it (via the adapter) after an operator cancel actually fired, instead of appending the audit row out-of-band. The row is then written from the post-turn cleanup (see drainInterruptAudit) once the interrupted turn has fully unwound.

Deferring the write this way is the whole fix for #565: the handler's out-of-band Get-then-AppendEvent bumped the live session row's last_update_time while the runner was still flushing the interrupted turn, tripping ADK's optimistic-concurrency check so the operator's clean cancel surfaced as an opaque "stale session error". Riding the turn loop moves the write to the one window with no live runner handle.

func (*Agent) MeterProvider added in v2.9.0

func (a *Agent) MeterProvider() metric.MeterProvider

MeterProvider returns the resolved OTel MeterProvider the agent's metric instruments were built from (the WithMeterProvider value, or the process-global resolved at New). Subagent construction sites use it to propagate an embedder's non-global provider down the tree — the background spawn path threads it into the spawned agent so subagent turns/tools land in the same provider as the parent rather than silently falling back to the global. Nil only on a hand-constructed Agent (tests); callers thread it conditionally.

func (*Agent) Mode added in v2.8.0

func (a *Agent) Mode() Mode

Mode reports the layer-3 overlay mode the agent was built with (#459). The autonomous driver consults this to warn when an interactive-mode agent is driven autonomously. Note a WithInstruction full-replace skips the overlay entirely; Mode still reports whatever WithMode set (default ModeInteractive) — the warning is advisory, and full-replace consumers know what they're doing.

func (*Agent) Model added in v2.8.0

func (a *Agent) Model() adkmodel.LLM

Model returns the LLM the agent was constructed with (#510). Exposes the value New received so drivers that accept a pre-built Agent — runner.Run in particular — can derive the streaming model from the agent instead of demanding it be passed alongside (the agent/model mismatch hazard #492 flagged). Nil-safe.

func (*Agent) ModelName

func (a *Agent) ModelName() string

ModelName returns the name of the LLM the agent was constructed with (sourced from model.Name() at New() time). Used by the attach-mode /status endpoint so the TUI usage panel can label the in/out/cost figures with the model in use.

func (*Agent) PendingInboxCount

func (a *Agent) PendingInboxCount() int

PendingInboxCount peeks at the inbox without draining it. Useful for UI surfaces (TUI queue panel) that need to render the current queue length without claiming the messages.

Returns 0 for nil agent / inbox.

func (*Agent) RecordGuardrailReset added in v2.9.0

func (a *Agent) RecordGuardrailReset(reset []string, budgetUSD float64, caller string)

RecordGuardrailReset persists an operator's reset (#643) and is the audit trail #331 asked for — one row, not two, so there is nothing to keep in agreement. reset names the guardrails whose flag was cleared, budgetUSD the runway added, caller the authenticated identity (empty when the reset came from an unauthenticated in-process surface).

Call it AFTER the state mutations, once per operator action. A reset that cleared nothing and added nothing writes nothing: a defensive reset against a healthy session is not an event.

func (*Agent) RequestWake

func (a *Agent) RequestWake()

RequestWake fires the agent's wake signal. Used by:

  • BackgroundAgentManager (via a driver-side goroutine) to wake a sleeping supervisor as soon as a child alert arrives, instead of waiting for the supervisor's next scheduled wake.
  • The future attach-mode `POST /sessions/<id>/wake` endpoint, when an operator outside the process wants an immediate rescan.
  • Operator input via Agent.Inject — Inject calls RequestWake internally so a typed command also pierces an active sleep.

No-op when the agent has no wake signal (defensive: hand-constructed Agent structs used in tests don't necessarily wire one up).

func (*Agent) ResetCostCeiling

func (a *Agent) ResetCostCeiling()

ResetCostCeiling clears any tripped cost-ceiling flag, allowing the agent to accept new turns again. Typically wired to an operator slash command after the operator has reviewed why the ceiling tripped. Safe to call even if no ceiling is configured or no flag was set — no-op in that case.

A bare reset is enough for a per-TURN trip: the next turn starts from a fresh baseline. It is NOT enough for a per-SESSION trip — the accumulator is already at or past the ceiling, so the very next turn re-trips. Pair it with AddSessionCostBudget (see WouldRetripCostCeiling) to hand the session real runway.

func (*Agent) ResetWatchdog added in v2.9.0

func (a *Agent) ResetWatchdog()

ResetWatchdog clears a tripped enforce-mode watchdog, letting the agent accept new turns again. Typically wired to an operator slash command after the operator has reviewed why the watchdog tripped. Also resets the underlying watchdog's signal state so the next run of identical calls has to build back up to the threshold. Safe to call when nothing tripped — no-op in that case.

Deliberately does NOT drop queued feedback (#159). A reset resumes a model whose context still ends in the loop it was halted for; the queued observation is the only thing that stops the first post-reset turn from re-issuing the same call. Clearing it here would make the reset undo the correction along with the halt.

func (*Agent) RestoreGuardrails added in v2.9.0

func (a *Agent) RestoreGuardrails(ctx context.Context) error

RestoreGuardrails folds this session's durable guardrail rows and applies the result: a halt that survived a restart is re-armed, and budget an operator granted before the restart is re-applied to the per-session ceiling.

Idempotent and cheap to over-call — the fold applies at most once per agent, and Run calls it before its first pre-flight, so most callers never need it. Exposed for embedders that want the state restored (and any error surfaced) before accepting traffic rather than on the first turn.

The latch is set on SUCCESS only, so a read that fails is retried on the next call rather than leaving the agent permanently unrestored. A sync.Once here would mean one transient database error at the wrong moment disarms the backstop for the whole life of the process — the exact failure this durability work exists to remove.

Returns an error only when the session read fails. A session with no guardrail history restores nothing and reports success.

func (*Agent) Run

func (a *Agent) Run(ctx context.Context, prompt string) iter.Seq2[*session.Event, error]

Run executes one turn of the agent against prompt and returns the event iterator straight from ADK's runner. Callers are expected to range over the returned iter.Seq2 and consume events as they arrive — partial text chunks, tool calls, and the final TurnComplete event.

Multi-turn use: call Run() repeatedly on the same Agent. The configured session ID is reused across calls, so the ADK accumulates conversation history automatically.

When a BackgroundAgentManager is wired via WithBackgroundManager, any alerts background subagents have emitted since the last turn are drained (non-blocking) and prepended to the prompt so the parent's model sees them before deciding what to do next.

Inbox messages queued via Agent.Inject from external callers (harness, orchestrator, HTTP handler) are also drained and prepended, sibling to the alerts block. Ordering: alerts go first (internal state changes); inbox goes second (external input, closer to the prompt logically); then the original prompt.

func (*Agent) RunSubtask

func (a *Agent) RunSubtask(ctx context.Context, spec SubtaskSpec) (SubtaskResult, error)

RunSubtask runs a synchronous, fresh-context, single-purpose LLM call against the parent's session.Service (with a distinct branch so events don't leak into the parent's next Run request). Returns the model's digested answer.

Properties:

  • Caller blocks until the subtask returns.
  • The subtask runs in its own ADK session (deriveSubagentSessionID produces a parent-prefixed ID; the audit log can correlate).
  • The subtask sees NO parent history; its model gets only the SystemPrompt + UserMessage from the spec.
  • The subtask's tool set is the spec's Tools — nothing inherited from the parent.
  • Cost rolls up to the parent's usage.Tracker (when one is wired via WithUsageTracker) so /stats reflects everything.
  • On budget exhaustion: returns SubtaskResult{Truncated: true} with whatever partial Digest accumulated. NOT a Go error.
  • On model failure or other unrecoverable error: returns the wrapped error; caller can errors.Is on transport vs API vs spec-validation failure.

Used by the agentic tool wrappers in core-agent/tools ( AgenticReadFile, AgenticFetchURL, AgenticGrep, AgenticResearch); also directly callable by host code that wants to spawn a one-shot research subagent without going through a tool.

func (*Agent) RunWithContents

func (a *Agent) RunWithContents(ctx context.Context, contents []*genai.Content) iter.Seq2[*session.Event, error]

RunWithContents drives one agent turn from a pre-built conversation history (genai.Contents) instead of a single prompt string. The trailing message is treated as the new user input; everything before it is pre-populated into a fresh session as history events.

Each call uses a fresh sessionID so prior calls don't accumulate state — the caller-supplied history is authoritative. Use this when integrating with a runtime (the AX adapter is the motivating example) that supplies the full conversation history per turn rather than relying on a session-managed prompt.

The last content's Role must be genai.RoleUser; non-user trailing messages return an error. Empty contents return an error.

func (*Agent) SessionCostUSD added in v2.9.0

func (a *Agent) SessionCostUSD() float64

SessionCostUSD reports the session's cumulative spend as the ceiling enforcement sees it — the same accumulator a per-session trip is measured against. 0 when no usage tracker is wired.

func (*Agent) SessionID

func (a *Agent) SessionID() string

SessionID returns the session identifier the agent was constructed with. Combined with AppName + UserID this is the key the event log uses to scope ForSession queries.

func (*Agent) SessionService

func (a *Agent) SessionService() session.Service

SessionService returns the session.Service backing this agent. When no WithSessionService option was passed at construction this is the default in-memory service. Useful for callers that want to query session state directly (e.g. listing prior events) without keeping their own reference to the Service they passed in.

func (*Agent) SetAttachEmitter deprecated

func (a *Agent) SetAttachEmitter(f func(eventType string, payload any))

SetAttachEmitter is the pre-#506 name of SetOperatorEventEmitter — the typed operator-event seam is transport-neutral; only its first consumer was attach.

Deprecated: use SetOperatorEventEmitter.

func (*Agent) SetOperatorEventEmitter added in v2.8.0

func (a *Agent) SetOperatorEventEmitter(f func(eventType string, payload any))

SetOperatorEventEmitter installs (or clears, when f is nil) the callback the agent uses to push typed operator events — status-update, usage-update, turn-complete, turn-error, inbox — to whatever transport is listening. The attach SSE broadcaster is today's only consumer (it wires its Emit on first subscriber and clears it when the last disconnects), but the seam itself is transport-neutral (#506; formerly SetAttachEmitter, which baked one transport's name into the frozen core surface).

When a tracker is wired via WithUsageTracker, this also installs (or clears) a tracker.SetOnAppend callback that emits a usage-update event with cumulative + per-model totals after every Append. That's what carries the "running cost" the spec describes for the usage-update event type — the turn-complete event reports 0 for cost because the agent itself has no pricing reference (pricing lives in the harness).

Optional. When no callback is installed, all agent-side emit calls are no-ops — events are dropped to the floor since no consumer can see them. This matches the protocol's design intent: typed events are operator-visible signals, not audit log entries; if there's no operator, there's nothing to signal.

Safe to call concurrently with the agent's own emit path; the internal mutex serializes the swap and any in-flight emit reads.

func (*Agent) Streaming added in v2.8.0

func (a *Agent) Streaming() adkagent.StreamingMode

Streaming returns the ADK streaming mode the agent was constructed with. Part of the read-only accessor seam the split-out subagent packages (pkg/agent/background) use to build child agents that inherit the parent's streaming behavior without reaching the unexported field directly.

func (*Agent) SubagentNames added in v2.9.0

func (a *Agent) SubagentNames() []string

SubagentNames returns the resolved tool names of the declarative subagents registered via WithSubagents (empty when none). These are synchronous parent-callable subagent tools; operator surfaces use the set to classify their tool-source as "subagent" (#627), distinct from ordinary built-ins. The result is a copy — callers may mutate it.

func (*Agent) Tools

func (a *Agent) Tools() []tool.Tool

Tools returns the resolved tool list the agent was constructed with — including any subagent tools materialized by WithSubagents. Useful for diagnostics ("does my parent know about the research subagent?") without introspecting ADK internals.

func (*Agent) Tracker added in v2.8.0

func (a *Agent) Tracker() *usage.Tracker

Tracker returns the usage.Tracker the agent was constructed with via WithTracker, or nil when none was wired. Part of the read-only accessor seam pkg/agent/background uses to roll background subagent turns into the parent's usage totals.

func (*Agent) UserID

func (a *Agent) UserID() string

UserID returns the user identifier the agent was constructed with.

func (*Agent) WakeRequested

func (a *Agent) WakeRequested() <-chan struct{}

WakeRequested returns a channel that fires whenever RequestWake (or Inject, which calls RequestWake internally) is invoked. The autonomous driver attaches this channel to the context it passes to Scheduler.BeforeNextTurn so SleepScheduler can select on it alongside its sleep timer and ctx.Done. Buffered(1) coalesced semantics: multiple wakes between consumer drains land as one notification.

func (*Agent) WatchdogMode added in v2.9.0

func (a *Agent) WatchdogMode() string

WatchdogMode reports the agent's configured watchdog posture as one of the config.Watchdog* strings: "off" (no watchdog wired), "warn" (observe and alert), "feedback" (observe, alert, and tell the model on its next turn), or "enforce" (all of that, and halt).

Exposed because "is the backstop actually on?" is the question #642 exists to answer, and the answer must be checkable from outside the package — by the startup summary, by operator surfaces, and by the wiring tests that keep a future refactor from quietly dropping the WithWatchdogEnforce option on a daemon's session-created agents.

func (*Agent) WatchdogTripped added in v2.9.0

func (a *Agent) WatchdogTripped() (bool, string)

WatchdogTripped reports whether the agent is currently blocking new turns because the enforce-mode watchdog fired. Exposed for /stats and similar surfaces so "why is the agent refusing my prompts?" has an obvious answer. Returns (true, reason) when blocked; (false, "") otherwise.

func (*Agent) WouldRetripCostCeiling added in v2.9.0

func (a *Agent) WouldRetripCostCeiling() (retrip bool, spent, ceiling float64)

WouldRetripCostCeiling reports whether clearing the trip flag right now would be immediately undone — the session's accumulated spend is already at or past the per-session ceiling, so the next turn's enforcement pass trips again before the operator sees any progress. Returns the spend and the ceiling so the caller can say so precisely.

This is the check that keeps the reset affordance honest: offering a button that provably does nothing is the same state-a-property-you-don't-enforce pattern the reset exists to fix.

type AgentSource added in v2.8.0

type AgentSource interface {
	Agents() []*Agent
}

AgentSource enumerates live agents for RegisterMetrics to sample on each export interval. Implementations must be cheap and thread-safe; nil agents in the slice are skipped.

session.id comes from each agent's own SessionID() — unlike the usage observer, which takes the registry Entry's triple as authoritative. The two coincide in the daemon (the adapter derives Entry fields from the agent); hosts registering agents under a different Entry session id will see the two observer families disagree.

type CheckpointResult

type CheckpointResult struct {
	CheckpointEventID string
	SummaryText       string
	TaskNote          string
	Duration          time.Duration
	Skipped           bool
}

CheckpointResult reports what happened on a Checkpoint call. Same fields as CompactionResult plus TaskNote (the detail that triggered the checkpoint, surfaced so UI / telemetry can show it without re-reading the event metadata).

type Checkpointer

type Checkpointer interface {
	// ShouldCheckpoint is the heuristic gate fired post-turn when
	// the model didn't explicitly call mark_task_done. Default
	// implementation always returns false (heuristic off). Custom
	// implementations could scan the just-completed assistant
	// message for completion patterns.
	ShouldCheckpoint(ctx context.Context, a *Agent) bool

	// CheckpointInstruction returns the system instruction the
	// summarizer LLM call uses. taskNote carries the operator/
	// model-supplied completion detail (the `detail` arg of
	// mark_task_done, or the operator's /done argument).
	CheckpointInstruction(taskNote string) string
}

Checkpointer decides whether to auto-trigger task-boundary checkpoints and produces the summarizer prompt. The mark_task_done tool always triggers a checkpoint regardless of ShouldCheckpoint — the heuristic is for OPTIONAL post-turn auto-fire based on assistant-text patterns ("looks done") and is off by default. Consumers customize by implementing Checkpointer themselves.

func NewDefaultCheckpointer

func NewDefaultCheckpointer() Checkpointer

NewDefaultCheckpointer returns a DefaultCheckpointer. Pass &DefaultCheckpointer{} directly if you want to assert the type; the constructor exists for symmetry with NewDefaultCompactor.

type CompactionResult

type CompactionResult struct {
	// SummaryEventID is the ID of the event the compactor wrote to
	// the session. Empty when no compaction ran (compactor returned
	// no-op, or the call errored before writing).
	SummaryEventID string

	// SummaryText is the full text the model produced. Useful for
	// callers that want to surface the summary in the UI before the
	// next turn runs.
	SummaryText string

	// Duration is wall-clock time spent in the summarizer LLM call.
	Duration time.Duration

	// Skipped is true when the compactor decided not to compact
	// (e.g., ShouldCompact returned false from a programmatic
	// Agent.CompactIfNeeded call). When Skipped is true, the rest of
	// the fields are zero-valued.
	Skipped bool
}

CompactionResult reports what happened on a Compact call.

type Compactor

type Compactor interface {
	// ShouldCompact returns true when the agent should compact before
	// the next turn. Called from Agent.Run's post-turn hook with the
	// agent's usage tracker so the implementation can read context-
	// window state (Tracker.ContextWindowUsed / ContextWindowSize).
	// Returning false is a no-op — the next turn proceeds normally.
	ShouldCompact(ctx context.Context, a *Agent) bool

	// SummarizerInstruction returns the system instruction the
	// compactor LLM call uses. focus carries the operator's optional
	// focus hint (empty when none). The instruction tells the model
	// what kind of summary to produce; the conversation history is
	// supplied as the LLMRequest.Contents.
	SummarizerInstruction(focus string) string
}

Compactor decides when context-window compaction should fire and produces the summary prompt the agent sends to its model. Consumers plug a custom implementation via agent.WithCompactor; the package default (NewDefaultCompactor) covers the common case.

func NewDefaultCompactor

func NewDefaultCompactor() Compactor

NewDefaultCompactor returns a DefaultCompactor with the package- default fallback threshold AND the default per-tier overrides from modeltier.DefaultCompactionThresholds — so small/mid/frontier each get a tier-appropriate trigger out of the box. Pass a &DefaultCompactor{...} literal directly to customize either knob.

type ContextStats

type ContextStats struct {
	// Compaction* report on Mechanism A boundary events.
	CompactionCount     int
	LastCompactionFocus string    // CompactionFocusKey from the last compaction event (empty when none)
	LastCompactionTime  time.Time // zero when none

	// Checkpoint* report on Mechanism C boundary events.
	CheckpointCount    int
	LastCheckpointNote string    // CheckpointNoteKey from the last checkpoint event (empty when none)
	LastCheckpointTime time.Time // zero when none

	// TotalSummaryChars is the aggregate character count of all
	// boundary summary text (compaction + checkpoint) written
	// this session. Proxy for "how much history has been
	// compressed" — useful as a sanity check that compaction is
	// actually doing something.
	TotalSummaryChars int

	// Subtask* report on Mechanism B usage. Count + tokens + cost
	// are accumulated in recordSubtaskUsage; usage.Tracker totals
	// (/stats) include this same cost in their grand total.
	SubtaskCount        int
	SubtaskInputTokens  int
	SubtaskOutputTokens int
	SubtaskCostUSD      float64

	// ModelBreakdown surfaces /stats-style per-model totals when
	// multiple models were used in the session (typically: parent
	// on a frontier model, subtasks on a cheap flash/haiku-tier
	// model via --agentic-small-model). Pulled from
	// usage.Tracker.TotalsByModel; empty when no tracker is wired
	// or only one model has been used (in which case the
	// breakdown wouldn't tell the operator anything new beyond
	// /stats' grand total).
	ModelBreakdown map[string]usage.Totals

	// DigestSavings surfaces the MCP digest wrap's cumulative
	// effect on the parent's context (#223). Structural and agentic
	// paths are broken out because their cost math differs — the
	// renderer labels the block "savings vs. no-digest baseline"
	// since these are hypothetical (what it would have cost without
	// the wrap layer), not real spend reductions. Zero-value when
	// the wrap layer is off or nothing has fired yet.
	DigestSavings usage.DigestSavingsTotals
}

ContextStats is a snapshot view of the three context-management mechanisms wired on the Agent. All fields are zero-value-safe; the consumer renders "no compactions yet" / "no subtasks yet" based on the counts.

Boundary fields (Compaction*, Checkpoint*, TotalSummaryChars) are derived from the session event log on each call. Subtask fields come from in-memory counters bumped in RunSubtask.

type CostCeiling

type CostCeiling struct {
	// MaxTurnUSD is the cap on a single conversation turn's spend
	// (cumulative cost of every model call between one operator
	// inject and the next agent-done state). Tripped → next Run
	// refuses with an ErrCostCeilingExceeded error.
	MaxTurnUSD float64

	// MaxSessionUSD is the cap on the session's cumulative spend
	// across all turns (parent + subtask).
	MaxSessionUSD float64
}

CostCeiling configures the per-turn / per-session spend caps the post-turn hook enforces. Zero or negative values disable that specific ceiling — both fields default to disabled when constructed via the zero value.

type DefaultCheckpointer

type DefaultCheckpointer struct{}

DefaultCheckpointer is the package-default Checkpointer. Heuristic is off (ShouldCheckpoint always false) — mark_task_done + /done are the trigger paths. Prompt mirrors DefaultCompactor's five-section handover plus a "Completion record" preamble that names the just-finished task as the focal point of the summary.

func (*DefaultCheckpointer) CheckpointInstruction

func (c *DefaultCheckpointer) CheckpointInstruction(taskNote string) string

CheckpointInstruction returns the five-section handover prompt with a "Completion record" preamble that names the just- finished task. The model's summary will frame the conversation from "this task is now done" angle rather than the "we're still mid-task" angle DefaultCompactor produces.

func (*DefaultCheckpointer) ShouldCheckpoint

func (c *DefaultCheckpointer) ShouldCheckpoint(_ context.Context, _ *Agent) bool

ShouldCheckpoint returns false. Heuristic-based auto-checkpoint is intentionally off by default — false positives (declaring a task done when the operator is mid-thought) are costly. The mark_task_done tool gives the model an explicit signal it can invoke when it's confident; /done gives the operator the same.

type DefaultCompactor

type DefaultCompactor struct {
	// Threshold is the fallback context-window utilization at which
	// compaction fires when no per-tier override applies (unknown
	// model, or the tier isn't in ThresholdByTier). 0.85 means
	// "compact once we've used 85% of the model's context window."
	// Zero is treated as "use the package default."
	Threshold float64

	// ThresholdByTier overrides Threshold per modeltier classification.
	// Keys are pkg/modeltier tier labels ("frontier", "mid", "small").
	// When the current model classifies to a tier present here, that
	// tier's threshold wins. Empty map (or zero value at a key) falls
	// back to Threshold. Use this to keep frontier sessions at 0.85
	// while compacting small-tier sessions much earlier.
	ThresholdByTier map[string]float64

	// TierClassifier is the function used to look up the current
	// model's tier. Defaults to modeltier.Classify. Override in tests
	// to inject deterministic tier resolutions without depending on
	// the modeltier table's current state.
	TierClassifier func(modelID string) string
}

DefaultCompactor is the package-default Compactor. Triggers on context-window utilization ≥ a per-model threshold (default 0.85 for frontier, 0.65 for mid, 0.35 for small; see pkg/modeltier.DefaultCompactionThresholds) and produces a five-section "teammate handover" summary (current state, files & changes, technical context, strategy & approach, exact next steps) per docs/context-management-design.md §Mechanism A.

Consumers needing a different prompt or trigger logic implement Compactor themselves; this type is a sensible default, not a required base class.

func (*DefaultCompactor) ShouldCompact

func (c *DefaultCompactor) ShouldCompact(_ context.Context, a *Agent) bool

ShouldCompact returns true when the agent's usage tracker reports context-window utilization at or above the resolved threshold for the current model's tier. Returns false when the tracker doesn't yet know the window size (no turn has landed, or the model isn't in usage.ContextWindowSizeFor's table) so a session with an unknown model never triggers premature compaction.

func (*DefaultCompactor) SummarizerInstruction

func (c *DefaultCompactor) SummarizerInstruction(focus string) string

SummarizerInstruction returns the five-section handover prompt. focus, when non-empty, appended as a "Compact focus: <text>" directive so the summarizer prioritizes that thread.

type Mode added in v2.8.0

type Mode int

Mode selects the layer-3 overlay: how the agent should carry itself given who (if anyone) is watching. Set where the agent is built — drivers never mutate a caller-supplied agent (the autonomous driver warns when it sees an interactive-mode agent; see autonomous.Run).

const (
	// ModeInteractive (the default): a user is present and can
	// redirect / answer questions.
	ModeInteractive Mode = iota
	// ModeAutonomous: nobody reads output in real time; narrate for
	// the audit record and never ask questions in output text.
	ModeAutonomous
)

type Option

type Option func(*options)

Option mutates Agent construction. Use the With* helpers below.

func WithAppName

func WithAppName(s string) Option

WithAppName overrides the AppName handed to the ADK runner. Useful when embedding so telemetry and session stores can distinguish multiple agents inside one binary.

func WithBackgroundManager

func WithBackgroundManager(mgr SubagentManager) Option

WithBackgroundManager attaches a BackgroundAgentManager to the agent. The manager's parent back-reference is set during construction so its Spawn calls can read the agent's session triple + session.Service without the consumer plumbing them twice.

Each turn of Agent.Run drains pending alerts from the manager's channel (non-blocking) and prepends them to the prompt the underlying ADK runner sees, so the parent's model is aware of what its background subagents have reported since the last turn.

Pass nil to clear (e.g. for tests that re-construct an agent).

func WithCheckpointer

func WithCheckpointer(c Checkpointer) Option

WithCheckpointer wires a Checkpointer implementation that drives task-boundary checkpoints (Mechanism C of docs/context-management-design.md). When wired, the agent automatically registers the mark_task_done built-in tool — the model can call it to signal task completion, and the post-turn hook in Run promotes that into a pending checkpoint the next Run drains by writing a richer handover record. The TUI's /done slash drives the same path manually.

Pass agent.NewDefaultCheckpointer() for the package default (heuristic off; mark_task_done + /done are the trigger paths; six-section completion-record prompt). Custom Checkpointer implementations let consumers swap in a different prompt or heuristic.

Optional. When nil, Agent.Checkpoint returns ErrNoCheckpointer and the mark_task_done tool is not registered.

func WithCompactor

func WithCompactor(c Compactor) Option

WithCompactor wires a Compactor implementation that drives context-window compaction (Mechanism A of docs/context-management-design.md). When wired, the post-turn hook in Run checks Compactor.ShouldCompact(); if true, the next Run call fires Compact() before its actual work, replacing the pre-summary history with a single summary event.

Pass agent.NewDefaultCompactor() for the package default (threshold 0.85, five-section handover prompt). Custom Compactor implementations let consumers swap in a different prompt or trigger logic.

Optional. When nil, Agent.Compact returns ErrNoCompactor and the post-turn hook is a no-op — compaction has to be wired in explicitly.

func WithCostCeiling

func WithCostCeiling(c CostCeiling) Option

WithCostCeiling wires per-turn and per-session spend caps. Pass a zero-value CostCeiling{} (or 0 in either field) to disable the corresponding bound — at least one must be > 0 for enforcement to run at all. Mirrors the usual WithX option shape.

func WithDescription

func WithDescription(s string) Option

WithDescription overrides the agent's description.

func WithEventHook

func WithEventHook(onEvent func(*session.Event), onTurnEnd func()) Option

WithEventHook wires per-event and end-of-turn observer callbacks. onEvent is called once per session.Event as events stream from Agent.Run — from inside the same iterator tap the watchdog and usage tracker already sit in, so observation is synchronous and ordered relative to the event yield. onTurnEnd is called from the post-turn cleanup that runs after wrapWithCleanup drains the iterator, alongside the watchdog / compaction / checkpoint hooks.

Either callback may be nil to disable that half of the surface. Both nil is legal and turns the option into a no-op; useful when a consumer wraps WithEventHook in a builder that always sets it.

Consumer contract: callbacks must not panic and should return quickly. A slow callback stalls the agent's event stream — synchronous by design so the hook mechanism can rely on ordering (matches pkg/hooks.Dispatcher, which spawns subprocesses with per-command timeouts to bound its own latency).

Single-slot: calling WithEventHook twice replaces the previous binding. Multi-consumer fan-out is the caller's responsibility (wrap two callbacks in one). This matches WithWatchdog's shape.

func WithEventLog

func WithEventLog(h *eventlog.Handle) Option

WithEventLog wires an eventlog.Handle into the agent — the Handle's Service becomes the agent's session.Service (so every event lands in the durable log), and the Handle is stored on the agent so callers can reach back to it for replay/watch via Agent.EventLog().

Equivalent to WithSessionService(h.Service) plus a stash of the Handle for later access; passing nil is a no-op.

func WithExtraInstruction added in v2.8.0

func WithExtraInstruction(s string) Option

WithExtraInstruction appends s as a layer-5 block (repeatable — each call appends another blank-line-separated block, in call order). The encouraged customization path: the harness contract and mode overlay stay intact underneath. Empty strings are dropped.

func WithGate

func WithGate(g *permissions.Gate) Option

the agent's metadata, so it can be surfaced over the attach-mode /tools endpoint (each tool gets a pre-flight `gate_state` field — "allowed" / "denied" / "prompted" / "denied-allow-mode" — without actually consulting the gate at request time). Optional; without it, the /tools endpoint reports an empty gate_state per tool and the TUI's auditing column is blank.

This is metadata-only — the gate that actually mediates tool calls is still the one wired into the tool constructors themselves. The agent does not call this gate; it just exposes a read-only view.

func WithInstruction

func WithInstruction(s string) Option

WithInstruction replaces the assembled system instruction wholesale — the full-replace escape hatch. Layers 1–3 (core, provider quirks, mode overlay) are skipped ENTIRELY; you take on the harness contract yourself (compaction summaries, parallel-dispatch rules — tool-use degradation is on you). Layers 4–5 (WithUserInstruction / WithExtraInstruction) still append after the replacement so a custom base can compose with operator appends.

func WithMeterProvider added in v2.8.0

func WithMeterProvider(mp metric.MeterProvider) Option

WithMeterProvider overrides the OTel MeterProvider backing the agent's metric instruments (gen_ai.agent.invocation.duration and the per-tool gen_ai.tool.execution.duration wrapper). Defaults to otel.GetMeterProvider() resolved at construction time — the daemon installs its provider via telemetry.SetupMetrics before any agent is built, and when metrics are disabled the global is the noop provider, so recording costs nothing. Embedders wanting metrics must likewise install their provider (or pass one here) before calling New. Background subagents always bind to the global provider (the spawn path doesn't thread this option). Primarily useful for tests injecting a ManualReader.

func WithMetricAgentName added in v2.8.0

func WithMetricAgentName(name string) Option

WithMetricAgentName overrides the gen_ai.agent.name attribute value on the agent's metric instruments without changing the agent's actual name (WithName). Metric attribute values must stay low-cardinality: the spawn path names background subagents with MODEL-CHOSEN strings, and stamping those on a histogram would accrete one series per invented name on a long-lived daemon — it passes a fixed class-level value here instead. Defaults to the WithName value, which is operator-configured and bounded.

func WithMode added in v2.8.0

func WithMode(m Mode) Option

WithMode selects the layer-3 overlay (default ModeInteractive). Autonomous consumers — anything driving the agent with no human reading output in real time — should set ModeAutonomous where they build the agent; the in-tree spawn paths (background subagents, RunSubtask, remote spawn) do.

func WithName

func WithName(s string) Option

WithName overrides the agent's display name (visible in OTEL spans).

func WithPostConstruct

func WithPostConstruct(f func(*Agent)) Option

WithPostConstruct registers a callback invoked once the *Agent is fully built (right before New returns). Useful for late- binding patterns where the caller needs the agent pointer to wire something they registered earlier — e.g., an externally- constructed tool whose handler closure captured a *Agent placeholder. The hook fires on the happy path only; if New returns an error the hook is not called.

One hook per agent. Calling WithPostConstruct twice keeps the last one (Option-pattern overwrite semantics, same as other scalar With* options).

func WithSession

func WithSession(userID, sessionID string) Option

WithSession overrides the user/session IDs handed to the ADK runner. Reuse the same pair across Run() calls to preserve conversation history.

func WithSessionService

func WithSessionService(s session.Service) Option

WithSessionService overrides the session.Service handed to the ADK runner. The default is session.InMemoryService(), which loses all state when the process exits. Pass a durable Service (typically the one returned by eventlog.Open(...).Service when wiring the audit log + crash-resume substrate) to persist sessions across runs.

The supplied Service is also exposed via Agent.SessionService() so callers can query session state directly without keeping their own reference. Passing nil restores the default.

func WithStreaming

func WithStreaming(m adkagent.StreamingMode) Option

WithStreaming overrides the streaming mode. Default is StreamingModeSSE (required to receive Partial events).

func WithSubagentMaxDepth added in v2.9.0

func WithSubagentMaxDepth(n int) Option

WithSubagentMaxDepth sets the recursion depth cap applied when THIS agent is exposed as a subagent tool via WithSubagents — the value forwarded to SubagentOptions.MaxDepth at the parent's construction. A subagent at depth >= this cap that is invoked from another subagent gets an error result rather than being allowed to recurse.

0 (the default) means "use the substrate default" (NewSubagentTool's defaultSubagentMaxDepth, currently 2). Declarative subagents thread their config `max_depth` through this option so an operator-set value is honored rather than silently dropped; a plain WithSubagents caller who never sets it keeps the default.

func WithSubagents

func WithSubagents(agents []*Agent) Option

WithSubagents registers each agent as a callable tool the parent's model can invoke by name. The subagent runs through ADK's runner using the parent's session.Service (so its events stream live into the same audit log) with session.Event.Branch set to "<parent_branch>.<subagent_name>" — ADK's contents-processor branch filter then keeps the subagent's events from leaking back into the parent's next-turn LLM request, which preserves context isolation while keeping the audit log unified.

Each subagent's tool name comes from its own WithName value, and its recursion depth cap from its own WithSubagentMaxDepth value (default 2). Use NewSubagentTool directly for the remaining per-subagent overrides (custom tool name, description, branch label).

Resolved at the end of New() so that the parent's session.Service and session triple — set by other With* options — are captured at the point the subagent tools are constructed.

func WithSystemInstructionPrefix deprecated

func WithSystemInstructionPrefix(prefix string) Option

WithSystemInstructionPrefix prepends prefix to the agent's instruction with the pre-#459 semantics: the result is a full replacement (prefix + whatever instruction was set, defaulting to the DefaultInstruction alias), so layer assembly is skipped.

Deprecated: memory belongs AFTER the core, not before it — use WithUserInstruction (layer 4). This survives through v2.8.x for consumers that composed against the old prefix arrangement and is deleted at the next breaking window together with DefaultInstruction.

func WithTools

func WithTools(ts []tool.Tool) Option

WithTools registers a set of tools the agent may call. Order is preserved but immaterial; ADK keys tools by Name.

func WithToolsets

func WithToolsets(ts []tool.Toolset) Option

WithToolsets registers groups of tools (MCP servers, skills, etc.). Each Toolset implements google.golang.org/adk/tool.Toolset and is passed to llmagent.Config.Toolsets.

func WithUsageTracker

func WithUsageTracker(t *usage.Tracker) Option

WithUsageTracker wires a shared *usage.Tracker into the agent so agent-level code (the compactor's threshold check, future per-turn rollups) can read context-window state without the consumer reaching in. The same tracker can be shared with a TUI host that already keeps one for /stats — both populate via usage.Append and read the same totals.

Optional. Nil-safe: components that read the tracker check first and degrade gracefully ("don't trigger threshold-based compaction if we don't know how full the window is").

func WithUserInstruction added in v2.8.0

func WithUserInstruction(s string) Option

WithUserInstruction installs the pkg/instruction loader's output as layer 4 (user memory: AGENTS.md and friends). Deliberately AFTER the core/overlay layers — user instructions take precedence over our defaults by ordinary instruction-following convention, and the stable-first ordering keeps the cached core prefix intact across memory edits (this inverts the deprecated WithSystemInstructionPrefix arrangement on purpose).

func WithWatchdog

func WithWatchdog(w watchdog.Watchdog, onAlert func(watchdog.Alert)) Option

WithWatchdog wires a behavioral watchdog. The agent calls w.ObserveToolCall as tool calls stream by, and w.Check from the post-turn hook. Returned alerts are passed to onAlert if non-nil; when onAlert is nil the alerts are collected and discarded each turn (useful for tests, or for hosts that read the watchdog's own state via a side channel).

Composable with everything else: pass alongside WithCompactor / WithCheckpointer / WithCostCeiling / etc. The watchdog runs in the same post-turn hook the others use.

func WithWatchdogEnforce added in v2.9.0

func WithWatchdogEnforce() Option

WithWatchdogEnforce promotes the watchdog from observe-only ("warn" mode) to a kill switch ("enforce" mode, #623). When set, a Critical alert (today: the repeated-tool-call runaway signal) trips the agent: it emits a watchdog turn-error and refuses subsequent turns until the operator calls ResetWatchdog — the same halt contract as the cost ceiling. No-op unless a watchdog is also wired via WithWatchdog. Warn- mode alerts (non-Critical) never halt, even under enforce.

func WithWatchdogFeedback added in v2.9.0

func WithWatchdogFeedback() Option

WithWatchdogFeedback routes watchdog alerts back into the model's next-turn context ("feedback" mode, #159): after a turn that tripped a signal, the next Run prepends a "watchdog" block carrying each alert's model-facing Guidance to the prompt.

The warn-mode surface it extends reaches an operator — who, on an unattended daemon, is not there, and who even at a terminal can only interrupt a turn already in flight. The party that can stop making the looping call is the model, and it never learned it was looping.

Implied by WithWatchdogEnforce, which is deliberate rather than incidental: an enforce-mode halt is cleared by an operator reset, and a reset resumes a model whose context still ends in the loop it was halted for. Without the injected observation, the very next turn re-issues the same call and re-trips — the reset would be a treadmill.

No-op unless a watchdog is also wired via WithWatchdog.

func WithoutProviderQuirks added in v2.8.0

func WithoutProviderQuirks() Option

WithoutProviderQuirks suppresses layer 2 — for consumers that have measured their model doesn't need the workarounds, or that are running their own probes.

type SubagentManager added in v2.8.0

type SubagentManager interface {
	// AttachParent sets the manager's parent back-reference so its
	// spawn calls can read the agent's session triple + session.Service
	// without the consumer plumbing them twice. Called once during
	// Agent construction.
	AttachParent(*Agent)

	// PrependPendingAlerts drains any pending background alerts
	// (non-blocking) and prepends them to the prompt the underlying ADK
	// runner sees, returning the augmented prompt. Called each turn of
	// Agent.Run.
	PrependPendingAlerts(prompt string) string

	// ListSubagents returns attach-facing metadata for the manager's
	// live subagents. Backs attachadapter.AttachAgents
	// (attach.AgentsProvider).
	ListSubagents() []attach.AgentInfo

	// ListSubagentCatalog returns the CONFIGURED subagent roster —
	// declarative templates + predefined catalog specs — as opposed to
	// ListSubagents' live/spawned instances. Backs
	// attachadapter.AttachSubagentCatalog (attach.SubagentCatalogProvider,
	// #627).
	ListSubagentCatalog() []attach.SubagentCatalogInfo

	// SpawnSubagent spawns a subagent from an attach spec. Backs
	// attachadapter.AttachSpawnSubagent (attach.SubagentSpawner).
	SpawnSubagent(ctx context.Context, spec attach.SubagentSpec) (attach.SubagentSpawnResponse, error)
}

SubagentManager is the narrow seam the core Agent uses to talk to a subagent/background manager without importing the background package (which imports agent, and would otherwise form an import cycle). The concrete implementation lives in pkg/agent/background; wire one via WithBackgroundManager.

Every method is expressed in core/attach/primitive types only — no background-package types leak across this boundary. Callers that need the richer *background.Manager surface (repl, coretui) recover it with background.ManagerOf(agent).

type SubagentOptions

type SubagentOptions struct {
	// Inner is the *agent.Agent to expose as a tool the parent's
	// model can call. The tool's function name comes from
	// Inner.AgentName() (set via agent.WithName), unless overridden
	// via Name. The tool's description comes from Inner's
	// llmagent.Description, unless overridden via Description.
	Inner *Agent

	// Name overrides the function name shown to the parent's model.
	// Empty falls back to Inner.AgentName().
	Name string

	// Description overrides the function description shown to the
	// parent's model. Empty falls back to Inner's Description (or a
	// generic fallback when that's also empty).
	Description string

	// MaxDepth caps recursion depth. A subagent at depth >= MaxDepth
	// that is invoked from another subagent gets an error result
	// rather than being allowed to recurse. Default 2; pass a
	// larger value if your agent topology genuinely needs deeper
	// nesting.
	MaxDepth int

	// Branch overrides the branch label appended to the parent's
	// branch on the subagent's events. Defaults to the tool name
	// (which is Inner.AgentName() unless Name overrides it). The
	// resulting branch is "<parent_branch>.<this>".
	Branch string

	// ParentService, when non-nil, overrides the session.Service
	// the subagent's runner uses. The agent.WithSubagents
	// convenience option fills this in automatically with the
	// parent agent's service so subagent events land in the
	// parent's audit log without any consumer plumbing.
	//
	// When nil, NewSubagentTool falls back to Inner.SessionService()
	// — which is fine for callers who construct subagents
	// pre-wired against the same Handle.
	ParentService session.Service

	// ParentAppName, ParentUserID, ParentSessionID identify the
	// parent's session triple. When set, the subagent runs through
	// the parent's session row (with branch isolation) so cross-
	// session audit queries find both. Empty values fall back to
	// Inner's own AppName/UserID/SessionID. Set automatically by
	// agent.WithSubagents.
	ParentAppName   string
	ParentUserID    string
	ParentSessionID string
}

SubagentOptions configures NewSubagentTool. Inner is required; everything else has sensible defaults.

type SubtaskBudgets

type SubtaskBudgets struct {
	// MaxTurns caps how many model turns the subtask may take.
	// Default is SubtaskDefaultMaxTurns. Subtasks are meant to be
	// single-purpose; >5 turns usually means the question is too
	// open-ended for a subtask and belongs in the parent's loop.
	MaxTurns int

	// MaxWallclock caps wall-clock duration. Default is
	// SubtaskDefaultMaxWallclock. Belt-and-suspenders against a
	// flaky model that streams forever without producing a final
	// turn.
	MaxWallclock time.Duration
}

SubtaskBudgets caps subtask cost in three dimensions. Whichever hits first wins. Returns SubtaskResult{Truncated: true} on budget exhaustion (not an error — caller's model can choose to retry with a wider budget or fall back to running the raw tool).

type SubtaskResult

type SubtaskResult struct {
	// Name echoes SubtaskSpec.Name for trace correlation.
	Name string

	// Digest is the model's final text output across all turns
	// (joined). Empty when Truncated is true and the model
	// produced no final-turn text before the budget hit.
	Digest string

	// Truncated reports that a budget (MaxTurns / MaxWallclock)
	// fired before the model emitted a final TurnComplete.
	// Digest may still hold partial text from earlier turns.
	Truncated bool

	// InputTokens / OutputTokens accumulate across every turn of
	// the subtask. Subtask cost rolls up to the parent's
	// usage.Tracker if WithUsageTracker is wired, so /stats
	// totals reflect everything.
	InputTokens  int
	OutputTokens int
	CostUSD      float64

	// Duration is wall-clock time spent in the subtask. Useful
	// for the "fresh-context is fast" claim — typical subtask
	// completes in <2s on a flash-tier model.
	Duration time.Duration

	// TurnsUsed is the number of model turns the subtask actually
	// took. Useful for budget tuning ("am I hitting MaxTurns
	// often?").
	TurnsUsed int
}

SubtaskResult is what RunSubtask hands back. Digest is the distilled text the subtask's model produced; Truncated flags budget exhaustion so the caller can decide whether the partial answer is useful.

type SubtaskSpec

type SubtaskSpec struct {
	// Name attributes cost + traces. Required (non-empty) so the
	// audit log can distinguish subtask events; the subtask's
	// session-branch derives from this name.
	Name string

	// SystemPrompt is the subtask's role instruction. Since #459 it
	// COMPOSES: the subtask runs on the layered baseline
	// (CoreInstruction + provider quirks + AutonomousOverlay) with
	// this text appended, so it understands compaction summaries and
	// edit sequencing like any other agent. The parent's own
	// instruction is still NOT inherited (fresh context). Set
	// ReplaceSystemPrompt for the pre-#459 bare-prompt behavior.
	SystemPrompt string

	// ReplaceSystemPrompt, when true, makes SystemPrompt a full
	// replacement — no harness layers. Use only when supplying a
	// complete prompt of your own.
	ReplaceSystemPrompt bool

	// UserMessage is the question / instruction the subtask
	// receives. Treated as the operator's first user message in
	// the subtask's brand-new session.
	UserMessage string

	// Tools is the restricted set the subtask may call.
	// Typically a small read-only subset (read_file, grep,
	// fetch_url, etc.). Empty means tool-less.
	Tools []tool.Tool

	// Model overrides the parent's model for this subtask. Nil
	// (the default) means "use the parent's model." Wrapper tools
	// like AgenticReadFile typically point this at a smaller,
	// faster model (haiku-tier / flash-tier) so a "summarize this
	// file" subtask doesn't burn opus tokens.
	Model adkmodel.LLM

	// Budgets caps the subtask's resource use. Zero fields fall
	// back to SubtaskBudgetDefaults.
	Budgets SubtaskBudgets

	// SkipParentUsage suppresses the roll-up of this subtask's cost
	// into the *running* agent's usage.Tracker and ContextStats
	// counters. The subtask's own SubtaskResult still carries full
	// token + cost figures, so callers lose nothing.
	//
	// Set this when the agent executing the subtask is NOT the agent
	// that should be billed. The MCP digest wrap is the motivating
	// case (#717): its LLMFallback closure is bound to the primary
	// agent at boot, but it fires on behalf of whichever session made
	// the MCP call. Rolling up here would bill the primary session for
	// every other session's digests — and, because
	// maybeEnforceCostCeiling reads the tracker, could trip the
	// primary's cost ceiling on traffic it never generated.
	//
	// The correct session recovers the cost from the `savings` sidecar
	// riding on the tool result (pkg/agent/tool_savings_observer.go),
	// so suppressing here moves the charge onto the right books rather
	// than dropping it.
	SkipParentUsage bool
}

SubtaskSpec configures one RunSubtask call.

Directories

Path Synopsis
internal
subsession
Package subsession holds the session-derivation plumbing shared by the two subagent-spawning paths: the core parallel-tool-call path in pkg/agent (subagent.go) and the long-lived background path in pkg/agent/background.
Package subsession holds the session-derivation plumbing shared by the two subagent-spawning paths: the core parallel-tool-call path in pkg/agent (subagent.go) and the long-lived background path in pkg/agent/background.

Jump to

Keyboard shortcuts

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