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.
Avenor operates in three modes:
- Single run β
avenor run(or justavenorwith no subcommand). 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.
Default subcommand. Starts a single agent and blocks until it finishes or times out.
avenor run [flags]
avenor [flags] # equivalent; explicit "run" is optional
| 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) |
--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 |
--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, claude-channel |
--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 |
--on-event |
(none) | Path to write NDJSON event stream; events are discarded if unset |
--sentinel-file |
(none) | Path to write a completion sentinel (exit code, session ID, stop reason); also derives permission handler unless --permission-handler is set |
--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 |
--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); 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 |
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.jsonlSee loop.md for multi-phase loop config.
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. 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"
}
}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.
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]
| 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.
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.
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.sock
| 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 |
--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 child runtimes and no control connections |
--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 |
Start a long-lived supervisor:
avenor stable --control-socket /tmp/avenor.sock --max-runtimes 4 \
--idle-timeout 2h &Then spawn runtimes via avenor control spawn. See stable.md and control-protocol.md for full details.
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...]
| Flag | Required | Description |
|---|---|---|
--socket |
yes | Control socket path |
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 all running child runtimes. Outputs JSON array.
avenor control --socket /tmp/avenor.sock listSubscribe to and tail all events from all runtimes. Outputs NDJSON.
avenor control --socket /tmp/avenor.sock tailSpawn 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, claude-channel |
--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 |
--timeout |
Overall session timeout (Go duration: 30s, 5m, etc.) |
--max-retries |
Maximum retry attempts on transient failure |
avenor control --socket /tmp/avenor.sock spawn \
--prompt "find bugs" --label "phase-1" --on-event /tmp/events.jsonlCancel a running runtime. If no runtime-id given, cancels the only child or errors.
avenor control --socket /tmp/avenor.sock cancel rt-12345Queue 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 the current message generation and queue a new prompt.
avenor control --socket /tmp/avenor.sock interrupt-and-prompt "stop and refocus" rt-12345Answer 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.
Shut down the supervisor. Mode is graceful (default, waits for children) or immediate.
avenor control --socket /tmp/avenor.sock shutdown gracefulWait 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 [flags] <run-id|label>
| 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 |
| 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 |
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.
avenor await --socket /tmp/avenor.sock run-123 --until done --print-outputRead or tail NDJSON event logs, with optional digestion into human-readable format.
avenor watch [flags] <log>
| 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 |
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.
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).
| 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 |
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.
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]
| 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 |
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.
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]
| 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. |
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.
Run a diagnostic probe against an OpenCode ACP backend to discover its capabilities and configuration.
avenor probe --out <transcript>
| 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 |
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.
Internal MCP sidecar invoked automatically by Claude Code when using the claude-channel backend. You don't run this directly β Avenor writes a per-run entry to the project's .mcp.json that tells Claude Code how to invoke it, then removes the entry when the session ends. The backend requires tmux on PATH because Avenor boots Claude Code inside a detached interactive tmux session.
avenor claude-channel --run-id <id> --token <token> --broker-url <url>
| Flag | Required | Description |
|---|---|---|
--run-id |
yes | Avenor run ID issued by the broker |
--token |
yes | Per-run bearer token for broker authentication |
--broker-url |
yes | HTTP base URL of the in-process broker |
When starting a claude-channel backend session, Avenor:
- Generates a unique 8-character run ID (e.g.,
47212ef3) - Writes an MCP server entry to
<project-dir>/.mcp.jsonwith nameavenor-channel-47212ef3 - The entry uses the avenor binary itself:
avenor claude-channel --run-id <id> --token <token> --broker-url <url> - On session exit, removes the entry from
.mcp.json
If the session crashes or is forcibly killed, the entry may remain. Use avenor claude-channel-cleanup --dir <project-dir> as a recovery command to remove stale entries manually after crash cleanup did not run.
The sidecar speaks JSON-RPC 2.0 over stdio, declares claude/channel and claude/channel/permission capabilities, and exposes the three tools Claude uses to communicate back:
avenor_reportβ progress updateavenor_finishβ session completion with status and summaryavenor_replyβ directed reply to a named recipient
See backends.md for architecture and security notes.
Remove all avenor-channel-* MCP server entries from a project's .mcp.json. Use this to clean up stale entries if an avenor session crashes or is forcibly killed before it can remove its bootstrap config.
avenor claude-channel-cleanup --dir <project-dir>
| Flag | Default | Description |
|---|---|---|
--dir |
. |
Project directory containing .mcp.json |
Clean up stale avenor-channel entries from a project:
avenor claude-channel-cleanup --dir /path/to/project
# Output: removed 2 avenor-channel entries from /path/to/project/.mcp.jsonThe cleanup command is safe to run at any time β it only removes entries with names starting with avenor-channel- and leaves all other MCP servers intact. If no avenor-channel entries exist, it reports removed 0 and exits successfully.
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/nullRun an agent with file-based permission handling:
# Start agent with permission handler
avenor run --prompt-file prompt.txt \
--sentinel-file /tmp/run.sentinel \
--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 completion
wait $RUN_PIDOrchestrate 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.jsonlProcess 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.jsonl0β 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β Usage/validation error (avenor control subcommands only)124β Overall session timeout (--timeoutreached)130β Cancelled (SIGINT, context done)
See loop.md for how exit codes interact with multi-phase loops.
AVENOR_OPENCODE_URLβ Default server URL if--server-urlnot set (only consulted for subprocess discovery)MCP_AUTH_TOKENβ Bearer token for HTTP MCP server if--auth-tokennot setOPENCODE_CONFIG_DIRβ Directory to search for opencode config; defaults to~/.config/opencode/
- 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