# agent-orchestrator — example project config. # # One file declares: which agents exist, their backend, model, prompt, kind, and how the # watchdog supervises them. Read by agents.py (driver + watchdog). Runtime state (phase index, # resume ids, limit windows) lives under /state/, NOT here. # # This example is self-contained: its agents use a dependency-free `demo` backend (a shell that # just idles), so the whole project can be brought up and torn down with no external agent CLI — # see ./smoke.sh. The `claude` and `opencode` backends below are the real ones; point an agent at # them with `backend = "claude"` for a live run. # ─────────────────────────── global watchdog cadence ─────────────────────────── [watchdog] signal_interval = 30 # s between handoff / stall / limit checks (light) heavy_interval = 300 # s between heal / phase-advance checks limit_probe_fallback = 300 # flat probe cadence when a reset time can't be parsed limit_reset_slack = 45 # s past a parsed reset before probing stall_grace = 180 # s of slack past a WAITING-UNTIL marker before a stall reboot # ─────────────────────────── defaults inherited by every agent ─────────────────────────── [defaults] session_prefix = "ao-example-" # REQUIRED — tmux namespace for this project (no implicit default) log_dir = ".ao-state" # REQUIRED — logs + state/, resolved relative to this file backend = "demo" model = "" watch = "none" # none | heal | heal+stall # ─────────────────────────── backends (declared as data) ─────────────────────────── # A backend is fully described by config. `prompt_delivery` selects how the kickoff reaches the # agent: "arg" (CLI argument, claude-style), "ping" (typed in after a TUI connects, opencode), # or "exec" (a plain command; {kickoff}=prompt file, {session}=session name, {model}=model). [backend.demo] # dependency-free backend used by this example's smoke run bin = "echo '[demo] {session} up (kickoff: {kickoff})'; exec sleep 1000000" prompt_delivery = "exec" [backend.claude] # the real claude backend bin = "claude" flags = "--dangerously-skip-permissions" remote_control = true supports_resume = true prompt_delivery = "arg" process_name = "claude" # used for backend-mismatch healing submit_key = "Enter" stall_idle = 300 active_re = "esc to interrupt|Running tool|⠇|⠙|· \\d+" limit_re = "spend limit|usage limit|limit reached|reached your .*limit|out of (credits|tokens)" fatal_re = "redacted_thinking|blocks cannot be modified|cannot be modified" [backend.opencode] # the real opencode backend (a TUI; prompt typed after connect) bin = "opencode" attach = "{bin} attach {server} --dir {dir}" server = "http://127.0.0.1:4096" supports_resume = false prompt_delivery = "ping" process_name = "opencode" footer_ui = true # static footer lingers after a turn → only the bottom = activity log_grace = 180 connect_delay = 12 submit_key = "C-m" model_env = true # pass the model via OPENCODE_CONFIG_CONTENT stall_idle = 900 active_re = "esc interrupt|thinking|inferring|running tool|tool call|preparing patch|reading|searching" limit_re = "spend limit|usage limit|limit reached|reached your .*limit|out of (credits|tokens)" fatal_re = "redacted_thinking|blocks cannot be modified|cannot be modified" # ─────────────────────────── agents ─────────────────────────── # A minimal 2-agent loop pair: a Builder that does the work and an Adversary that verifies it. [[agent]] name = "builder" # tmux session: ao-example-builder kind = "loop" # kickoff = kickoff_template + prompts/builder.md, per phase role = "builder" [[agent]] name = "adversary" # tmux session: ao-example-adversary kind = "loop" role = "adversary" # A persistent supervisor and a one-shot task are also supported: # [[agent]] # name = "orchestrator" # kind = "persistent" # backend = "claude" # resume = true # watch = "heal" # wake = { interval = 3600, prompt_file = "prompts/supervise.md" } # prompt = "You supervise this project. On startup, check status and report." # Non-AI helper services (started by a bare `up`, not AI sessions): # [[service]] # name = "cleanlogs" # command = "python3 agent-log.py follow-all" # ─────────────────────────── the phase machine (kind="loop" agents) ─────────────────────────── [loop] state_file = "phase-idx" # under /state/ resume_phase = true # keep the current index across restarts (don't reset to 0) auto_advance = true # advance when the current phase's status file says the done_marker done_marker = "## DONE" kickoff_template = "prompts/kickoff.md" # project preamble; slots {phase_id}/{plan}/{status}/{role} roles_dir = "prompts" # role prompt = /.md handoff = { repo = ".", claim_pings = "adversary", review_pings = "builder", inboxes = ["ADVERSARY-INBOX.md", "BUILDER-INBOX.md"], claim_pattern = "^claim", review_pattern = "^review", state_subdir = "machine-docs" } # on_complete = { trigger_file = ".run-on-complete", run = "reporter" } phases = [ { id = "demo1", plan = "examples/PLAN-demo1.md", status = "STATUS-demo1.md" }, { id = "demo2", plan = "examples/PLAN-demo2.md", status = "STATUS-demo2.md", models = { builder = "claude-opus-4-8" } }, ]