Documentation
¶
Overview ¶
Package agent is the embeddable agent loop: it builds requests, calls the Upstream, parses responses, dispatches tools, and applies Mechanisms at the loop's hook points. It owns the Turn/Step state machine, the serializable conversation state, and typed Event emission, and holds no ambient process or filesystem state — every state root is injected via Config. Sub-agent orchestration (privileges ≤ parent) and the Plan / Ask-Before / Auto modes live here too.
In the ADR-0010 layout it is the engine layer: it imports internal/domain for the public types and internal/provider for the Responder seam, and never imports the root apogee package. The root facade re-exports the Agent handle and forwards New / Resume here. See ADR 0001 (embeddable, steppable loop) and ADR 0007 (Step/Turn and the quiescent boundary). The real loop body, sub-agents, and modes land in Phase 1; P0.6 seeded the single-Turn slice this package now hosts.
Index ¶
- type Agent
- func (a *Agent) AbortExchange()
- func (a *Agent) ClearContext() error
- func (a *Agent) Close() error
- func (a *Agent) Compact(ctx context.Context) (skipped bool, err error)
- func (a *Agent) ConfineToWorkspace() bool
- func (a *Agent) Mode() domain.Mode
- func (a *Agent) Run(ctx context.Context) (domain.StepResult, error)
- func (a *Agent) SetConfineToWorkspace(confine bool)
- func (a *Agent) SetMode(m domain.Mode)
- func (a *Agent) Snapshot() (domain.Session, error)
- func (a *Agent) Step(ctx context.Context) (domain.StepResult, error)
- func (a *Agent) Submit(in domain.UserInput) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Agent ¶
type Agent struct {
// contains filtered or unexported fields
}
Agent is a single embeddable Apogee agent instance. It owns the loop, conversation state, tool dispatch, and Mechanism application. It holds no process-global state: every state root is injected through Config, so many Agents can run in one process against isolated directories (the property the bench relies on for isolation — ADR 0001). The root apogee package re-exports it as an alias (type Agent = agent.Agent); its methods are the public surface.
An Agent is not safe for concurrent use by multiple goroutines; drive one Agent from one goroutine (Step/Run), and observe it from another only via its EventSink.
func New ¶
New constructs an Agent from cfg. It validates the configuration — including the Auto-mode/Confinement gate (ADR 0004) and the Mechanism ordering graph (ADR 0003, a constraint cycle is a startup error) — and returns an error rather than silently degrading a misconfigured surface. The root facade forwards apogee.New here, binding the real OpenAI-compatible provider client at cfg.Endpoint (P1.1).
func Resume ¶
Resume reconstructs an Agent from a prior Session snapshot. Config supplies the live delegates (Approver, Confiner, EventSink) and state roots again — only the serializable conversation state comes from snap. External connections (MCP, network) reconnect fresh; no server-side state is restored (ADR 0008).
func (*Agent) AbortExchange ¶
func (a *Agent) AbortExchange()
AbortExchange discards an interrupted Exchange and returns the Agent to a clean quiescent boundary that accepts the next Submit. It rolls the conversation back to the boundary the Exchange began at — dropping the un-answered user message and any tool Turns committed so far — and clears inExchange. It is a no-op when no Exchange is open.
It is the interactive host's counterpart to the Step-driven resume path. After a cancel, Step leaves the Exchange OPEN on purpose so a Step-driven host (the bench) re-Steps to re-attempt the Turn (see cancelTurn). A host with no resume affordance — the TUI, where Esc means "stop, scrap it" — calls this instead, so the next /clear or message is accepted rather than rejected with ErrInputPending. Like Snapshot, it is valid only at a quiescent boundary: no worker may be driving the Agent when it is called (the host calls it only after the worker has returned its cancellation), preserving the single-goroutine contract.
func (*Agent) ClearContext ¶
ClearContext drops the model-facing conversation history while preserving the rest of the loop state — the Turn counter keeps advancing, allow-for-session approvals and the autonomy mode survive, and the visible TUI transcript (a separate structure the host owns) is untouched. It is the engine half of the /clear command: the model forgets prior turns; the human keeps their scrollback. Valid only at a quiescent boundary; calling it mid-Exchange is refused (ErrInputPending) so a half-streamed Turn is never orphaned. The Agent stays snapshot-safe after it returns.
func (*Agent) Close ¶
Close releases the Agent's resources. Because tools are stateless across Turns (ADR 0008), there is no live tool state to flush — Close tears down the provider client, any MCP connections, and the log sink. The Phase-0 slice holds no such live resources (the responder is in-process and hermetic), so Close is a no-op today; it exists now so embedders write the correct lifecycle before Phase 1 adds real teardown.
func (*Agent) Compact ¶
Compact triggers generative Compaction on demand — the engine half of the /compact command. It summarizes the conversation and Replaces the folded history with a single summary message (internal/context.Compact), keeping the protected prefix verbatim. Valid only at a quiescent boundary; calling it mid-Exchange is refused (ErrInputPending) so a half-streamed Turn is never orphaned, mirroring ClearContext. The Turn counter is untouched and the Agent stays snapshot-safe after it returns. A summary-call failure leaves the conversation unchanged.
skipped reports that the conversation was too small to be worth folding (the reducer's Result.Skipped — no upstream call, conv untouched), so the caller can say "nothing to compact" and leave the context gauge alone rather than falsely claiming a compaction. It is always false on error (a fault is not a skip).
func (*Agent) ConfineToWorkspace ¶
ConfineToWorkspace reports whether Auto's blast radius is currently fenced to the workspace (ADR 0012). It reads the live flag under the lock, so a concurrent SetConfineToWorkspace (/confine from the UI) is observed safely from the worker goroutine.
func (*Agent) Mode ¶
Mode reports the Agent's current autonomy mode. It reads the live mode under the lock, so a concurrent SetMode (Shift+Tab from the UI) is observed safely from the worker goroutine.
func (*Agent) Run ¶
Run steps the loop until the Exchange completes (a final no-tool response), cancellation, or a loop-level error — a convenience wrapper over Step for hosts that do not need Turn-level control. It returns the StepResult of the Step that ended the loop (StatusExchangeComplete or StatusCancelled). Each intermediate Turn still returns at its own quiescent boundary, so a cancel delivered through ctx is honoured at the next boundary exactly as it is under Step. The bench drives Step directly.
func (*Agent) SetConfineToWorkspace ¶
SetConfineToWorkspace changes Auto's blast radius for subsequent tool calls: true (the default) fences confinable subprocess writes to the workspace and gates what cannot be fenced; false is the user's explicit "I am the sandbox" — Auto then runs every call unconfined with the user's full privileges, which is safe ONLY on a disposable machine (ADR 0012, as amended 2026-07-21). It is the engine half of /confine off|on; the Agent never flips it on its own initiative, and the ladder itself is untouched either way.
It is safe to call from another goroutine (the UI) while a Step runs: the per-call Resolution reads the flag through ConfineToWorkspace() under the same lock, so the change lands on the NEXT tool call with no rebuild — exactly like SetMode. It changes only THIS Session; nothing is written to disk (persisting the host acknowledgement is the host's job, not the engine's).
Sub-agents: a child spawned AFTER a toggle inherits the new value (newChildAgent reads the live flag at spawn, as it does for the mode); one already mid-flight keeps the value it was spawned with, so the toggle can neither loosen nor tighten a running delegation.
func (*Agent) SetMode ¶
SetMode changes the autonomy mode for subsequent tool calls. It is safe to call from another goroutine while a Step runs: the tool menu (Plan filter) and the per-call Resolution both read the mode through Mode() under the same lock, so the change lands on the next read with no registry rebuild. A switch to Auto is safe even where fs-confinement is unavailable — the subprocess surface gates through Approval ("confine if you can, gate if you can't", ADR 0012), so no eligibility precheck is needed here.
func (*Agent) Snapshot ¶
Snapshot captures the Agent's conversation state at the current quiescent boundary as a copyable, serializable value (ADR 0001/0007). It is valid only at a boundary (between Steps). Apogee exposes snapshot/resume; it exposes no fork — the bench composes forking by deep-copying a Session and the sandbox directory.
Domain owns the Session envelope and its version; the engine owns the opaque State payload, so Snapshot serializes the engine's loop state (conversation + turnIndex + inExchange + pending input — internal/agent/state.go) into it (ADR 0010).
func (*Agent) Step ¶
Step advances the loop exactly one Turn and returns at a quiescent boundary — no in-flight stream, no in-flight tool call, conversation state fully serializable (ADR 0007). Streaming tokens and Approval prompts happen *inside* a Step (via the EventSink and Approver). Snapshot and Resume are valid only at the boundary Step returns at.
Cancellation: cancelling ctx abandons the in-flight Upstream call or tool and returns at the next quiescent boundary with StepResult.Status == StatusCancelled and conversation state left serializable — never half-streamed (ADR 0007).
Recovery: a panic in a tool or Mechanism is caught at that extension boundary, converted to an ErrorEvent, and the loop degrades to the quiescent boundary rather than unwinding into the host (ADR 0007 / ADR 0002). Step returns a non-nil error only for loop-level faults the Agent itself cannot localise.