Extracted and generalized from a project-specific agent launch engine. No project specifics remain in code: paths, the loop kickoff preamble, handoff conventions, and the on-complete hook are all config/template driven; session_prefix + log_dir are required. - agents.py: driver + watchdog (data-driven backends via prompt_delivery arg|ping|exec; required session_prefix/log_dir; project-rooted path resolution; configurable kickoff template, handoff patterns, on_complete task; tmux-safe; selftest + init verbs) - agent-log.py: config-driven claude transcript renderer - agents.example.toml: self-contained 2-agent example (dependency-free demo backend) - prompts/: generic builder/adversary/kickoff templates - smoke.sh: isolated up+down sandbox proof that cleans up after itself - flake.nix/.lock: devShell (python311 + tmux + git) - README.md: schema + verbs + AI-PO usage + nix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
6.0 KiB
TOML
110 lines
6.0 KiB
TOML
# 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 <log_dir>/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 <log_dir>/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 = <roles_dir>/<role>.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" } },
|
|
]
|