Documentation
¶
Overview ¶
Package adapter bridges brigada and each agent CLI. One adapter per CLI: it builds the headless command (fresh or resume), parses the CLI's event stream into progress events, detects limit-hit and auth errors, and finds the session id needed to resume a session. It never modifies the CLI and never holds credentials itself; the user signs in through the vendor's own flow.
Every adapter implements the interface whole, both parse methods included: ParseEvent reads the CLI's stdout and ParseErrEvent its stderr, because some failures print only there — a resume that cannot be found before any JSON, a limit or an auth message — and must still become error events. A line with nothing to act on returns false from either method; only the CLI's own errors are errors.
Index ¶
- Constants
- Variables
- func AgentFamily(harness, model string) (string, error)
- func AgentTier(harness, model string) string
- func CredentialEnv(harness string) string
- func EffortForHarness(harness, taskEffort string) string
- func EffortLevels(harness string) []string
- func IsInfraText(text string) bool
- func IsModelRejectedText(text string) bool
- func ModelFamily(model string) (string, error)
- func ModelTier(model string) string
- func ParseToolScope(value string) ([]string, error)
- func ProgressEvents(events []Event) []string
- func ResumeCapable(harness string) bool
- func ToolArgs(raw json.RawMessage) string
- func ValidateEffort(harness, level string) error
- func ValidateTaskEffort(level string) error
- func ValidateTier(tier string) error
- type Adapter
- type ErrorKind
- type Event
- type EventKind
- type Interactive
- type Job
- type SessionWriteback
- type TurnMarker
- type Usage
Constants ¶
const ( TierLight = "light" TierStandard = "standard" TierHeavy = "heavy" )
Task tiers describe how heavy a model a task needs.
Variables ¶
var ResumeCapableHarnesses = map[string]bool{ "claude": true, "codex": true, "opencode": true, "fake": true, }
ResumeCapableHarnesses is the set of harnesses a foreman may run on (design §4.1): the foreman's turns wake headless and resume the project's session, so choosing a foreman refuses a harness whose CLI cannot resume headless and carry the foreman's MCP bridge. This is the one place that set lives; the engine consults it at setup.
var TaskEffortLevels = []string{"low", "medium", "high", "max"}
TaskEffortLevels is brigada's task vocabulary, independent of harness levels.
var Tiers = []string{TierLight, TierStandard, TierHeavy}
Functions ¶
func AgentFamily ¶
AgentFamily uses the model's family, including known CLI defaults. OpenCode's default can serve any provider, so its family is unknown (empty, without error). Kimi serves Moonshot models unless the name identifies another known family. For other unknown aliases it returns the historical harness fallback and an error: standalone runners support custom aliases, while init requires known models.
func AgentTier ¶
AgentTier uses the model's table tier. CLI defaults (codex, kimi and opencode) are standard because their model name is not known to brigada.
func CredentialEnv ¶
CredentialEnv names the environment variable a harness's credential comes from, or "" when the harness logs in some other way (a stored login, an OAuth session). A runner started without the variable cannot authenticate that harness, which the needs-you line for an auth break says.
func EffortForHarness ¶
EffortForHarness maps a task's brigada effort level to the CLI's control. An empty result leaves the CLI variant or effort unspecified.
func EffortLevels ¶
EffortLevels returns the effort levels brigada accepts for harness; nil when there is no fixed list (OpenCode), or no effort control (Kimi).
func IsInfraText ¶
IsInfraText reports whether a failure's text names a connection or DNS failure. The words alone prove nothing about when they were said: the runner keeps the infra kind only for a run that produced no output before it.
func IsModelRejectedText ¶
IsModelRejectedText reports whether a failure's text says the provider rejected the model: unsupported for the account, or nonexistent.
func ModelFamily ¶
ModelFamily identifies a model independently of the CLI/provider serving it. Unknown aliases need an explicit model name in init, rather than a guessed family.
func ModelTier ¶
ModelTier labels a model by its name. Heavy matches take precedence; names without a matching segment serve the standard tier.
func ParseToolScope ¶
ParseToolScope parses a Claude worker tool scope in its flag, config and project-file spelling: Claude Code tool patterns separated by "+", or "none" for the file tools alone. The result is non-nil for none, so a caller can tell an explicit none from no scope at all. A pattern containing a comma cannot ride an --agent value, which splits on commas.
func ProgressEvents ¶
ProgressEvents renders the last few progress events for a heartbeat: one line each, newest last, so the engine's record of a run names what the worker was doing, not just when.
func ResumeCapable ¶
ResumeCapable reports whether harness's CLI can resume a session headless (§4.1), which the foreman's harness must.
func ToolArgs ¶
func ToolArgs(raw json.RawMessage) string
ToolArgs renders a tool call's arguments for an Event's Args: the raw JSON the CLI's stream carried, compacted so two identical calls read identical whatever whitespace the stream put between them. Empty input, or input that is not JSON, renders empty — no arguments identifiable, nothing to compare.
func ValidateEffort ¶
ValidateEffort checks level against harness's accepted set. The empty level (unspecified) is always valid. The error is a usage-grade message naming what is accepted.
func ValidateTaskEffort ¶
ValidateTaskEffort accepts an unset effort, which uses the agent's level.
func ValidateTier ¶
ValidateTier accepts an unset tier, which means standard.
Types ¶
type Adapter ¶
type Adapter interface {
// Name is the adapter's identifier, one per CLI ("claude", "kimi",
// "fake"): the name a runner's advertisement pairs it with (§5.1)
// and the agents table keys on (§11).
Name() string
// Command builds the headless command for job, to run in workdir.
// resumeSessionID is empty for a fresh run; otherwise the command
// resumes that harness session with its context kept (§5.3).
//
// The environment is the adapter's to build, under one policy: the
// worker's own login state is its business — the ambient
// environment, the vendor CLI's tokens and configuration, are what
// it needs to run (§5.3) — but the adapter
// must not add brigada's own secrets to it. The runner enforces the
// line from its side: whatever variables its git credentials ride
// (Config.GitEnv) are stripped from the command's environment
// before it starts, whether the adapter put them there or they came
// from the ambient set.
Command(job Job, workdir, resumeSessionID string) (*exec.Cmd, error)
// ParseEvent parses one line of the CLI's JSON event stream. false
// means the line is not an event the runner tracks — a banner, a
// blank line, anything the CLI prints that is not one of its
// events. Only the CLI's own error event is an error: a stray line
// on its stdout must not fail an otherwise good run.
ParseEvent(line []byte) (Event, bool)
// ParseErrEvent parses one line of the CLI's stderr, which rides
// the same event pipeline as stdout: some failures print only there
// — a resume-not-found before any JSON, a limit or an auth message
// — and must become error events all the same. false means the line
// carries nothing to act on, like a progress note or a warning.
ParseErrEvent(line []byte) (Event, bool)
// ResultText returns the worker's final message from the events it
// parsed: the text the result contract (§5.4) is read from.
ResultText(events []Event) string
// ProbeCommand builds the cheap probe of the CLI for the
// broken-agent recovery (§8 "Health"): a command bound to ctx — a
// version or status check — that exits 0 when the CLI answers. The
// runner runs it against an agent broken by repeated failures with a
// short deadline; a passing probe clears the agent, a failing one
// backs the next probe off. The command is built with ctx so the
// deadline kills it: a hung CLI reads as a failing probe, never a
// stuck runner.
ProbeCommand(ctx context.Context) *exec.Cmd
}
Adapter is what one CLI adapter provides (design §5.2): it builds the headless command for a job, parses the CLI's event stream into progress events, and finds the final message and the session id in what the CLI printed. An adapter never modifies the CLI and never holds credentials itself.
type ErrorKind ¶
type ErrorKind string
ErrorKind sorts an error event: a quota limit hit (the account cools and the job moves to another agent), an authentication failure (the agent is broken until a probe passes), a resume that could not happen (the session is gone or forked; §5.6 restarts fresh in place), and everything else.
const ( ErrLimit ErrorKind = "limit" ErrAuth ErrorKind = "auth" // ErrResumeFailed is a resume that could not happen: the session the // command was told to resume is gone or its conversation cannot be // continued. What answers it is not another resume but a fresh run in // place, carrying the previous attempt's context in its prompt (§5.6) — a session cannot be rebuilt from its id alone. ErrResumeFailed ErrorKind = "resume_failed" ErrOther ErrorKind = "other" // ErrInfra is the CLI failing to reach its provider at all: the // connection or the name lookup failed before the run produced // anything. The environment's failure, never the agent's: the job is // retried as a transient error and no health strike is charged. ErrInfra ErrorKind = "infra" // ErrModel is the provider rejecting the model itself: the account // has no access to it, or it does not exist (a 400 // invalid_request_error). The agent cannot work until the user // fixes its model or the account, so it stays out like an auth break // and no probe clears it. ErrModel ErrorKind = "model" )
type Event ¶
type Event struct {
Usage *Usage
Kind EventKind
Time time.Time // when the adapter parsed it; the runner's clock
Text string
// Args is a tool call's arguments as the CLI's stream carried them,
// empty for a CLI that prints none (or a kind that has none): with
// Text it names the call, so the runner's loop watch (§5.6) tells
// two calls of one tool apart.
Args string
// SessionID may accompany any kind when the CLI puts its id in
// every envelope (including an error before a session-start event).
SessionID string
ErrorKind ErrorKind
// LimitUntil is a limit error's reset time, RFC3339, when the CLI's
// text carried one machine-readably; empty when unknown (§4.4).
LimitUntil string
// Final marks an error event as the CLI's closing verdict on the run
// (codex's turn.failed): it names the failure over any earlier error
// event of the same stream.
Final bool
// Notice marks an error event that is only a warning while the
// turn has not started (codex's startup notices): the runner, which
// tracks each job's turn through TurnMarker, downgrades it to a
// warning until then.
Notice bool
}
Event is one parsed line of the CLI's event stream. Which fields carry data depends on Kind: tool calls fill Text (the tool name), a message fills Text, a session id fills SessionID, an error fills ErrorKind and Text.
func (Event) IsProgress ¶
IsProgress reports whether the event is progress in the sense of §5.6: a tool call starting or ending, or a message. Sessions and errors are not progress.
type EventKind ¶
type EventKind string
EventKind is the shape of one progress event (§5.6): what the runner tracks for heartbeats and, later, stall detection.
const ( // EvToolStart is a tool call beginning; one in flight suspends the // stall clock (§5.6). EvToolStart EventKind = "tool_start" // EvUsage reports counters without advancing the progress clock. EvUsage EventKind = "usage" // EvToolEnd is a tool call finishing. EvToolEnd EventKind = "tool_end" // EvMessage is an assistant message. EvMessage EventKind = "message" // EvSession carries the harness's own session id, written back on // complete so the next session-mode job resumes it (§4.2). EvSession EventKind = "session" // EvError is the CLI reporting a failure whose kind sorts what the // engine does with it (§8: quota, auth, the rest). EvError EventKind = "error" // EvWarning is a CLI notice that is not a failure: the run goes on, // and the runner does not take it for the run's error. EvWarning EventKind = "warning" )
type Interactive ¶
type Interactive interface {
// AttachCommand builds the interactive command for job, to run in
// workdir on the caller's terminal: the adapter wires none of the
// process's streams — stdin is the user's, and the caller connects
// it. The opening prompt (job.Prompt) reaches the CLI the way it
// takes one interactively; the MCP config and the model ride as in
// Command. resumeSessionID is the key's current harness session,
// empty when the foreman has never run; newSessionID is the id the
// client generated for the session this chat forks into — the CLI
// names it itself, so the client knows what to write back on detach
// (§4.2) without reading the CLI's own files.
AttachCommand(job Job, workdir, resumeSessionID, newSessionID string) (*exec.Cmd, error)
}
Interactive is the optional adapter interface for the attach (§4.2 item 1): an adapter whose CLI can carry an interactive terminal session — the user's own chat with the foreman, not a headless run — implements it beside Adapter. The client asserts it when it attaches and builds the command through it; a harness whose adapter does not implement it cannot be attached to, and the client says so rather than falling back to a headless run, which is no chat at all.
type Job ¶
type Job struct {
Prompt string
Model string
// Review selects the harness's read-only review policy, where supported.
Review bool
// Effort is the reasoning-effort level for the run, one of the harness's
// accepted levels (ValidateEffort); empty adds nothing. An adapter whose
// CLI has no effort control refuses a non-empty one.
Effort string
// MCPConfig names the harness's MCP config for the run: a config
// file's path, offered to the CLI through its own flag. It is where
// the `brigada mcp` bridge (§12.2) will hand the foreman's harness
// brigada's tool server; empty — every job today — adds nothing.
MCPConfig string
// ReadDirs names absolute directories outside the worktree the
// worker must be able to read but never write: the extra
// repositories' checkouts (§5.8). The adapter opens them to the
// worker as its CLI allows, and the checkouts' read-only files
// refuse writes all the same; codex's and dsh's sandboxes already
// allow reads outside the workspace, and kimi has no directory
// flag, so those adapters ignore it. Empty adds nothing.
ReadDirs []string
// Tools is the worker's tool scope in Claude's spelling
// (`Bash(go test:*)`), for a harness that maps it onto its own
// permission rules; nil is the Go default scope. Claude takes its
// scope from its adapter's config instead and ignores this.
Tools []string
// StatusLine is a shell command whose one line of output an
// interactive session shows in its status bar, re-run every few
// seconds (§10): the attach sets it to `brigada status --line`, so
// the user sees what the workers are doing while they chat with the
// foreman. Only AttachCommand reads it, and an adapter whose CLI has
// no status bar ignores it; empty adds nothing.
StatusLine string
}
Job is what an adapter needs to build one headless run (§5.2): the composed prompt and the model to run on. The prompt is the engine's product — a fixed, versioned template plus verbatim data (§1.4) — and the adapter passes it through untouched.
type SessionWriteback ¶
type SessionWriteback interface {
// WritebackSessionID names the session id an ended attach leaves the
// key on, bound to ctx for the discovery a fresh chat needs.
WritebackSessionID(ctx context.Context, workdir, resumeSessionID string) (string, error)
}
SessionWriteback is the optional adapter interface for a CLI whose interactive resume keeps its own session id — one the client cannot mint for it the way claude's --session-id takes a caller's id: the release of the attach writes back the id this returns instead of the id the client minted. resumeSessionID is the id the chat resumed — returned unchanged, for the CLI continues under it — and "" means the chat started fresh, its id discovered from the CLI's own session list in workdir, which is why the caller runs this after the CLI exits. An error is the caller's to warn about and answer with the minted id, which the CLI does not know: its next headless turn then refuses the resume and starts fresh in place (§5.6).
type TurnMarker ¶
type TurnMarker interface {
// StartsTurn reports whether the stdout line opens the turn.
StartsTurn(line []byte) bool
}
TurnMarker is the optional adapter interface for a CLI whose stream opens the turn with a line of its own. The adapter itself is shared by concurrent jobs and keeps no stream state; the runner asks it of each stdout line and keeps the job's turn.
type Usage ¶
type Usage struct {
InputTokens *int64 `json:"input_tokens,omitempty"`
OutputTokens *int64 `json:"output_tokens,omitempty"`
CachedInputTokens *int64 `json:"cached_input_tokens,omitempty"`
ReasoningTokens *int64 `json:"reasoning_tokens,omitempty"`
CostUSD *float64 `json:"cost_usd,omitempty"`
ModelCalls *int64 `json:"model_calls,omitempty"`
// CostCumulative marks CostUSD as the running total of a resumable
// session, not this invocation's own spend; the engine turns it into
// a per-job delta. Unset for adapters whose cost is per invocation.
CostCumulative bool `json:"cost_cumulative,omitempty"`
}
Usage contains only counters reported by this invocation. Nil fields are unknown, including on older runners; a reported zero remains known. InputTokens is all input, cache reads included; cached input and reasoning tokens are breakdowns of input and output. ModelCalls counts the model requests the CLI reported, the unit of request-limited plans.
func ParseUsage ¶
func ParseUsage(raw json.RawMessage, input, output, cached, reasoning, cost string) *Usage
ParseUsage reads selected numeric fields independently. Malformed, negative and missing counters are ignored without discarding the enclosing event.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package claude drives the Claude Code CLI headless (design §5.2): one run per job, `claude -p` with stream-json output, resumed by session id when the job carries one.
|
Package claude drives the Claude Code CLI headless (design §5.2): one run per job, `claude -p` with stream-json output, resumed by session id when the job carries one. |
|
Package codex drives the Codex CLI headless (design §5.2): one run per job, `codex exec --json`, resumed by thread id when the job carries one.
|
Package codex drives the Codex CLI headless (design §5.2): one run per job, `codex exec --json`, resumed by thread id when the job carries one. |
|
Package dsh drives DeepSeek Harness (npm @deepseek-ai/dsh, verified on 0.2.1-rc.2) headless: one `dsh --profile headless --json` per worker job, the prompt on stdin, resumed by session id.
|
Package dsh drives DeepSeek Harness (npm @deepseek-ai/dsh, verified on 0.2.1-rc.2) headless: one `dsh --profile headless --json` per worker job, the prompt on stdin, resumed by session id. |
|
Package fake is a scripted CLI adapter for tests (design §5.2): it drives a fake worker — a shell script each test writes — instead of a real agent CLI, so the runner's tests exercise the lease loop, the workspace, the result contract and the check without any vendor CLI.
|
Package fake is a scripted CLI adapter for tests (design §5.2): it drives a fake worker — a shell script each test writes — instead of a real agent CLI, so the runner's tests exercise the lease loop, the workspace, the result contract and the check without any vendor CLI. |
|
Package kimi drives the Kimi Code CLI headless (design §5.2): one run per job, `kimi -p` with stream-json output, resumed by session id when the job carries one.
|
Package kimi drives the Kimi Code CLI headless (design §5.2): one run per job, `kimi -p` with stream-json output, resumed by session id when the job carries one. |
|
Package opencode drives OpenCode 1.19.5 headless: one `opencode run` per worker job, resumed by session id, and the bare TUI for an attached foreman chat.
|
Package opencode drives OpenCode 1.19.5 headless: one `opencode run` per worker job, resumed by session id, and the bare TUI for an attached foreman chat. |