agent

package
v0.331.0 Latest Latest
Warning

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

Go to latest
Published: May 21, 2026 License: AGPL-3.0 Imports: 45 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// TierClassify is for cheap, short-form work: intent routing, tool-
	// catalog narrowing (P0-04 router), reflection summaries (P0-05).
	// Haiku-class models are expected.
	TierClassify = "classify"

	// TierGenerate is the default tier for payload generation (evil
	// portal HTML, BadUSB DuckyScript, generated .sub/.ir/.nfc files).
	// Sonnet-class models are expected.
	TierGenerate = "generate"

	// TierPlan is the default tier for the main agent turn — tool
	// planning, multi-step orchestration, confirmation framing.
	// Sonnet-class models are expected.
	TierPlan = "plan"

	// TierExploit is reserved for highest-stakes decisions (critical
	// risk tools, confirmation explanations). Opus-class models are
	// expected. PromptZero uses this sparingly because Opus is ~5x the
	// price of Sonnet.
	TierExploit = "exploit"
)

Cost tiers consumed by the agent when it needs to pick a model for a particular kind of work. Higher tiers are expected to be more expensive/capable than lower ones. Tiers are strings rather than enums so personas can introduce new ones (e.g. "vision") in YAML without a code change.

View Source
const (
	GroupMetaAudit      = "meta.audit"     // always on
	GroupMetaUtil       = "meta.util"      // always on
	GroupFlipperSystem  = "flipper.system" // storage, loader, power, etc.
	GroupFlipperSubGHz  = "flipper.rf.subghz"
	GroupFlipperIR      = "flipper.rf.ir"
	GroupFlipperNFC     = "flipper.nfc"
	GroupFlipperRFID    = "flipper.rfid"
	GroupFlipperIButton = "flipper.ibutton"
	GroupFlipperBadUSB  = "flipper.badusb"
	GroupFlipperHW      = "flipper.hw" // GPIO / I2C / OneWire
	GroupMarauderWiFi   = "marauder.wifi"
	GroupGen            = "gen"
	GroupWorkflows      = "workflows"
	GroupVision         = "vision"
	// GroupSecurity covers host-side security tools (hash analysis,
	// network scanning, HTTP enumeration). Mirrors the constant of the
	// same name in internal/tools/spec.go.
	GroupSecurity = "security"
	// GroupHostTools covers tools that run on the operator's host machine
	// (firmware extraction, container-bridge tools, binary analysis).
	GroupHostTools = "host.tools"
)

Tool groups are the coarse-grained buckets the router reasons over. Each tool in the agent catalog maps to exactly one group via ToolGroup. Groups named meta.* are always sent to the main model, regardless of the user's apparent intent — they hold audit primitives, general utilities, and structured format tools the agent may need for any task.

View Source
const (
	VerifySeverityNone     = "none"
	VerifySeverityLow      = "low"
	VerifySeverityMedium   = "medium"
	VerifySeverityHigh     = "high"
	VerifySeverityCritical = "critical"
)

Known severity values.

View Source
const HandoffResumeSentinel = "<handoff-resume>"

HandoffResumeSentinel is the prefix used on the synthetic user message injected on resume so the model sees the structured handoff. UI code strips messages with this prefix from rendered transcripts.

View Source
const MinimumConfirmDelay = 2 * time.Second

MinimumConfirmDelay is the minimum wall time that must pass between rendering a high-risk confirmation preview and accepting a user approval keystroke. Two seconds matches the Warp Terminal precedent for risky-command UX: long enough to absorb an accidental reflex, short enough not to feel laggy. Low-risk prompts use the gate too so accidental Enter keypresses on a fast-moving transcript don't silently authorise a transmit.

Variables

View Source
var ErrBlockedByMode = errors.New("tool blocked by operation mode")

ErrBlockedByMode is the sentinel returned by dispatch when the active operation mode (Recon/Intel/Stealth/Assault) does not allow a tool's group. Callers (telemetry, UI) can errors.Is against this so the rejection is distinguishable from a runtime failure.

Layered after ErrReadOnly: dispatch consults readOnly first; mode is checked only when readOnly=false. v0.19.0 introduced the read-only rail; mode was originally slated for removal in v0.20.0 but remained as a useful coarse capability filter (see mode_dispatch_test.go).

View Source
var ErrBudgetExceeded = errors.New("session refused: USD budget exhausted (use /budget set $X to extend)")

ErrBudgetExceeded is returned at the top of Run() when the cost tracker's session USD cap has been crossed. The configured warn callback fires at 80% as a courtesy; this sentinel is the hard stop at 100%. Operators raise the cap with /budget set $X to clear the state. Wraps for errors.Is so REPL / web layers can render a dedicated message rather than a generic "claude API: …" line.

View Source
var ErrReadOnly = errors.New("tool blocked: read-only mode (no writes, no transmits, no execution)")

ErrReadOnly is returned by dispatch when the agent is in read-only mode (SetReadOnly(true)) and the operator (or the LLM) attempts a tool whose risk classification is anything above risk.Low. Read-only is the v0.19.0 replacement for the persona+mode allow-list maze: one boolean, one rule — Spec.Risk == risk.Low passes, anything else is refused.

Functions

func BuildSystemPrompt

func BuildSystemPrompt(p *persona.Persona, hasWiFi, hasWorkflows bool) string

BuildSystemPrompt assembles the system prompt the agent hands to the model at the start of each turn. When a persona is supplied and sets its own SystemPrompt, that preamble replaces the default. The WiFi framing is appended only when the Marauder tool set is still present after persona filtering. The workflow section is appended when composite workflows are registered. The trust-boundary clause is always appended — it governs the <untrusted-hardware-output> wrappers that quarantine attacker-controllable content returned by hardware tools.

func DefaultSessionStore

func DefaultSessionStore() (*session.Store, error)

DefaultSessionStore creates a Store rooted at ~/.promptzero/sessions.

func DeriveTitleFromMessages added in v0.12.0

func DeriveTitleFromMessages(msgs []session.Message) string

DeriveTitleFromMessages reads the persisted message slice and returns a one-line preview of the first non-handoff user message — same shape as the live-history derivation in deriveTitle, just operating on the disk wire format. Used as the /api/sessions fallback for sessions saved before the Title field existed (and for any future state that somehow reached the wire without a title).

func FormatConfirmPreview added in v0.3.0

func FormatConfirmPreview(req ConfirmRequest) string

FormatConfirmPreview renders a multi-line boxed preview for the given ConfirmRequest. The preview pulls well-known fields (frequency, file path, duration_seconds, protocol, data hex, target_os) out of the tool input JSON and surfaces them in a human-reviewable shape before the operator approves. Low-risk tools return the empty string so the caller can fall back to the existing compact prompt.

Output shape (ASCII box, no colors — colors are the caller's responsibility so tests can assert exact content):

┌─ About to run wifi_deauth ──────────────┐
│ risk: critical                          │
│ duration_seconds: 30                    │
└─────────────────────────────────────────┘

The caller is expected to paint this to the screen, invoke ConfirmDelayGate.Show(), then render its own colored prompt line.

func HistorySnapshot added in v0.3.1

func HistorySnapshot(a *Agent) string

HistorySnapshot returns the concatenated user-visible text of the agent's current conversation history. Intended for eval / test scenarios that want to assert the shape of resumed sessions or injected context blocks (e.g. the <handoff-resume> sentinel). Not a stable API.

func PromptTemplateHash added in v0.53.0

func PromptTemplateHash(name string) string

PromptTemplateHash returns the SHA-256 hash (hex, full 64-char) of the embedded prompt template by name. Returns "" for unknown names — callers pre-validate against the known set; an unknown name almost certainly signals a typo and should fail loudly at the call site rather than silently match a different template.

Roadmap P3-31: prompt hashes are recorded on each audit row so regression analysis and the future fine-tune data exporter can distinguish sessions that ran against different prompt versions.

func QuarantineForTest added in v0.3.1

func QuarantineForTest(toolName, output string) string

QuarantineForTest exposes the internal quarantine wrapping helper to the eval harness so adversarial scenarios can assert that hardware-origin output gets framed as untrusted before reaching the main model. Production callers use the unexported quarantineOutput directly.

func QuarantineOutput added in v0.54.0

func QuarantineOutput(toolName, output string, isErr bool) string

quarantineOutput runs sanitizeControlChars on every output and, for hardware-origin tools, wraps the result in <untrusted-hardware-output> delimiters regardless of whether the call succeeded or errored. Structured-internal tools (those in the notWrappedTools allowlist) are never wrapped.

Error strings from hardware-origin tools are wrapped on the same rule as successes: error messages can contain attacker-controlled text (e.g., an SSID embedded in a connection-failure message) so they must be quarantined too.

The wrapping is the countermeasure for prompt-injection attacks where attacker-controllable content (SSIDs, NFC tag URIs, BLE device names, NDEF records, filenames on the SD card) is fed back to the model as tool output. The paired clause in the system prompt tells the model to treat content inside these tags as data, not instructions. QuarantineOutput is the exported entry point for cross-package safety tests (`test/adversarial`, P3-30). The agent's normal dispatch path uses the unexported quarantineOutput sibling; this wrapper exists so the adversarial corpus can call into the production sanitiser + wrapper without re-implementing them. Production agent code should continue to call the unexported sibling — keeping that one private nudges in-package callers toward the centralised dispatch routing.

func SystemPromptHash added in v0.53.0

func SystemPromptHash(p *persona.Persona, hasWiFi, hasWorkflows bool) string

SystemPromptHash returns the SHA-256 hash (hex) of the assembled system prompt that BuildSystemPrompt would produce for the same arguments. Pure function of its inputs — safe to compute at audit time without re- rendering the full prompt. The hash is what the audit exporter records; the prompt content itself remains in memory only, never persisted to the audit DB.

A nil persona uses the default-persona prompt (matches BuildSystemPrompt).

func ToolGroup added in v0.3.0

func ToolGroup(name string) string

ToolGroup returns the logical group a registered tool belongs to. The registered Spec.Group is the source of truth — Mode.Allows() (persona blocking) and the dynamic router's narrowing both consult the same value, so they cannot disagree.

For names not in the registry (or specs that left Group at the zero value), the function falls through to a name-prefix heuristic so a tool registered without an explicit Group still classifies. Tools that match neither path map to GroupMetaUtil, which is the safe default — shipping them every turn never breaks correctness.

func ToolNames

func ToolNames(hasMarauder bool) []string

ToolNames returns the names of every tool currently registered with the agent, in builder order. Exposed so the CLI can render /tools without reaching into the private builder functions.

Types

type Agent

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

func New

func New(client *anthropic.Client, flip *flipper.Flipper, cfg *config.Config) *Agent

func NewForTest added in v0.3.1

func NewForTest(model string) *Agent

NewForTest constructs a minimal Agent suitable for the eval harness (internal/eval) and cross-package integration tests. No Anthropic client, no Flipper — callers wire up whatever the test needs. Not part of the stable API; may change without notice.

func (*Agent) AttackConstraint added in v0.3.0

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

AttackConstraint returns a copy of the current technique-ID constraint set. Returns an empty slice when no constraint is installed.

func (*Agent) AttackIndex added in v0.3.0

func (a *Agent) AttackIndex() *attack.Index

AttackIndex returns the installed ATT&CK index, or nil. Used by /report and the REPL /attack command.

func (*Agent) Breaker added in v0.54.0

func (a *Agent) Breaker() *breaker.Counter

Breaker returns the active circuit-breaker counter, or nil when the feature is disabled. Exposed for /stats and operator-facing /reset-breaker commands.

func (*Agent) DeleteSession added in v0.3.0

func (a *Agent) DeleteSession(id string) error

DeleteSession removes a session from the attached store and auto- purges any per-session snapshots (P1-09). Callers that only want the session file removed can use sessionStore.Delete directly; DeleteSession is the sanctioned path for the CLI because it keeps the snapshot tree in lockstep with the session's lifecycle. Errors from the snapshot purge are best-effort — a failed snapshot purge leaves orphaned backup files but doesn't block the session deletion itself.

func (*Agent) DisableDynamicCatalog added in v0.3.0

func (a *Agent) DisableDynamicCatalog()

DisableDynamicCatalog reverts to the full catalog on every turn. Primarily useful for tests that share an agent and want to toggle the feature between cases.

func (*Agent) EnableDynamicCatalog added in v0.3.0

func (a *Agent) EnableDynamicCatalog()

EnableDynamicCatalog opts the agent into per-turn tool narrowing via the Haiku-class router. Off by default; operators enable it through config once they've measured the tradeoff (typically a ~500 ms latency bump in exchange for 60-80 % fewer tool-description tokens per turn).

func (*Agent) ListSessions

func (a *Agent) ListSessions() ([]session.State, error)

ListSessions returns every saved session known to the attached store.

func (*Agent) Marauder added in v0.9.0

func (a *Agent) Marauder() *marauder.Marauder

Marauder returns the attached Marauder client, or nil when unconnected. Read-only access for callers (e.g. setup.go probing firmware) that hold the agent reference but should not own the client lifecycle.

func (*Agent) Mode added in v0.13.0

func (a *Agent) Mode() mode.Mode

Mode returns the currently-active operation mode. Returns mode.ModeStandard when no mode has been explicitly set. Lock-free because dispatch is called from Run with a.mu already held; this getter would otherwise deadlock.

func (*Agent) ModelFor added in v0.3.0

func (a *Agent) ModelFor(tier string) string

ModelFor returns the model name the agent should use for the given cost tier. Resolution order:

  1. The active persona's Models[tier] if present.
  2. The defaultModelsByTier table for the tier.
  3. The session's base model (a.model) — final fallback.

Unknown tiers fall straight through to the base model so code that introduces a new tier name is always safe. The function takes the agent mutex for a brief read — callers already holding it can use modelForLocked instead.

func (*Agent) NewSession added in v0.12.0

func (a *Agent) NewSession() string

NewSession resets the in-memory history and starts a fresh session id so subsequent turns persist under a new file. Returns the new id. No-op when no session store is configured beyond clearing history.

func (*Agent) Persona

func (a *Agent) Persona() *persona.Persona

Persona returns the currently active persona, or nil when the default (unrestricted) behaviour is in effect.

func (*Agent) PersonaSnapshot added in v0.2.5

func (a *Agent) PersonaSnapshot() *persona.Persona

PersonaSnapshot returns the currently active persona without taking a.mu. Intended for read-only callers (debug endpoints, status panels) that must remain responsive even while Run holds the agent mutex. Returns nil when the default persona is active.

func (*Agent) ReadOnly added in v0.19.0

func (a *Agent) ReadOnly() bool

ReadOnly reports whether the read-only safety rail is engaged. Used by REPL banner rendering, /status, and the catalog narrowing in buildTools.

func (*Agent) RenameSession added in v0.12.0

func (a *Agent) RenameSession(id, title string) error

RenameSession sets the human-friendly title on a saved session. Empty title clears it (the UI will fall back to a derived preview).

func (*Agent) Reset

func (a *Agent) Reset()

func (*Agent) ResumeSession

func (a *Agent) ResumeSession(id string) error

ResumeSession loads a saved session and replaces the in-memory history with its messages. The tool_use / tool_result blocks round-trip via the Raw field on session.Message, so the model sees an identical prior conversation.

func (*Agent) Run

func (a *Agent) Run(ctx context.Context, userInput string) (string, error)

func (*Agent) RunTool added in v0.3.1

func (a *Agent) RunTool(ctx context.Context, tool string, params map[string]interface{}) (string, error)

RunTool exposes the agent's tool-dispatch path so external orchestrators (Campaigns runner, MCP server) can invoke individual tools without going through the full Run loop. The two safety gates that protect Run are applied here too:

  1. audit.RequireOpen — High/Critical tools are refused when no audit log is wired. Fail-closed: an unattended runner cannot silently execute destructive tools without leaving a trace.
  2. confirmCb gate — when a confirm callback is installed and the resolved risk meets confirmThreshold, the operator is asked before dispatch. Mirrors the Run-loop gate at agent.go:797.

Outputs are NOT quarantine-wrapped (no <untrusted-hardware-output> tags) because RunTool callers consume the result as data, not as model context. If you intend to feed RunTool output back to an LLM, wrap it through QuarantineForTest / quarantineOutput at the call site.

Audit records are written on success and failure exactly as Run would record them. ctx cancellation aborts an in-flight tool call.

func (*Agent) SaveSessionAs

func (a *Agent) SaveSessionAs(name string) error

SaveSessionAs persists the current history under a caller-supplied id (typically a human-friendly name). The auto-save session continues to write under its original id.

func (*Agent) SessionID

func (a *Agent) SessionID() string

SessionID returns the current session's identifier (empty until a store is attached).

func (*Agent) SetAttackConstraint added in v0.3.0

func (a *Agent) SetAttackConstraint(techniques []string)

SetAttackConstraint limits the agent's per-turn tool catalog to tools tagged with at least one of the given ATT&CK technique IDs. Pass an empty slice to clear the constraint. Unknown IDs are kept verbatim — the filter simply won't match any tool for them, which is the conservative behaviour when a user pastes a technique that isn't in the curated registry yet.

func (*Agent) SetAttackIndex added in v0.3.0

func (a *Agent) SetAttackIndex(idx *attack.Index)

SetAttackIndex wires an ATT&CK index onto the agent so the router and the runtime constraint filter (SetAttackConstraint) can resolve tool-to-technique mappings. Nil detaches the index.

func (*Agent) SetAuditLog

func (a *Agent) SetAuditLog(l *audit.Log)

SetAuditLog attaches the audit log and wires the per-session PersonaContextResolver (P3-31) so each recorded entry picks up the active persona's version + a hash of the system prompt that would be presented for the current tool config. The resolver is a closure over the agent so a mid-session persona switch updates the next audit row's PersonaVersion + PromptHash without a re-wire.

func (*Agent) SetBreaker added in v0.54.0

func (a *Agent) SetBreaker(b *breaker.Counter)

SetBreaker attaches the per-tool circuit breaker (P3-28 second half). When non-nil, every tool error feeds the breaker and a trip prepends a <circuit-breaker-open> block to the model-facing output so the LLM sees an explicit "stop hammering this" cue instead of looping. Pass nil to disable; production wiring uses breaker.New(0) to pick up the default 3-strike threshold.

func (*Agent) SetBruce added in v0.9.0

func (a *Agent) SetBruce(c *bruce.Client)

SetBruce attaches a Bruce ESP32 backend client. Nil disables Bruce Specs (handlers short-circuit via tools.Deps.RequireBruce).

func (*Agent) SetBudgetCheckCallback added in v0.23.0

func (a *Agent) SetBudgetCheckCallback(cb func() error)

SetBudgetCheckCallback installs a pre-flight gate consulted at the start of every Run() turn. The callback returns nil to allow the turn or a non-nil error to refuse it before any tokens burn. Production wiring (cmd/promptzero/setup.go) tests cost.Tracker.BudgetExceeded() and returns ErrBudgetExceeded when the session USD cap has been crossed.

Pass nil to clear. Lock-free: stored under a.mu like the other agent callbacks. The callback is invoked while a.mu is held, so it MUST NOT reach back into the agent — keep it a thin predicate.

func (*Agent) SetBusPirate added in v0.9.0

func (a *Agent) SetBusPirate(c *buspirate.Client)

SetBusPirate attaches a Bus Pirate 5 universal-bus probe client. Nil disables buspirate_* Specs.

func (*Agent) SetConfirmCallback

func (a *Agent) SetConfirmCallback(f ConfirmFunc)

SetConfirmCallback registers an interactive gate consulted before any tool whose classified risk meets or exceeds the confirm threshold runs. Passing nil disables the gate. Non-interactive surfaces (MCP, web) leave this unset so tools execute without prompting.

func (*Agent) SetConfirmIdleTimeout added in v0.2.5

func (a *Agent) SetConfirmIdleTimeout(d time.Duration)

SetConfirmIdleTimeout overrides how long confirmWithIdleTimeout waits for an operator response before treating silence as a deny. A zero or negative value restores the default (5 minutes).

func (*Agent) SetConfirmThreshold

func (a *Agent) SetConfirmThreshold(l risk.Level)

SetConfirmThreshold configures which risk level triggers a confirmation prompt. Tools classified at or above the threshold are gated. Defaults to risk.High.

func (*Agent) SetDetectorEngine added in v0.3.0

func (a *Agent) SetDetectorEngine(e *rules.DetectorEngine)

SetDetectorEngine installs a rules.DetectorEngine. When set, the agent runs registered detectors after every tool dispatch and appends each verdict in a <detector-verdict> block on the tool result so the main model can factor the signal into its next turn (e.g. a deauth that reports success but the detector calls suspicious).

func (*Agent) SetFaultier added in v0.9.0

func (a *Agent) SetFaultier(c *faultier.Client)

SetFaultier attaches a Faultier USB voltage-glitcher client. Nil disables glitch_* Specs.

func (*Agent) SetGenLLM

func (a *Agent) SetGenLLM(p provider.Provider)

func (*Agent) SetGenerator

func (a *Agent) SetGenerator(g *generate.Generator)

func (*Agent) SetMarauder

func (a *Agent) SetMarauder(m *marauder.Marauder)

func (*Agent) SetMaxToolsPerTurn

func (a *Agent) SetMaxToolsPerTurn(n int)

SetMaxToolsPerTurn overrides the per-turn tool-call cap. A non-positive value resets the cap to the default. Callers typically leave this alone — it exists so tests can force early termination and future config can expose the knob without changing Run's internals.

func (*Agent) SetMode added in v0.13.0

func (a *Agent) SetMode(m mode.Mode)

SetMode swaps the active operation mode. The mode constrains which tool groups dispatch will accept; see internal/mode for the per-mode allow-lists. An empty Mode resets to mode.ModeStandard (the default, behaviour-preserving profile).

Layers after SetReadOnly: dispatch consults readOnly first, then mode. Both gates are independently useful — read-only is a hard no-write rail; mode is a coarse capability profile (e.g. Recon blocks transmit groups; Stealth blocks high-noise groups).

func (*Agent) SetPersona

func (a *Agent) SetPersona(p *persona.Persona)

SetPersona swaps the active operator persona. The persona's SystemPrompt replaces the default preamble on the next streamed request and its tool allowlist filters the advertised tool set. Passing nil clears any active persona and restores default behaviour. Callers typically pair this with Reset() so a mid-turn handoff doesn't sandwich two system prompts inside the same assistant context.

func (*Agent) SetRAGIndex added in v0.3.1

func (a *Agent) SetRAGIndex(idx *rag.Index)

SetRAGIndex installs a custom RAG index. Nil restores the default embedded corpus on the next docs_search call.

func (*Agent) SetReadOnly added in v0.19.0

func (a *Agent) SetReadOnly(v bool)

SetReadOnly toggles the read-only safety rail. When v is true, dispatch refuses any tool whose Spec.Risk is above risk.Low — no writes, no transmits, no emulation, no payload generation. Buys an operator a hard guarantee that the session cannot mutate the Flipper, the Marauder, the SD card, or anything off-host.

The flag is independent of the confirm gate — read-only is a refusal, not a "confirm first". Pair with --confirm-risk if you also want confirms on Low-risk reads (e.g. audit_export from a sensitive session).

Lock-free atomic, matching SetMode's contract for the same reason (dispatch reads it under a.mu held, must not re-acquire).

func (*Agent) SetRetryNotifyCallback added in v0.21.0

func (a *Agent) SetRetryNotifyCallback(cb func(RetryNotice))

SetRetryNotifyCallback installs a per-attempt retry observer. Called once per backoff window with the attempt number, the configured max, and the error that triggered the retry. Pass nil to clear.

Lock-free: stored under a.mu like the other agent callbacks.

func (*Agent) SetSessionStore

func (a *Agent) SetSessionStore(s *session.Store)

SetSessionStore wires a session store so Run auto-saves after every turn and ResumeSession / SaveSessionAs / ListSessions become usable. Safe to leave nil — persistence is opt-in.

func (*Agent) SetSnapshotManager added in v0.3.0

func (a *Agent) SetSnapshotManager(m *snapshot.Manager)

SetSnapshotManager wires an optional pre-write SD snapshotter. Writes through fileformat_edit capture the prior file content into the per-session snapshot tree so /rewind can roll them back. Nil disables the feature.

func (*Agent) SetStreamErrorCallback

func (a *Agent) SetStreamErrorCallback(f func(err error))

SetStreamErrorCallback registers a hook that fires when the upstream Messages.NewStreaming call returns an error. Wired to the cost Tracker so consecutive network failures flip the offline banner.

func (*Agent) SetTargetMemory added in v0.3.1

func (a *Agent) SetTargetMemory(s *targetmem.Store)

SetTargetMemory installs the persistent target store. Nil leaves the target_* tools inert (dispatch returns a friendly error) — callers who failed to open the DB at startup still get a working agent.

func (*Agent) SetTextDeltaCallback

func (a *Agent) SetTextDeltaCallback(f func(TextDelta))

func (*Agent) SetToolStatusCallback

func (a *Agent) SetToolStatusCallback(f func(ToolEvent))

func (*Agent) SetToolStreamCallback added in v0.55.0

func (a *Agent) SetToolStreamCallback(f func(streaming.Frame) bool)

SetToolStreamCallback registers a per-frame callback for tools that opted into streaming dispatch (P3-28 first half). The callback is invoked once per partial frame, in arrival order; dispatch blocks until the consumer drain completes so no frame arrives after the dispatch returns. Pass nil to disable streaming dispatch entirely — opted-in tools then fall back to their non-streaming Handler.

Return value semantics: true keeps the stream alive, false triggers abort-early. On false, dispatch closes the sink's Aborted() channel and cancels the per-tool context; honouring producers wrap up and return a partial result via the normal final-string path. Producers that ignore both signals will run to completion (no forced kill) — abort-early is cooperative.

func (*Agent) SetUIContext added in v0.9.1

func (a *Agent) SetUIContext(view, path string)

SetUIContext records the latest browser navigation state (view + path) so buildUIContextBlock can inject it into the next turn prefix.

func (*Agent) SetUsageCallback

func (a *Agent) SetUsageCallback(f func(u Usage))

SetUsageCallback registers a per-response token counter. Fires once per successful streamOnce with the message's Usage block, including prompt-cache read / creation tokens. Pass nil to disable.

func (*Agent) SnapshotManager added in v0.3.0

func (a *Agent) SnapshotManager() *snapshot.Manager

SnapshotManager returns the currently installed snapshot manager, or nil when snapshots are disabled. Exposed for /rewind list/restore commands that operate against the live agent.

func (*Agent) ThinkingBudgetFor added in v0.3.1

func (a *Agent) ThinkingBudgetFor(tier string) int64

ThinkingBudgetFor returns the extended-thinking token budget for the given tier. Returns 0 when no budget is configured (thinking disabled). Values are clamped to the supported range so a misspecified persona always produces a valid request instead of surfacing the Anthropic API error to the operator:

  • below 1024 (Anthropic minimum) → raised to 1024.
  • above maxThinkingBudget (64 Ki tokens, comfortably under every model's output ceiling once added to responseBudget) → clamped to maxThinkingBudget. Pre-v0.161 the docstring claimed these values were "clamped by buildCachedRequest at send time" but the actual code scaled MaxTokens to fit, so a typo'd persona with `thinking: { plan: 1000000000 }` produced a request the API rejected with a cryptic 400.

Takes a.mu briefly.

func (*Agent) UIContext added in v0.9.1

func (a *Agent) UIContext() (view, path string)

UIContext returns the last view and path the web UI reported.

type ConfirmDelayGate added in v0.3.0

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

ConfirmDelayGate records when a confirmation prompt was first shown and reports whether the enforced delay has elapsed. The gate is time-source-agnostic (the clock can be stubbed for tests); callers invoke Show() once the prompt is visible, then check Open() (or Remaining()) before accepting a decision keystroke.

Intended usage at a REPL:

g := NewConfirmDelayGate(MinimumConfirmDelay)
g.Show()
for {
    key := readKey()
    if key == "y" && !g.Open() {
        // swallow — user pressed too fast
        continue
    }
    ...
}

func NewConfirmDelayGate added in v0.3.0

func NewConfirmDelayGate(delay time.Duration) *ConfirmDelayGate

NewConfirmDelayGate builds a gate with the given minimum delay. The gate is closed until Show() is called.

func (*ConfirmDelayGate) Open added in v0.3.0

func (g *ConfirmDelayGate) Open() bool

Open reports whether enough time has elapsed since Show() for an approval keystroke to be accepted.

func (*ConfirmDelayGate) Remaining added in v0.3.0

func (g *ConfirmDelayGate) Remaining() time.Duration

Remaining returns how much of the delay window is left. A zero or negative return means the gate is open.

func (*ConfirmDelayGate) Show added in v0.3.0

func (g *ConfirmDelayGate) Show()

Show starts the clock on the delay window. Call once the prompt text has been rendered to the terminal. Safe to call multiple times — each call resets the countdown, useful if the prompt is redrawn on a resize.

type ConfirmFunc

type ConfirmFunc func(ctx context.Context, req ConfirmRequest) ConfirmResponse

ConfirmFunc is the callback type used by SetConfirmCallback. Implementations must block until the user (or some other authority) returns a ConfirmResponse. Honouring ctx cancellation is recommended — a cancelled ctx should return {Decision: DecisionDeny} so the agent short-circuits cleanly.

type ConfirmRequest

type ConfirmRequest struct {
	Tool  string
	Input json.RawMessage
	Risk  risk.Level

	// Diff is an optional unified-diff preview of the file the tool is
	// about to write. Populated by the confirmation flow for
	// medium-risk file-write tools whose Spec advertises a non-nil
	// WriteIntent (see internal/tools.Spec.WriteIntent). Empty when
	// the tool isn't a file write, the flow couldn't fetch the
	// existing content, or the new content is identical to the old.
	// UIs render it as a `<pre>` / colored block above the action
	// buttons.
	Diff string
}

ConfirmRequest describes a pending tool invocation the UI is asked to approve before the agent runs it.

type ConfirmResponse added in v0.3.0

type ConfirmResponse struct {
	Decision Decision
	Revision string
}

ConfirmResponse is what a confirm callback returns. Decision is the primary signal; Revision carries free-form text when Decision == DecisionRevise — the model will see it as a fresh user turn telling it what to change about the pending tool call.

type Decision

type Decision int

Decision is the user's reply to a ConfirmRequest.

const (
	DecisionApprove    Decision = iota // run this one tool
	DecisionDeny                       // skip this tool, feed "user denied" back
	DecisionApproveAll                 // run this and every remaining tool in the current turn
	DecisionRevise                     // skip this tool; inject the operator's revision as a user turn so the model re-plans
)

type HandoffArtifact added in v0.3.0

type HandoffArtifact struct {
	Findings     []HandoffFinding `json:"findings,omitempty"`
	OpenThreads  []HandoffThread  `json:"open_threads,omitempty"`
	Blocked      []HandoffBlocked `json:"blocked,omitempty"`
	TurnsCovered int              `json:"turns_covered"`
	GeneratedAt  time.Time        `json:"generated_at"`

	// DeviceStateAtCompact pins the Flipper snapshot we had at
	// handoff-generation time (fork, firmware, battery, SD info). Used
	// by /session resume and /report to render "session state at
	// pause" without re-probing the device. Optional — BuildHandoff
	// leaves it nil and callers who have a flipper.State on hand call
	// WithDeviceState on the result.
	DeviceStateAtCompact json.RawMessage `json:"device_state_at_compact,omitempty"`
}

HandoffArtifact is a structured summary of what happened in a session, emitted whenever history is compacted or an operator asks for a resumable snapshot. Callers (/report, /session resume, future Campaigns runner) consume the JSON shape directly rather than scraping prose out of the conversation.

The schema is deliberately shallow and grep-friendly so the LLM can reason over it without deep destructuring:

{
  "findings":      [{"tool":"...","count":N,"last_seen":"..."}, ...],
  "open_threads":  [{"text":"...","role":"user"}, ...],
  "blocked":       [{"tool":"...","code":"...","message":"..."}, ...],
  "turns_covered": N,
  "generated_at":  RFC3339
}

func BuildHandoff added in v0.3.0

func BuildHandoff(history []anthropic.MessageParam) HandoffArtifact

BuildHandoff derives a HandoffArtifact from a conversation history via pure heuristics — no LLM call. Designed to be cheap enough that it can run on every autosave without introducing latency.

Future enhancement (tracked as follow-up to P1-08): synthesize a richer narrative via Haiku at TierClassify when available. The shape stays the same; the heuristic fields just get smarter.

func (HandoffArtifact) JSON added in v0.3.0

func (h HandoffArtifact) JSON() string

JSON serialises the artifact to a compact wire representation. Used both for session.State persistence and for injection into the model turn as a <handoff> prefix.

func (HandoffArtifact) WithDeviceState added in v0.3.0

func (h HandoffArtifact) WithDeviceState(state any) HandoffArtifact

WithDeviceState stamps the given device state (marshalled as JSON) onto the artifact. Returns the mutated receiver so callers can chain: `BuildHandoff(history).WithDeviceState(state)`. Pass a nil value to clear.

type HandoffBlocked added in v0.3.0

type HandoffBlocked struct {
	Tool    string `json:"tool"`
	Code    string `json:"code,omitempty"`
	Message string `json:"message,omitempty"`
}

HandoffBlocked captures a tool failure so resumption can prefer different tactics instead of re-running the same doomed call. Populated by parsing structured ToolError JSON out of tool_result blocks; free-form error strings fall back to the Message field.

type HandoffFinding added in v0.3.0

type HandoffFinding struct {
	Tool     string `json:"tool"`
	Count    int    `json:"count"`
	LastSeen string `json:"last_seen,omitempty"` // best-effort preview of the last result
}

HandoffFinding counts tool invocations of one kind. Useful because a session that ran 12 wifi_scan_ap calls is telling a different story from one that ran 1 wifi_deauth.

type HandoffThread added in v0.3.0

type HandoffThread struct {
	Text string `json:"text"`
	Role string `json:"role"`
}

HandoffThread captures an unresolved user request — a user message that wasn't followed by an assistant "done" response yet.

type ProspectiveCritique added in v0.3.1

type ProspectiveCritique struct {
	// Risk is the classifier's opinion on whether the proposed
	// tool call is coherent: "ok" / "unclear" / "risky".
	Risk string `json:"risk"`
	// Confidence 0.0-1.0 indicating the classifier's certainty.
	Confidence float64 `json:"confidence"`
	// Concerns enumerates specific issues — wrong frequency, missing
	// prerequisite, scope violation, etc.
	Concerns []string `json:"concerns,omitempty"`
	// Recommendation is a short action hint for the main model when
	// Risk != "ok".
	Recommendation string `json:"recommendation,omitempty"`
}

ProspectiveCritique is the structured output of a prospective pass. Greppable for downstream consumers (report generator, detector engine, future constrained planner). Risk and Confidence mirror the VerificationVerdict shape so the eventual dashboard stays consistent across checkpoints.

type RetryNotice added in v0.21.0

type RetryNotice struct {
	Attempt     int
	MaxAttempts int
	Backoff     time.Duration
	Err         error
}

RetryNotice carries the per-attempt retry telemetry surfaced through Agent.retryNotifyCb. The REPL shows this as a one-line status update so the operator knows the turn is being recovered rather than wedged.

type TextDelta

type TextDelta struct {
	Text string
}

TextDelta carries a single chunk of streamed assistant text. Tool calls are reported separately through SetToolStatusCallback.

type ToolCatalogEntry

type ToolCatalogEntry struct {
	Name        string
	Description string
}

ToolCatalogEntry pairs a registered tool's name with its description. Used by /tools to render each entry with a short description alongside the name.

func ToolCatalog

func ToolCatalog(hasMarauder bool) []ToolCatalogEntry

ToolCatalog returns every registered tool's name + description, in the same builder order as ToolNames.

type ToolError added in v0.3.0

type ToolError struct {
	// Code is a machine-matchable failure class. Uses snake_case.
	// Examples: "flipper_timeout", "marauder_disconnect",
	// "storage_not_ready", "unknown_error".
	Code string `json:"code"`

	// Tool is the tool name that errored. Included so a detector or
	// audit consumer doesn't have to re-correlate with the surrounding
	// tool_use block.
	Tool string `json:"tool"`

	// Message is the original human-readable error text. Sanitised of
	// ANSI/control bytes — mirrors the quarantine pass applied to
	// successful output.
	Message string `json:"message"`

	// Excerpt is the last ~500 sanitised bytes of device I/O when
	// available. Gives the LLM textual context without shipping the
	// whole serial transcript.
	Excerpt string `json:"excerpt,omitempty"`

	// Remediation is a short (1-3 item) list of suggested next steps
	// derived from the error pattern. Examples: "reposition card",
	// "increase timeout_seconds", "reconnect BLE". Empty when no
	// heuristic matches.
	Remediation []string `json:"remediation,omitempty"`

	// Retryable signals whether blind retry has any chance of
	// succeeding. false for configuration / capability errors
	// ("not ready", "unknown protocol") so the main model doesn't
	// burn tool-call budget on hopeless retries.
	Retryable bool `json:"retryable"`

	// DeviceState is the Flipper's state at the time of failure.
	// Optional — when nil the consumer should fall back to the
	// per-turn state-oracle block. Populated by the agent when a
	// flipper.State probe is available at dispatch time.
	DeviceState *flipper.State `json:"device_state,omitempty"`
}

ToolError is the canonical shape of a failed tool result, serialised as JSON into the tool_result content block. Replaces the old free-form "error: <message>" string so reflexion (P0-05), detectors (P1-10), and report generation (P1-11) can pattern-match on structure rather than scraping text.

Fields stay flat so the LLM can reason over the shape without nested destructuring. DeviceState is optional — the state-oracle block (P0-03) already ships the current state on every user turn, but capturing it at failure time gives forensic consumers (report generator, post-hoc debugging) a pinned snapshot of what the device looked like when the call blew up.

func NewToolErrorForTest added in v0.3.1

func NewToolErrorForTest(toolName string, err error, excerpt string) ToolError

NewToolErrorForTest exposes the ToolError classifier to cross- package test code (internal/eval). Mirrors newToolError. Not a stable API.

func (ToolError) JSON added in v0.3.0

func (e ToolError) JSON() string

JSON renders the error into the string form the agent splices into the tool_result content block. The encoder can only fail on invalid UTF-8 in Message / Excerpt, both of which pass through sanitizeControlChars first — so we swallow the error and fall back to a minimal struct representation.

type ToolEvent

type ToolEvent struct {
	Phase    string
	Name     string
	Input    json.RawMessage
	Duration time.Duration
	Output   string
	Err      bool
}

ToolEvent describes one tool invocation phase. Phase is "start" when execution begins (Duration/Output are zero) and "finish" when it completes.

type ToolExample added in v0.3.0

type ToolExample struct {
	Input string // JSON for the tool's input params, e.g. `{"file":"/ext/subghz/garage.sub"}`
	Note  string // short human-readable outcome, e.g. "replays a garage-door capture"
}

ToolExample is a single canonical input → outcome pair for a tool's description. Examples are rendered into the prompt-cached tool definition so the model sees concrete usage patterns without any per-turn cost. Keep each example short — two lines max — so the cumulative description stays under ~1 KB.

type TranscriptEvent added in v0.12.0

type TranscriptEvent struct {
	Kind      string          `json:"kind"`
	Text      string          `json:"text,omitempty"`
	ToolUseID string          `json:"tool_use_id,omitempty"`
	Name      string          `json:"name,omitempty"`
	Input     json.RawMessage `json:"input,omitempty"`
	Output    string          `json:"output,omitempty"`
	IsError   bool            `json:"is_error,omitempty"`
}

TranscriptEvent is one frontend-renderable record produced from a persisted session.Message. Kinds: "user_text", "assistant_text", "tool_use", "tool_result". Empty kinds are skipped by callers.

func SessionTranscript added in v0.12.0

func SessionTranscript(state *session.State) []TranscriptEvent

SessionTranscript flattens a saved session's messages into the frontend-renderable event stream. Synthetic <handoff-resume> messages are dropped so resumed sessions don't show internal context as a chat bubble. Tool inputs are passed through as raw JSON; tool results are rendered as plain text (the SDK's tool_result content is already a list of text blocks for the formats we use).

type Usage added in v0.3.0

type Usage struct {
	InputTokens         int64
	OutputTokens        int64
	CacheReadTokens     int64
	CacheCreationTokens int64

	// Model identifies the upstream model that produced this usage
	// block. Populated from the resolved tier-model for each call (e.g.
	// the plan tier for a main turn, the classify tier for a router
	// narrowing). Downstream cost trackers can use it to bill at the
	// per-call rate via cost.Tracker.AddUsageFullForModel — falling
	// back to the tracker's configured model when Model is "".
	Model string
}

Usage reports token consumption for one successful streamOnce call. InputTokens and OutputTokens are the usual Anthropic counters; the two cache fields track prompt-cache hits (read) and misses that created a new cache (creation). A healthy session shows steadily growing CacheReadTokens and occasional CacheCreationTokens spikes when the cached prefix rotates.

type VerificationVerdict added in v0.3.0

type VerificationVerdict struct {
	Severity       string   `json:"severity"`
	FailureModes   []string `json:"failure_modes,omitempty"`
	Recommendation string   `json:"recommendation,omitempty"`
	Verified       bool     `json:"verified"`
}

VerificationVerdict is the structured output of a verify pass.

Jump to

Keyboard shortcuts

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