feat: agent-orchestrator v0.1.0 — generic multi-agent harness
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>
This commit is contained in:
89
smoke.sh
Executable file
89
smoke.sh
Executable file
@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env bash
|
||||
# Self-contained smoke test: bring a 2-agent example project up and tear it down in an ISOLATED
|
||||
# sandbox (its own session_prefix + a throwaway log_dir), using only files in this repo and no
|
||||
# external agent CLI (the demo backend is just a shell that idles). Cleans up after itself.
|
||||
#
|
||||
# Usage: ./smoke.sh → prints "SMOKE PASS" and exits 0 on success.
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
PREFIX="ao-smoke-$$-"
|
||||
SANDBOX="$(mktemp -d)"
|
||||
CFG="$SANDBOX/agents.toml"
|
||||
|
||||
cleanup() {
|
||||
local rc=$?
|
||||
python3 "$HERE/agents.py" --config "$CFG" down >/dev/null 2>&1 || true
|
||||
# belt-and-suspenders: kill any session that still carries our unique prefix
|
||||
if command -v tmux >/dev/null 2>&1; then
|
||||
tmux ls 2>/dev/null | sed 's/:.*//' | grep "^${PREFIX}" | while read -r s; do
|
||||
tmux kill-session -t "=$s" 2>/dev/null || true
|
||||
done || true
|
||||
fi
|
||||
rm -rf "$SANDBOX"
|
||||
exit "$rc"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() { echo "SMOKE FAIL: $1" >&2; exit 1; }
|
||||
|
||||
command -v tmux >/dev/null 2>&1 || fail "tmux not on PATH (run inside 'nix develop')"
|
||||
|
||||
# A throwaway config: project_dir points back at the repo so prompts/ resolve, but session_prefix
|
||||
# and log_dir are unique + temporary, so this run can never touch a real project's sessions.
|
||||
cat > "$CFG" <<EOF
|
||||
[defaults]
|
||||
project_dir = "$HERE"
|
||||
session_prefix = "$PREFIX"
|
||||
log_dir = "$SANDBOX/state"
|
||||
backend = "demo"
|
||||
watch = "none"
|
||||
|
||||
[backend.demo]
|
||||
bin = "echo '[demo] {session} up'; exec sleep 1000000"
|
||||
prompt_delivery = "exec"
|
||||
|
||||
[[agent]]
|
||||
name = "builder"
|
||||
kind = "loop"
|
||||
role = "builder"
|
||||
|
||||
[[agent]]
|
||||
name = "adversary"
|
||||
kind = "loop"
|
||||
role = "adversary"
|
||||
|
||||
[loop]
|
||||
kickoff_template = "prompts/kickoff.md"
|
||||
roles_dir = "prompts"
|
||||
phases = [ { id = "smoke", plan = "examples/PLAN-demo1.md", status = "STATUS-smoke.md" } ]
|
||||
EOF
|
||||
|
||||
echo "== sanity: 'status' on the shipped example config =="
|
||||
python3 "$HERE/agents.py" --config "$HERE/agents.example.toml" status >/dev/null \
|
||||
|| fail "status on agents.example.toml failed"
|
||||
|
||||
echo "== bring up isolated sandbox ($PREFIX) =="
|
||||
python3 "$HERE/agents.py" --config "$CFG" up builder adversary
|
||||
|
||||
for s in "${PREFIX}builder" "${PREFIX}adversary"; do
|
||||
tmux has-session -t "=$s" 2>/dev/null || fail "$s did not start"
|
||||
echo " up: $s"
|
||||
done
|
||||
|
||||
# the kickoff prompt should have been assembled (template preamble + role prompt) into state/
|
||||
KF="$SANDBOX/state/state/kickoff-${PREFIX}builder.txt"
|
||||
test -s "$KF" || fail "kickoff file not written ($KF)"
|
||||
grep -q "PROJECT PHASE: smoke" "$KF" || fail "kickoff template not rendered into kickoff"
|
||||
grep -q "You are the \*\*Builder\*\*" "$KF" || fail "role prompt not appended to kickoff"
|
||||
echo " kickoff assembled OK (template + role prompt)"
|
||||
|
||||
echo "== tear down =="
|
||||
python3 "$HERE/agents.py" --config "$CFG" down builder adversary
|
||||
sleep 1
|
||||
for s in "${PREFIX}builder" "${PREFIX}adversary"; do
|
||||
! tmux has-session -t "=$s" 2>/dev/null || fail "$s still alive after down"
|
||||
echo " down: $s"
|
||||
done
|
||||
|
||||
echo "SMOKE PASS"
|
||||
Reference in New Issue
Block a user