agent

package
v2.8.0-dev.3 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 28 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 (
	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 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 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 = `You are a helpful assistant. Be concise and accurate.

For non-trivial work — multi-file edits, architectural choices, or asks with multiple valid approaches — sketch your plan in 1-3 sentences before acting so the user can redirect cheaply. Skip the preamble for trivial asks (typo fixes, single-line changes, narrowly-scoped tasks with one obvious solution); just do them.

Tools execute in parallel by default. Execute multiple independent tool calls in parallel when feasible — searching, reading files, independent shell commands, or editing different files. When investigating code, if you need to read multiple files or grep multiple directories, issue all the tool calls in a single response; do not execute them one by one.

Do not issue multiple ` + "`edit_file`" + ` or ` + "`write_file`" + ` calls targeting the same path in one response — those must run sequentially across turns so each edit sees the prior result; parallel writes to the same file race and corrupt state. Efficiency is secondary to correctness: if you are unsure whether two operations are independent, run them sequentially.

Earlier conversation may have been summarized into context for you in one of two shapes: "[Conversation compacted…]" framing (we hit the context wall mid-task and the prior turns were condensed), or "[The prior task is complete…]" framing (the prior task closed cleanly and a handover record replaces its history). Both arrive wrapped at the start of your context, both are authoritative shared history. Read FROM them when the user references prior work — what was discussed, what files were touched, what was decided, recap, summary, status — rather than re-running tools to rediscover what's already recorded there. The conversation continues in both cases; treat the framing as picking up an in-progress session, not as a fresh start.`

DefaultInstruction is the system instruction applied to every agent that doesn't override it via WithInstruction. Comprises a baseline helpfulness/concision directive plus a parallelism mandate adapted from google-gemini/gemini-cli's prompt patterns (packages/core/src/prompts/snippets.ts).

The parallelism mandate is load-bearing for Gemini, which otherwise emits one tool call per assistant turn even when independent operations are obviously batchable. Direct measurement (dev/parallel-probe/) shows Gemini-3.1-pro-preview-customtools without this instruction never batched across 65 search turns; Claude is less affected but still benefits marginally.

Exported so consumers building on WithInstruction can reuse the baseline + mandate verbatim: `agent.WithInstruction(agent.DefaultInstruction + "\n\n" + extra)`.

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).

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 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 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.

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) 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) 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) 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

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

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) 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

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) 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) 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.

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) 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

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

SetAttachEmitter installs (or clears, when f is nil) the callback the agent uses to push typed events onto the attach SSE event stream. The attach broadcaster calls this on first subscriber (wiring its Emit method) and again with nil when the last subscriber disconnects.

When a tracker is wired via WithUsageTracker, SetAttachEmitter 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

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) 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

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.

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 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 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 overrides the system instruction.

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 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. Use NewSubagentTool directly for per-subagent overrides (custom name, description, depth cap, 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

func WithSystemInstructionPrefix(prefix string) Option

WithSystemInstructionPrefix prepends prefix to the agent's default instruction. Used for memory loading: AGENTS.md / CLAUDE.md / GEMINI.md project memory becomes part of the system prompt rather than the user's first message.

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 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.

type SubagentManager

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

	// 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. Replaces
	// agent.DefaultInstruction for this subtask only; the parent's
	// instruction is NOT inherited (the subtask runs in fresh
	// context).
	SystemPrompt string

	// 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
}

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