agentcli

package
v0.0.314 Latest Latest
Warning

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

Go to latest
Published: Jul 22, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package agentcli abstracts the headless agent CLI the quality-management spine shells out to for isolated reviews and summaries. satellites hardcoded claude's flag surface (claude -p --append-system-prompt …); satelle routes every subprocess through a Runner driven by a CONFIG TEMPLATE, so the operator picks their agent and its exact argv in `.satelle/agents.toml` and no reviewer code names a binary or a flag directly.

A command string is a command template: the first token is the binary, the rest are argv tokens that may carry the placeholders {system}, {tools}, {model}, {settings}, and {payload}. At call time satelle substitutes each placeholder into its own argv token (so a multi-line system prompt or a JSON payload stays a single argument). The work-item payload is ALWAYS also written to the child stdin (dual delivery): stdin-first CLIs (claude -p) keep using stdin alone; argv-first CLIs (e.g. grok -p {payload}) include {payload} in the template.

agents.toml bindings carry a FULL command template (or "in-loop"/empty). Bare single-token CLI names are NOT accepted on the agents.toml path — the operator must see the real argv. DefaultClaudeCommand / DefaultGrokCommand are the canonical templates init seeds and migrate expands into; NewRunner still generates those templates for init/migrate/detection only.

Index

Constants

View Source
const (
	CLIClaude = "claude"
	CLICodex  = "codex"
	CLIGrok   = "grok"
)

Supported agent CLI identifiers.

View Source
const (
	InterfaceCommand = "command"
	InterfaceACP     = "acp"
)

Interface names for RunnerFromBinding (mirror config.Interface* without importing config — agentcli must not depend on the config package).

View Source
const DefaultClaudeCommand = "" /* 166-byte string literal not displayed */

DefaultClaudeCommand is the claude preset template — the satellites gate argv reproduced behind the template seam, hardened with a --disallowedTools denylist so the reviewer's grant is a CEILING (deny wins over allow) over any permissions the repo's .claude/settings.json would otherwise inherit. {system} is the gate/skill body, {tools} the allow-grant, {model} the optional model (dropped, with its flag, when unset). The work-item body rides on stdin only — this template does NOT place {payload} on -p, so Claude is not double-fed; the {payload} placeholder is still available for other harness templates (sty_5cf4a1fb dual delivery).

The denylist keeps the work-tree MUTATORS off (Write, Edit, NotebookEdit, and Bash — a shell redirect writes files whatever the user's ~/.claude permission allows; sty_892517e7) so a reviewer can never modify the repo it judges — the read-only invariant. The reviewer's default allow-grant ({tools}) is read-only (Read, Grep, Glob) and needs no shell: the substrate it reasons about is materialised as markdown under .satelle, so it reads it directly. A repo MAY widen the grant in .satelle/agents.toml (transparently), but the default is read-only.

View Source
const DefaultGrokCommand = "" /* 251-byte string literal not displayed */

DefaultGrokCommand is the grok preset template — the proven dogfood reviewer command (this repo's own [reviewer]) behind the single-token "grok" preset. Grok is argv-first, so the work-item rides on -p {payload} (and also stdin, dual delivery). Unlike claude, the read-only grant is BAKED IN (--tools with grok's own tool names read_file,grep,list_dir, plus --deny on every mutator) rather than drawn from {tools}: the reviewer's default {tools} is Claude tool names (Read,Grep,Glob), which grok does not understand — feeding them to grok was the exact failure this preset removes. To widen grok's grant, author a full command template instead of the bare preset. {model} is dropped (with -m) when unset, so grok falls back to its own default unless the binding pins one (e.g. grok-4.5).

Variables

This section is empty.

Functions

func Available

func Available(name string) bool

Available reports whether the named CLI's binary is on PATH. Known CLI names (claude/grok/codex) are looked up directly — availability does not route through the agents.toml preset resolver (which no longer accepts bare tokens).

func Detect

func Detect() string

Detect returns the first supported agent CLI found on PATH (claude preferred), or "" when none is installed. Used by the install-time selection.

Types

type Request

type Request struct {
	SystemPrompt string // {system}: appended as the system prompt (the gate/skill body)
	// Payload is the work-item / transition JSON. Dual delivery (sty_5cf4a1fb):
	// always written to the subprocess stdin, and substituted into {payload} when
	// that token appears in the harness template (one argv token). Empty Payload
	// is still delivered (empty stdin + empty argv token if {payload} is present)
	// — unlike {model}/{settings}, empty does not drop a preceding flag.
	Payload      string
	AllowedTools string // {tools}: comma-separated tool grant
	Model        string // {model}: optional model override; "" drops the placeholder
	// Effort is {effort}: optional reasoning/thinking level (sty_657f77b9);
	// "" drops the placeholder AND a directly preceding flag (like Model).
	Effort string
	// Settings is {settings}: a pre-marshalled JSON object mirroring claude's
	// settings.local.json schema (env/model/permissions), already ${VAR}-resolved
	// by the caller (config.ResolveAgentEnvs / agentstep.buildRequest) — agentcli
	// never sees the raw map, only this JSON string, like Payload. "" drops the
	// placeholder AND a directly preceding flag, exactly like an empty Model, so a
	// binding with no settings emits no --settings arg. Passed INLINE (no
	// temp-file), so a secret it carries never touches disk. Values MAY be
	// secrets, so like Env it is never included in Command() evidence, logs, or
	// error strings.
	Settings string
	Dir      string // working directory for the subprocess
	// Env sets environment variables on the subprocess, layered onto the parent
	// env with these keys winning (composeEnv). Already ${VAR}-resolved by the
	// caller (config.ResolveAgentEnvs). Values MAY be secrets (an API token), so
	// Env is NEVER included in Command() evidence, logs, or error strings
	// (sty_001558ce).
	Env map[string]string
	// Sink, when non-nil, receives a live TEE of the subprocess's stdout/stderr AS
	// IT RUNS (stderr lines prefixed "[stderr] "), so a caller can write it to a
	// tailable per-dispatch log an operator watches while a long dispatch runs —
	// the only way to see a hang or an approaching timeout before the SIGKILL
	// (sty_0aa67b7f). It never affects the accumulated bytes runProcess returns for
	// the final JSON/usage parse; nil is a no-op (the default, unchanged behaviour).
	// Sink.Write errors are ignored (best-effort, like the reviewer/executor logs).
	Sink io.Writer
}

Request is one headless agent invocation.

type Runner

type Runner interface {
	// Name reports the agent CLI identifier (the template's binary).
	Name() string
	// Command reports the resolved command/harness template (binary + argv with the
	// {system}/{tools}/{model}/{settings}/{payload} placeholders intact) — a
	// concise, body-free description of HOW the agent is invoked, recorded as
	// invocation evidence (sty_fb3e0873). It never expands the rubric body or the
	// payload bytes (the literal {payload} token stays in the string when present).
	Command() string
	// Run executes the agent over req and returns its raw stdout.
	Run(ctx context.Context, req Request) ([]byte, error)
}

Runner invokes an agent CLI headlessly and returns its stdout.

func NewRunner

func NewRunner(name string) (Runner, error)

NewRunner returns the Runner for a bare CLI NAME — the preset. An empty name defaults to claude; "grok" expands to the grok preset; "codex" is the not-yet-mapped stub; an unknown name errors. Callers with a full command template use RunnerFromCommand instead.

func RunnerFromBinding added in v0.0.275

func RunnerFromBinding(iface, command string) (Runner, error)

RunnerFromBinding resolves an agents.toml transport + command to a Runner (epic:agent-dispatch-transport). iface is "command" (default when empty) or "acp".

  • command / empty: same as RunnerFromCommand (full argv template; in-loop → nil).
  • acp: spawn line only (no {system}/{payload} substitution on argv); ACP session protocol carries system/payload. Does not silently fall back to command.

func RunnerFromCommand added in v0.0.180

func RunnerFromCommand(command string) (Runner, error)

RunnerFromCommand resolves an agents-layer command binding to a Runner. An empty or "in-loop" command returns (nil, nil): no agent-CLI runner, so the caller keeps its configured default. A SINGLE non-in-loop token is rejected (bare CLI presets removed — write a full command template, or run satelle init to migrate). A MULTI-token command is a literal command template: the first token is the binary, the rest the argv template.

type UsageResult added in v0.0.132

type UsageResult struct {
	InputTokens  int
	OutputTokens int
	TotalTokens  int
	Duration     time.Duration
}

UsageResult is the cost of one agent invocation — token counts and, when the caller times the run, its wall-clock duration. Populated from a machine-readable agent envelope (claude's `--output-format json`); a plain-text harness yields the zero value, so cost capture degrades gracefully (sty_a699ad14). It never carries the env or any secret — only numbers.

func UnwrapUsage added in v0.0.132

func UnwrapUsage(stdout []byte) ([]byte, UsageResult)

UnwrapUsage splits an agent's raw stdout into the INNER result text (what verdict parsing consumes, unchanged) and its token UsageResult. Recognized envelopes:

  • Claude `--output-format json`: unwraps `.result` and captures `.usage`
  • Grok `--output-format json`: unwraps `.text` (usage zero when absent)

Otherwise stdout is returned verbatim with a zero UsageResult — plain-text harnesses keep working and simply report no cost. Duration is set by the caller (UnwrapUsage measures only tokens).

Jump to

Keyboard shortcuts

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