CLI Reference
The avenor command-line tool runs agents, supervises them over time, inspects their output, and answers their questions. This page catalogs every subcommand, its flags, and how they fit together.
Overview
Avenor operates in three modes:
- Single run —
avenor run. Start an agent, wait for it to finish. - Long-lived supervisor —
avenor stable. A process that lives for hours or days, spawning child agents on demand via the control plane. - Orchestration — Combine
stable+control spawn/cancel/prompt+watchto coordinate multi-phase workflows, permission handling, and real-time visibility.
All modes produce NDJSON event streams on disk, which you query with watch. Permissions are answered via answer or the control plane. A bare avenor invocation prints the command list and exits 0.
avenor run
Starts a single agent and blocks until it finishes or times out.
avenor run [flags]Flags
| Flag | Default | Description |
|---|---|---|
--agent | (none) | Agent name; used to resolve configured model from opencode config if --model is not set |
--prompt | (none) | Inline prompt text; mutually exclusive with --prompt-file |
--prompt-file | (none) | Path to prompt file; mutually exclusive with --prompt |
--loop-file | (none) | Path to multi-phase loop config (JSON, YAML, or TOML); enables looping mode (incompatible with --resume) |
--team-file | (none) | Path to a parallel-team config; enables team mode. See team.md |
--roster-file | (none) | Path to a roster map (JSON, YAML, or TOML) for direct selection, or the root workflow fallback |
--roster-entry | (none) | Entry key within --roster-file for a direct spawn; invalid with a workflow file |
--label | (none) | Free-form label for log correlation; appears in events and sentinel |
--dir | . | Working directory for the agent |
--resume | (none) | Resume an existing session by ID; incompatible with --loop-file. Messaging is off for this path, so a poo session created with messaging on may fail to resume if its recorded tools are unavailable |
--server-url | (none) | Long-lived ACP server endpoint; required for --backend opencode-http |
--backend | opencode-acp | Runtime backend: opencode-acp, agy, gemini-acp, cursor-acp, codex-app-server, opencode-http, pi, claude |
--pony-config | (none) | Path to the pony backend JSON config; required for --backend pony |
--model | (none) | Backend-specific model ID; if not set, resolved from opencode config via --agent |
--thinking | (none) | Backend-native reasoning level; validated against the effective backend |
--claude-mcp-wait | 0 (off) | Hold the first prompt of a claude run until this long after launch (Go duration, at most 60s), so MCP servers Claude Code connects in the background are available to the first turn. Direct runs only; rejected with other backends, with --loop-file/--team-file, and when not shorter than --progress-timeout. See backends.md |
--on-event | (none) | Path to write NDJSON event stream; events are discarded if unset. A final symlink is refused, so the event log cannot be redirected into another artifact's file |
--sentinel-file | (none) | Path to write a completion sentinel (exit code, session ID, stop reason); also derives permission handler unless --permission-handler is set |
--result-file | (none) | Path to write the run's final assistant message as plain text. Direct runs only (not with --loop-file/--team-file). Written atomically before the sentinel; the sentinel then carries a RESULT=<path> line |
--pid-file | (none) | Path to write the run's own pid at startup, removed when it exits. Track a backgrounded run by this pid instead of a launcher's $! |
--permission-handler | (derived) | Permission resolver: file:<path> for file-based answers, or omitted for socket/auto-approve only. If --sentinel-file is set and --permission-handler is not, defaults to file:<sentinel-base> |
--auto-approve | false | Automatically approve ordinary permission requests. Questions that require user input still wait for an answer |
--persist-sessions | false | Persist the backend session (pi: a session directory instead of --no-session, enabling post-hoc transcripts and cross-process resume). Omitted inherits AVENOR_PI_SESSIONS |
--control-socket | (none) | Unix socket path for control plane; enables remote commands while session is running |
--http-debug | (none) | HTTP debug adapter bind address (e.g., 127.0.0.1:8080); enables /debug/status and /debug/events endpoints |
--timeout | 0 (disabled) | Overall session timeout (Go duration: 30s, 5m, etc.); fires after duration regardless of progress |
--progress-timeout | 0 (disabled) | Session progress timeout; fires if no event received for this duration |
--max-retries | 0 | Maximum retry attempts on transient failure (exit code 1, or a rate-limited turn); emits avenor.retry and avenor.error events (see events.md) |
--run-id | (generated) | Correlation ID for this run; auto-generated if not set |
--permission-claim-timeout | 0 (disabled) | Optional deadline for a connected control client to answer a permission request. With 0, fallback occurs only after all control clients disconnect |
Flag validation errors exit 2. These include unknown flags, malformed values, and non-empty --prompt and --prompt-file together. Errors raised after flag parsing exit 1, including a missing prompt, an invalid roster or workflow file, or --loop-file with --team-file.
Examples
Run a single agent with a prompt file:
avenor run --prompt-file my-prompt.txt --label "data-prep" \
--on-event /tmp/events.jsonl --sentinel-file /tmp/run.sentinelRun with auto-retry on transient failure (up to 3 attempts):
avenor run --prompt "find bugs" --max-retries 3 \
--backend opencode-http --server-url http://localhost:8080 \
--on-event /tmp/events.jsonlResult and pid files
A caller that runs avenor run unattended usually needs two things the event log makes awkward: the run's final answer and its pid.
--result-file <path> writes the final assistant message as plain text — the same text the terminal session.end carries as final_output (the backend's own final output when it supplies one, otherwise the text of the run's last turn). The file is written atomically before the sentinel, so a reader that waits for the sentinel never sees a partial answer, and the sentinel then ends with RESULT=<path>. A run that ends with no final message writes no file and no RESULT= line.
The file is written only when the run's last turn settled on the backend's terminal event. A turn that settled with a failure still has a final message, so a failed run writes the text it settled with; a cancelled run, a timed-out run, and a run whose attempt or resume failed before the backend settled write no file and no RESULT= line, because the reply collected so far is a prefix of an answer, not the run's final message. SIGINT and SIGTERM both end the run at exit code 130 with a KILLED sentinel carrying STOP_REASON=cancelled. An answer left by an earlier run at the same path is removed just before the first prompt, once the provider and session are up, so a run that fails before then leaves that answer alone.
--pid-file <path> writes the run's own pid once the setup that mode can fail has passed, and removes the file when the run exits, including on SIGINT and SIGTERM. Every run mode publishes it: a direct run publishes before its first attempt, and a --loop-file/--team-file run publishes after its workflow config loads and validates, so a rejected config still leaves no pid file behind. A run rejected before the pid is written, such as one whose prompt file is unreadable or whose workflow config is invalid, never publishes a pid and leaves another run's pid file untouched. The signal trap is installed before the pid file is published, so a first signal landing during setup still has a cleanup path and does not leave the pid file behind; a second signal restores the default disposition and can end the process before that cleanup runs, leaving the file behind. A SIGKILL or a crash skips cleanup and leaves a stale pid file behind, and the kernel recycles pids, so kill -0 $(cat run.pid) alone can report an unrelated process as live. Check the process identity before signalling — ps -o command= -p <pid> works on both Linux and macOS. A sentinel alone cannot prove that a process is still running or that an invocation completed: validation, a crash, or a write failure can leave it absent. Launch wrappers cannot supply a trustworthy pid: setsid forks when the caller leads a process group, so a recorded $! can name a shell that already exited while the run continues. Letting the run publish its pid avoids that. The pid file is only removed by the run that wrote it.
--result-file, --pid-file, --sentinel-file and --on-event must name distinct paths — compared as absolute paths with existing parent directories resolved, and by file identity when the paths already exist — and none may claim the permission request or response file derived from the sentinel while the file handler is active, and the result and pid paths must be single-line. An invocation rejected by flag validation writes no artifacts and leaves earlier files untouched. For example, non-empty --prompt and --prompt-file together exit 2 without a sentinel. A rejection after flags parse attempts to write a sentinel, but a write error is only reported on stderr and does not change the exit code. A crash or forced termination can also leave no sentinel. Use fresh paths for every invocation; old sentinel, event, result, or pid files can otherwise be mistaken for this run.
For a direct child process, wait for its exit as well as checking its sentinel. The following example uses GNU timeout to bound the entire command, including setup. It does not use a launcher that forks away from the shell's child:
RUN_DIR=$(mktemp -d "${TMPDIR:-/tmp}/avenor-run.XXXXXX")
timeout --kill-after=10s 120s avenor run --prompt "Summarize this directory" \
--label "data-prep" --on-event "$RUN_DIR/events.jsonl" \
--sentinel-file "$RUN_DIR/done.env" --result-file "$RUN_DIR/result.txt" \
--pid-file "$RUN_DIR/run.pid" &
CHILD=$!
if wait "$CHILD"; then code=0; else code=$?; fi
printf 'process exit: %s\n' "$code"
if [ -f "$RUN_DIR/done.env" ]; then
cat "$RUN_DIR/done.env"
if grep -Fxq "RESULT=$RUN_DIR/result.txt" "$RUN_DIR/done.env"; then
cat "$RUN_DIR/result.txt"
fi
else
printf '%s\n' 'No sentinel: the command failed before publication or was interrupted.' >&2
fiWhen another supervisor launches the run, use that supervisor's completion status and timeout rather than assuming $! names the run. Read a result only when the current sentinel names it in RESULT=; a settled failure can carry a result too, so inspect the outcome before treating it as success.
See loop.md for multi-phase loop config.
Roster selection
A roster is a map from a name to a complete backend/agent/model loadout. roster_file names the map; roster_entry names one key inside it. Every entry must contain backend and at least one of agent or model. An entry may also carry thinking. Roster files can be authored in JSON, YAML, or TOML — the format is detected from the file extension:
{
"planner": {
"backend": "opencode-acp",
"model": "provider/model"
},
"executor": {
"backend": "agy",
"agent": "windsurf-swe"
},
"reviewer": {
"backend": "codex-app-server",
"model": "provider/reviewer",
"thinking": "high"
}
}thinking is a default, not part of the identity: an explicit --thinking wins over the entry's value, so a roster only supplies the level when the caller did not. An unknown level is rejected when the roster loads, with the offending entry named. Whether a level is usable at all still depends on the backend, and that check happens when the run starts. In loop and team configs, a per-phase roster entry supplies the same default for that phase: the run-level --thinking still wins when set, and the merged level is validated against the phase's resolved backend before the phase starts.
Direct mode selects an entry with both flags and does not accept direct identity overrides:
avenor run --prompt "Analyze the repository" \
--roster-file /repo/roster.json --roster-entry plannerThe direct selector is intentionally permissive when no roster is selected: --agent, --model, and --backend are independently optional. Any combination of the three may be supplied — including none at all — and omitted identity values use the selected runtime's defaults, and the last command uses the explicit backend with no agent or model:
avenor run --prompt "Use the runtime defaults"
avenor run --prompt "Use this agent" --agent reviewer
avenor run --prompt "Use this model" --model provider/model
avenor run --prompt "Use backend defaults" --backend opencode-acp--roster-file and --roster-entry must be supplied together for a direct request. A roster request cannot also supply --agent, --model, or --backend; the roster entry supplies all three identity fields. A roster file is strict: system, thinking, and unknown entry fields are rejected in all formats. Run-level --thinking remains separate and is checked against the effective backend selected by the direct entry or by each workflow phase.
For a workflow, --roster-file is only a root fallback and --roster-entry is invalid. The workflow config selects entries per direct phase:
mkdir -p /tmp/avenor-roster-demo
cat >/tmp/avenor-roster-demo/roster.json <<'JSON'
{
"plan": {"backend": "opencode-acp", "model": "provider/planner"},
"test": {"backend": "agy", "model": "provider/tester"}
}
JSON
cat >/tmp/avenor-roster-demo/loop.json <<'JSON'
{
"roster_file": "roster.json",
"pre": [{
"name": "plan",
"roster_entry": "plan",
"prompt": "Produce the implementation plan."
}],
"loop": [{
"name": "test",
"roster_entry": "test",
"prompt": "Run the tests and report the result.",
"on_incomplete": {"nudge": "Finish the test report.", "max_nudges": 1}
}]
}
JSON
avenor run --dir /tmp/avenor-roster-demo --loop-file /tmp/avenor-roster-demo/loop.jsonA phase roster entry overrides the run-level backend, agent, and model for that phase. Phases without an entry retain the existing run-level selection rules. roster_entry is valid only for a direct prompt or prompt-file phase, not for a phase dispatching loop_file or team_file.
For CLI workflows, a declared roster_file is resolved relative to its loop/team config. If the root config has no declaration, the command-level --roster-file fallback is resolved relative to --dir; nested CLI loads do not reuse that fallback. A child with no declaration inherits the already loaded parent entries, recursively; a child declaration replaces them. Stable-mode nesting keeps its existing behavior and does not gain this CLI-only inheritance rule. For direct selection, pass a path that is valid from the invoking process (an absolute path avoids ambiguity).
A phase with resume_from_previous: true must match four values stored for the session: effective backend, agent, model, and agent profile. If its roster selection changes any value, Avenor fails the phase before creating a provider. Matching only the backend is insufficient.
avenor verify
Validate config files without starting a run. Loads and checks loop, team, and roster configs for structural correctness — format decoding, unknown-field rejection, mutual exclusions, prompt presence, roster entry references, and prompt_file resolution. Recursively validates nested loop_file/team_file references that are only loaded on demand during a real run.
avenor verify [flags]Flags
| Flag | Default | Description |
|---|---|---|
--dir | . | Working directory (relative config paths are resolved from here) |
--loop-file | (none) | Path to loop config to validate |
--team-file | (none) | Path to team config to validate |
--roster-file | (none) | Path to roster config to validate (standalone, or as fallback for loop/team) |
--roster-entry | (none) | Roster entry name to look up (requires --roster-file) |
At least one of --loop-file, --team-file, or --roster-file is required.
Examples
Validate a loop config and its nested references:
avenor verify --loop-file loop.json
# ok: loop loop.json (3 phases, max_iterations=5)
# ok: loop inner.json (2 phases, max_iterations=2)Validate a team config with a roster fallback and check a specific entry:
avenor verify --team-file team.json --roster-file roster.json --roster-entry reviewer
# ok: roster roster.json (3 entries)
# ok: roster roster.json: entry "reviewer" found
# ok: team team.json (4 phases)Catch an invalid config before a run:
avenor verify --loop-file loop.yaml
# error: loop loop.yaml: decode config loop.yaml: unknown field "bogus"Works with JSON, YAML, and TOML configs. Exit code 0 with ok: messages on success; exit code 1 with error: messages on failure.
avenor stable
Start a long-lived supervisor process that accepts control commands on a Unix socket. The supervisor manages a pool of child runtimes, spawning them on demand and reusing them across commands.
avenor stable --control-socket /tmp/avenor.sockFlags
| Flag | Default | Description |
|---|---|---|
--control-socket | (required) | Unix socket path for control plane commands |
--http-debug | (none) | HTTP debug adapter bind address (e.g., 127.0.0.1:8080) |
--max-runtimes | 16 | Maximum concurrent child runtimes for this supervisor (parked finished runtimes do not count) |
--max-tree-budget | 64 | Maximum concurrent executing runtimes across the whole supervisor tree including nested supervisors |
--idle-timeout | 0 (disabled) | Exit after this duration with no live child runtimes (parked runtimes count as live); control connections do not delay it |
--parked-timeout | 30m | Reap a finished runtime parked for follow-up after this duration (0 = park until shutdown) |
--lease-sweep-interval | 10s | Expire workflow leases whose heartbeat stopped at this cadence (0 = disable; leases expire only on restart recovery) |
--shutdown-timeout | 10s | Graceful shutdown timeout before killing children |
--permission-claim-timeout | 0 (disabled) | Optional permission claim deadline; with 0, fallback occurs only after all control clients disconnect |
--mcp-launch-wrapper-policy | $AVENOR_MCP_LAUNCH_WRAPPER_POLICY, then $HOME/.avenor/launch-wrapper-policy.json | JSON allowlist of launch_wrapper argv entries permitted for MCP-driven spawns. The flag wins; a path named by the flag or the environment must exist. The well-known file is optional, and with no source the policy is disabled. A malformed file is an error (see backends.md) |
--broker-addr | (none) | Address the broker binds, published beside the control socket as <socket>.broker.url. Empty binds an ephemeral loopback port. A durable hub names a concrete interface address and fixed port, such as 127.0.0.1:8787 or 192.0.2.10:8787; a wildcard bind (0.0.0.0, ::) is refused because children on this host are told to reach the bound address, which a wildcard does not name |
--broker-advertise-url | $AVENOR_BROKER_ADVERTISE_URL | http:// or https:// URL other hosts reach this broker on, used for the <socket>.broker.url record and for registration with the parent broker. Empty advertises the bound address; a non-empty flag overrides the environment. Set it when a TLS terminator or NAT publishes the broker under another host, port or scheme; children and in-process transports on this host keep the bound address. The value must be an absolute http:///https:// root URL naming a host, with no credentials, query or fragment (one trailing slash is accepted), or startup exits 1. A value also requires a concrete --broker-addr; paired with the empty (ephemeral) bind, startup exits 1 because the published URL would hide the actually bound port |
--broker-parent-url | $AVENOR_PARENT_BROKER_URL | http:// address of the parent broker to join; a non-empty flag overrides the environment. The credential comes from the AVENOR_PARENT_BROKER_TOKEN environment variable (required whenever the effective URL is non-empty); a credential without a URL runs this broker as the tree's parent broker |
--broker-parent-token | (none) | Explicit parent-link credential overriding AVENOR_PARENT_BROKER_TOKEN; prefer the env var, since argv is world-readable via /proc/<pid>/cmdline |
Example
Start a long-lived supervisor:
avenor stable --control-socket /tmp/avenor.sock --max-runtimes 4 \
--idle-timeout 2h &Run a hub with a durable broker address, then point branches at it through the environment:
TOKEN=$(avenor parent-token)
# Hub: fixed broker address, link credential in the environment.
AVENOR_PARENT_BROKER_TOKEN=$TOKEN \
avenor stable --control-socket /tmp/hub.sock --broker-addr 127.0.0.1:8787 &
HUB_PID=$!
# The record appears once the hub owns its control socket; wait for it, then read
# the URL it actually bound (here, the fixed address).
waited=0
while [ ! -e /tmp/hub.sock.broker.url ]; do
waited=$((waited + 1))
if [ "$waited" -gt 100 ]; then
echo "hub broker URL record never appeared: /tmp/hub.sock.broker.url" >&2
kill "$HUB_PID" 2>/dev/null || true
wait "$HUB_PID" 2>/dev/null || true
exit 1
fi
sleep 0.1
done
cat /tmp/hub.sock.broker.url # http://127.0.0.1:8787
# Branch: no --broker-parent-url needed; the environment names the hub.
AVENOR_PARENT_BROKER_TOKEN=$TOKEN \
AVENOR_PARENT_BROKER_URL=http://127.0.0.1:8787 \
avenor stable --control-socket /tmp/branch.sock &Then spawn runtimes via avenor control spawn. See stable.md, messaging.md and control-protocol.md for full details.
avenor schema
avenor schema prints the declared command surface as JSON. Flag defaults use their value types: integers are numbers and durations are Go duration strings, such as 30s. Command input_rules describe relationships between supplied flags. The CLI checks those rules and required flags before running a handler; missing required flags report missing required flag --<name>. The rules do not cover every validation: handlers also check configuration, environment variables, and some flag relationships.
avenor message
Send broker messages as the current child run, not as the supervisor:
avenor message --action tell --target supervisor --message "Status update"
avenor message --action ask --target supervisor --message "Question?" --timeout-ms 30000
avenor message --action reply --reply-to MESSAGE_ID --message "Answer"
avenor message --action withdraw --message-id MESSAGE_ID
printf 'line one\nline two\n' | avenor message --action tell --target supervisor --message-stdin--target accepts a broker run ID or label. An ask reply needs the claim record written by the feed; --asks-dir selects its directory when the feed uses a non-default location. A second reply to the same ask, or a reply to an ask with no record, reports edge-absent. The broker resolves a reply to a tell without a claim record. Use withdraw with --message-id to close an open ask.
--message-stdin reads the message text from stdin to EOF, preserving newlines. This keeps the text out of command arguments, which can be visible to other processes. The flag is mutually exclusive with --message, including an empty --message=. Supplying both exits 2 without sending. When the flag is absent or false, the command does not read stdin. The stdin flag removes text from argv, not from tool calls, transcripts, or other surfaces. A read error, input past 1 MiB of raw input, or input that is not valid UTF-8 exits 1 without sending. --message text must be valid UTF-8 too; invalid UTF-8 exits 1 without sending, instead of being silently replaced with the Unicode replacement character during encoding. The broker separately caps the entire JSON-encoded request at 1 MiB, so encoding and metadata can make the effective text limit smaller.
Each command uses AVENOR_BROKER_URL, AVENOR_BROKER_TOKEN and AVENOR_RUN_ID, or the respective --broker-url, --token and --run-id flags. Prefer the environment for the token: command arguments can be visible to other processes. --timeout-ms takes 0 to 600000, and the router refuses any other value with invalid_request. Zero or omission uses the shared default of 600 seconds.
Results are JSON with action, outcome, text and, where applicable, message_id and reply. Router outcomes exit 0, including timed-out: the ask remains open for a later reply. Refusals and transport errors exit 1; missing action or credentials exit 2. Router refusals include code and text on stdout. See messaging.md for the transport and addressing rules.
avenor control
Send commands to a running supervisor or standalone run with a control socket. The supervisor must be listening on --control-socket.
avenor control --socket /tmp/avenor.sock <command> [args...]Global flags
| Flag | Required | Description |
|---|---|---|
--socket | yes | Control socket path |
Commands
status [runtime-id]
Fetch the status snapshot of a runtime, or the supervisor if no runtime-id given. Outputs JSON.
avenor control --socket /tmp/avenor.sock status
avenor control --socket /tmp/avenor.sock status rt-12345list
List all running child runtimes. Outputs JSON array.
avenor control --socket /tmp/avenor.sock listtail
Subscribe to and tail all events from all runtimes. Outputs NDJSON.
avenor control --socket /tmp/avenor.sock tailThe stream ends when the control server shuts down, including when the server refuses the subscription because shutdown has already begun. A connection that drops before the subscription is answered exits 1.
spawn [flags]
Spawn a new runtime under the supervisor with the given parameters. Outputs JSON result with assigned runtime-id.
Spawn flags (all optional):
| Flag | Description |
|---|---|
--prompt | Inline prompt text (mutually exclusive with --prompt-file) |
--prompt-file | Path to prompt file (mutually exclusive with --prompt) |
--dir | Working directory |
--agent | Agent name |
--label | Free-form label for log correlation |
--model | Backend-specific model ID |
--backend | Runtime backend: opencode-acp, agy, gemini-acp, cursor-acp, codex-app-server, opencode-http, pi, claude |
--server-url | Long-lived ACP server endpoint |
--on-event | Path to write NDJSON events |
--sentinel-file | Path to write completion sentinel |
--permission-handler | Permission resolver (e.g., file:<path>) |
--auto-approve | Automatically approve all permission requests |
--persist-sessions | Persist the backend session (see backends.md) |
--messaging | Request poo messaging on or off. Omitted defaults on for delegation spawns and off for workflow nodes, loop or team phases, and permission-decider runs. True is refused for loop and team spawns. The switch is carried per run but is not wired to poo launch yet. |
--timeout | Overall session timeout (Go duration: 30s, 5m, etc.) |
--max-retries | Maximum retry attempts on transient failure |
--launch-wrapper | Opaque argv prefix placed in front of the backend process command; repeatable, each use adds one element (e.g. --launch-wrapper nice --launch-wrapper -n --launch-wrapper 5). Avenor does not interpret the wrapper — see backends.md |
avenor control --socket /tmp/avenor.sock spawn \
--prompt "find bugs" --label "phase-1" --on-event /tmp/events.jsonlcancel [runtime-id]
Cancel a running runtime. If no runtime-id given, cancels the only child or errors.
avenor control --socket /tmp/avenor.sock cancel rt-12345prompt <text> [runtime-id]
Queue a follow-up prompt for the runtime. If no runtime-id given, targets the only child.
avenor control --socket /tmp/avenor.sock prompt "continue with part 2" rt-12345interrupt-and-prompt <text> [runtime-id]
Interrupt the current message generation and queue a new prompt.
avenor control --socket /tmp/avenor.sock interrupt-and-prompt "stop and refocus" rt-12345answer-permission <request-id> <option-id> [runtime-id]
Answer a pending permission request by request-id and option-id. If no runtime-id given, targets the only child.
avenor control --socket /tmp/avenor.sock answer-permission \
req-abc123 opt-allowSee permission-handler.md for how to integrate with your permission UI.
shutdown [mode]
Shut down the supervisor. Mode is graceful (default, waits for children) or immediate.
avenor control --socket /tmp/avenor.sock shutdown gracefulavenor await
Wait on a run until it reaches attention or done. Resolves a run by ID or label over a control socket, or by scanning ~/.avenor/sockets when --socket is omitted.
avenor await attaches to either a supervisor socket or a one-shot run started with --control-socket. A one-shot server exposes its single run: list reports it under the run id, and status, history, subscribe, and result accept that id, so --until done follows it to the terminal snapshot.
avenor await [flags] <run-id|label>Flags
| Flag | Default | Description |
|---|---|---|
--socket | (none) | Control socket path; when omitted, avenor await scans ~/.avenor/sockets |
--until | attention | Wait target: attention (stop at pending_permission) or done (continue to terminal state) |
--timeout | 0 (disabled) | Wall-clock backstop duration; exits 124 when reached |
--print-output | false | Print the result payload after --- |
--format | plain | Output format: plain or json |
Exit codes
| Code | Meaning |
|---|---|
0 | Reached done |
2 | Usage, not found, or dead socket |
10 | Reached failed |
11 | Reached phase timeout |
12 | Reached killed |
20 | Reached attention (pending_permission) |
124 | Wall-clock timeout |
Output
Plain format emits line-buffered transition records:
ATTENTION permission <runtime_id> <summary>TURN-DONE <runtime_id>END <failed|timeout|killed> <runtime_id> [reason](completion emitsTURN-DONE)
With --print-output, the final payload is printed after ---. With --format json, the same transitions are emitted as structured records.
Example
avenor await --socket /tmp/avenor.sock run-123 --until done --print-outputavenor watch
Read or tail NDJSON event logs, with optional digestion into human-readable format.
avenor watch [flags] <log>Flags
| Flag | Default | Description |
|---|---|---|
--follow | false | Poll and tail the log instead of reading to EOF |
--format | plain | Output format: plain (EVENT lines) or json (pass-through NDJSON) |
--classify | false | Prefix each plain-format line with MILESTONE | FINDING | ACTIVITY; in json format, adds a top-level classify field |
--poll-interval | 250ms | Follow-mode sleep interval |
--since-cursor | (none) | Cursor file path: seek to saved offset before reading; rewrite offset on exit |
Examples
Read a log to completion and output human-readable digest:
avenor watch --format plain /tmp/events.jsonlTail a log as it grows, updating a cursor file so you can resume from the same point:
avenor watch --follow --format plain --since-cursor /tmp/cursor /tmp/events.jsonlTail with classification (milestones, findings, etc.):
avenor watch --follow --format plain --classify /tmp/events.jsonlSee watch.md and events.md for full details.
avenor answer
Write a permission response file to answer a pending permission request. Specific to the file-based permission handler (--permission-handler file:<path>); not applicable when using --auto-approve or a control socket.
avenor answer [flags] <perm-base>The permission handler expects files at <perm-base>.req (request) and <perm-base>.req.response (response). Flags may appear before or after <perm-base>; the examples below show them before the path. Repeating a flag on the same side of the path keeps the last value. Giving the same flag on both sides is an error, so a later flag cannot silently override an earlier one across the path.
Flags
| Flag | Default | Description |
|---|---|---|
--option | (required) | Option ID to select (must match an optionId from the .req file) |
--message | (none) | Free-text message to include in the response |
--outcome | selected | Outcome: selected (approve) or cancelled (deny) |
--force | false | Overwrite an existing response file; by default, command errors if response already exists |
Example
Read the request file, find an allowed option ID, and answer:
cat /tmp/run.perm.req | jq '.options[] | select(.kind == "allow") | .optionId'
# Output: opt-12345
avenor answer --option opt-12345 --message "approved by ops" /tmp/run.permSee permission-handler.md for full integration guide.
avenor mcp
Run an MCP (Model Context Protocol) server that orchestrates avenor runtimes. Used by Claude and other compatible clients to invoke agents remotely.
avenor mcp [flags]Flags
| Flag | Default | Description |
|---|---|---|
--transport | stdio | Transport: stdio (default, single client) or http (multi-client) |
--control-socket | (none) | Unix socket path for the supervisor (created if not present and --no-autostart is not set) |
--supervisor-socket | (none) | Unix socket path for an existing supervisor; requires --no-autostart |
--no-autostart | false | Disable automatic supervisor startup; requires --supervisor-socket |
--idle-timeout | 30m | Idle timeout before server exits |
--addr | 127.0.0.1:3748 | Address to listen on for HTTP transport |
--auth-token | (none) | Bearer token for HTTP transport; defaults to MCP_AUTH_TOKEN env var |
--mcp-launch-wrapper-policy | $AVENOR_MCP_LAUNCH_WRAPPER_POLICY, then $HOME/.avenor/launch-wrapper-policy.json | Policy file forwarded to a supervisor this command autostarts; ignored when attaching to an existing supervisor (see backends.md) |
Examples
Run stdio MCP server (single client):
avenor mcpRun HTTP MCP server on a custom port:
avenor mcp --transport http --addr 0.0.0.0:9000 --auth-token secret123Connect to an existing supervisor:
avenor mcp --transport stdio --supervisor-socket /tmp/avenor.sock --no-autostartSee mcp.md for protocol details and client configuration.
avenor workflow
Drive a durable workflow through a stable supervisor's control socket. All subcommands take --socket (before or after the subcommand). claim, start, and heartbeat are control-plane commands sent through the workflow.command control method, not CLI subcommands.
avenor workflow <command> [args] [flags]Commands
| Command | Purpose |
|---|---|
create --request-file FILE | Register a versioned template. |
instantiate --template-id ID --template-version V --request-file FILE | Create an instance of a template version. |
status <workflow-id> | Current workflow snapshot summary. |
wait <workflow-id> [--timeout 30s] | Block until terminal or timeout. |
inspect <workflow-id> | Full instance detail. |
events <workflow-id> [--after-seq N] [--limit N] | Event log. |
complete <workflow-id> <node-id> --activation-id ID --attempt-id ID --lease-id ID --request-file FILE | Complete a machine/external handoff activation. |
gate <workflow-id> <node-id> <gate-id> --activation-id ID --operation OP --request-file FILE | Record a gate decision (satisfy, reject, waive, external_result). |
skip <workflow-id> <node-id> --request-file FILE | Waive every unsatisfied required gate on a parked activation. |
unblock <workflow-id> <node-id> --request-file FILE | Return a blocked activation to ready. |
cancel <workflow-id> --reason TEXT [--actor ID] | Cancel the whole instance: terminal canceled, in-flight attempts terminated. |
Examples
Register a template and instantiate a work unit:
avenor workflow create --socket /tmp/avenor.sock \
--request-file templates/software-factory/work.json
echo '{"metadata":{"issue":"115"}}' > /tmp/instance.json
avenor workflow instantiate --socket /tmp/avenor.sock \
--template-id software-factory-work --template-version 1.0.0 \
--request-file /tmp/instance.jsonObserve the workflow:
avenor workflow status --socket /tmp/avenor.sock wf_...
avenor workflow inspect --socket /tmp/avenor.sock wf_...
avenor workflow events --socket /tmp/avenor.sock wf_... --after-seq 0 --limit 50Complete a run node with evidence and a declared outcome:
avenor workflow complete --socket /tmp/avenor.sock \
wf_... assessment \
--activation-id act_... --attempt-id att_... --lease-id lease_... \
--request-file /tmp/complete.jsonRecord an external gate result bound to an exact PR head:
avenor workflow gate --socket /tmp/avenor.sock \
wf_... review ci \
--activation-id act_... --operation external_result \
--request-file /tmp/ci-result.jsonSee workflow.md for the full durable-workflow reference and the templates/software-factory/README.md guide for a complete example.
avenor probe
Run a diagnostic probe against an OpenCode ACP backend to discover its capabilities and configuration.
avenor probe --out <transcript>Flags
| Flag | Default | Description |
|---|---|---|
--dir | . | Working directory for the probe |
--out | (required) | Path to write the probe transcript (NDJSON events) |
--prompt | (default) | Override the default probe prompt; used only in Stage 1 discovery |
--timeout | 5m | Probe timeout |
Example
Probe an OpenCode installation and save the transcript:
avenor probe --dir /path/to/project --out /tmp/probe.jsonlThen read the transcript:
avenor watch /tmp/probe.jsonlSee backends.md for more on backend discovery.
avenor parent-token
Print a fresh 256-bit random parent-link credential as 64 lowercase hex characters. Use it to mint the shared credential that links a child branch supervisor's broker to its parent's broker. See messaging.md for how the token is carried and consumed.
avenor parent-tokenFlags
None.
Example
Mint a token and hand it to both sides of a parent link:
avenor parent-token
# Output: 3f9c1a… (64 hex characters, one line)Each invocation prints a different token; the value is never persisted by this command.
Common Patterns
Long-lived supervisor with on-demand agents
Start a supervisor in the background, then spawn agents as needed:
# Terminal 1: Start supervisor
avenor stable --control-socket /tmp/avenor.sock &
SUPERVISOR_PID=$!
# Terminal 2: Spawn a phase-1 agent
avenor control --socket /tmp/avenor.sock spawn \
--prompt "analyze the codebase" \
--label phase-1 \
--on-event /tmp/phase1.jsonl
# Terminal 2: Tail its output in real-time
avenor watch --follow --format plain /tmp/phase1.jsonl
# When phase-1 finishes, spawn phase-2 on the same supervisor
avenor control --socket /tmp/avenor.sock spawn \
--prompt "generate tests for the issues found" \
--label phase-2 \
--on-event /tmp/phase2.jsonl
# Shutdown supervisor when done
avenor control --socket /tmp/avenor.sock shutdown graceful
kill $SUPERVISOR_PID 2>/dev/nullPermission handling via file
Run an agent with file-based permission handling:
# Start agent with a file-based permission handler
avenor run --prompt-file prompt.txt \
--sentinel-file /tmp/run.sentinel \
--permission-handler file:/tmp/run.perm \
--on-event /tmp/events.jsonl &
RUN_PID=$!
# Monitor for permission requests
avenor watch --follow /tmp/events.jsonl | grep -i "permission.request" &
# When you see a request, answer it
avenor answer --option opt-allow /tmp/run.perm
# Wait for the child, then read the outcome
wait "$RUN_PID"
cat /tmp/run.sentinelMulti-phase workflow with control socket
Orchestrate a multi-phase workflow using control commands:
# Start first phase
avenor run --prompt "phase 1 prompt" \
--control-socket /tmp/rt.sock \
--on-event /tmp/events.jsonl &
# While it's running, monitor status
sleep 2
avenor control --socket /tmp/rt.sock status
# Interrupt and send follow-up prompt
avenor control --socket /tmp/rt.sock interrupt-and-prompt "phase 2 prompt"
# Tail events
avenor watch --follow /tmp/events.jsonlEvent log inspection with cursor tracking
Process event logs incrementally, remembering position across runs:
# Initial read and cursor save
avenor watch --format plain --since-cursor /tmp/cursor /tmp/events.jsonl
# Later, resume from saved position
avenor watch --follow --format plain --since-cursor /tmp/cursor /tmp/events.jsonlExit Codes
0— Success (session ended with stop_reason "success" or no explicit reason)1— General error or session ended with stop_reason "error"; may trigger retry if--max-retries> 02— Flag validation error (unknown flags, malformed values,--prompt/--prompt-fileconflict), or a session that ended with stop_reasonrefusalortool_denied3— Session ended with stop_reasonmax_tokens4— Session ended with stop_reasonmax_turn_requests5— Blocked (a phase aborted with an abort marker)6— Provider quota exhausted (quota_exhausted); fall back to another model or retry later7— Provider rate limit (rate_limited); retried when--max-retries> 0124— Overall session timeout (--timeoutreached)130— Cancelled (SIGINT,SIGTERM, or context cancellation)
A provider limit failure is reported as exit code 6 or 7 instead of 1, so an orchestrator can distinguish "out of quota, switch or retry later" from "the agent failed" without reading the event log.
Completion sentinel
With --sentinel-file, the file contains the status, session ID, and stop reason, plus any applicable extra lines:
FAILED
SESSION=ses_abc123
STOP_REASON=rate_limited
EXIT_CODE=7
RETRY_AFTER=20
RUN=a3f9...RETRY_AFTER (whole seconds) appears only when the provider stated how long to wait. Without a hint, the line is omitted; it is never emitted empty.
See loop.md for how exit codes interact with multi-phase loops.
Environment Variables
AVENOR_OPENCODE_URL— Default server URL if--server-urlnot set (only consulted for subprocess discovery)AVENOR_PARENT_BROKER_URL— Parent broker URLavenor stablejoins when--broker-parent-urlis empty or absent; a non-empty flag overrides it. An interactive supervisor autostart (thepiextension oravenor mcp) joins the hub this names without command-line changesAVENOR_PARENT_BROKER_TOKEN— Parent-link credential consumed byavenor stableon both sides of a parent link; required when the effective parent URL is non-empty, and a credential without a URL runs the broker as the tree's parent broker. Prefer this over--broker-parent-tokenMCP_AUTH_TOKEN— Bearer token for HTTP MCP server if--auth-tokennot setOPENCODE_CONFIG_DIR— Directory to search for opencode config; defaults to~/.config/opencode/
Related Documentation
- stable.md — Supervisor architecture and lifecycle
- control-protocol.md — Control plane protocol and command semantics
- watch.md — Event log digestion and filtering
- permission-handler.md — Permission request/response flow
- mcp.md — MCP server configuration and protocol
- backends.md — Supported runtime backends
- loop.md — Multi-phase loop configuration
- workflow.md — Durable workflows: templates, gates, composition, recovery
- events.md — Event schema and types