feat(launch): ADV_MODEL — per-role model override for the Adversary loop

This commit is contained in:
autonomic-bot
2026-06-10 04:03:35 +00:00
parent a1b4943da1
commit e0c9f23391

View File

@ -12,6 +12,7 @@ Usage:
Env (all optional — defaults shown):
LOOP_BACKEND claude (default) | opencode
LOOP_MODEL model flag, e.g. "sonnet" (claude) or "tinfoil/deepseek-v4-pro" (opencode)
ADV_MODEL optional model override for the Adversary only (falls back to LOOP_MODEL)
RESUME_PHASE 1 = keep current phase index on start (default resets to 0)
CLAUDE_BIN claude
@ -46,9 +47,10 @@ LOG_DIR = os.environ.get("LOG_DIR", "/srv/cc-ci/.cc-ci-logs")
# Backend is read from env, falling back to a persisted file written by `start`.
# This ensures the watchdog (which runs in its own tmux session without the caller's env)
# uses the same backend/model when it restarts a dead session.
_BACKEND_FILE = os.path.join(LOG_DIR, ".loop-backend")
_PHASES_FILE = os.path.join(LOG_DIR, ".phases-spec")
_MODEL_FILE = os.path.join(LOG_DIR, ".loop-model")
_BACKEND_FILE = os.path.join(LOG_DIR, ".loop-backend")
_PHASES_FILE = os.path.join(LOG_DIR, ".phases-spec")
_MODEL_FILE = os.path.join(LOG_DIR, ".loop-model")
_ADV_MODEL_FILE = os.path.join(LOG_DIR, ".loop-model-adv")
def _read_file_default(path, default):
try:
@ -59,6 +61,12 @@ def _read_file_default(path, default):
BACKEND = os.environ.get("LOOP_BACKEND") or _read_file_default(_BACKEND_FILE, "claude")
LOOP_MODEL = os.environ.get("LOOP_MODEL") or _read_file_default(_MODEL_FILE, "")
# Per-role override: the Adversary may run a different model (e.g. opus for review while the
# Builder runs fable). Empty → falls back to LOOP_MODEL at the call site.
ADV_MODEL = os.environ.get("ADV_MODEL") or _read_file_default(_ADV_MODEL_FILE, "")
def role_model(role):
return ADV_MODEL if (role == "adversary" and ADV_MODEL) else LOOP_MODEL
REMOTE_CONTROL = os.environ.get("REMOTE_CONTROL", "1") == "1"
CLAUDE_BIN = os.environ.get("CLAUDE_BIN", "claude")
@ -245,14 +253,15 @@ def start_agent(role, session, workdir):
kf = Path(LOG_DIR) / f".kickoff-{session}.txt"
kf.write_text(build_kickoff(role, idx))
model_flag = f"--model '{LOOP_MODEL}'" if LOOP_MODEL else ""
model = role_model(role)
model_flag = f"--model '{model}'" if model else ""
session_cwd = workdir
if BACKEND == "claude":
rc = f"--remote-control '{session}'" if REMOTE_CONTROL else ""
cmd = f"{CLAUDE_BIN} {rc} {model_flag} {CLAUDE_FLAGS} \"$(cat '{kf}')\""
log(f"starting {session} (backend=claude, phase={pid}, plan={plan}, model={LOOP_MODEL or 'default'})")
log(f"starting {session} (backend=claude, phase={pid}, plan={plan}, model={model or 'default'})")
elif BACKEND == "opencode":
# Attach each TUI to the shared opencode web server so sessions are recorded the same
# way as browser-created sessions, including a populated `path` in the DB.
@ -263,7 +272,7 @@ def start_agent(role, session, workdir):
f"set -a; . /srv/cc-ci/.testenv; set +a; "
f"NO_COLOR=1 {OPENCODE_BIN} attach {OPENCODE_SERVER} --dir {session_cwd}"
)
log(f"starting {session} (backend=opencode, phase={pid}, model={LOOP_MODEL or 'default'})")
log(f"starting {session} (backend=opencode, phase={pid}, model={model or 'default'})")
log(f" visible at http://oc.commoninternet.net (tailnet only)")
else:
die(f"unknown BACKEND '{BACKEND}' — set LOOP_BACKEND=claude or LOOP_BACKEND=opencode")
@ -619,7 +628,7 @@ def start_watchdog():
# caller's env — it would fall back to the default spec and mis-detect phase completion.
env_prefix = (
f"PHASES_SPEC='{PHASES_SPEC}' PHASE_IDX_FILE='{PHASE_IDX_FILE}' "
f"LOOP_BACKEND='{BACKEND}' LOOP_MODEL='{LOOP_MODEL}' "
f"LOOP_BACKEND='{BACKEND}' LOOP_MODEL='{LOOP_MODEL}' ADV_MODEL='{ADV_MODEL}' "
)
subprocess.run([
"tmux", "new-session", "-d", "-s", WATCHDOG_SESSION, "-c", PLAN_DIR,
@ -692,8 +701,10 @@ def main():
# Persist backend/model so the watchdog uses them when restarting dead sessions.
Path(_BACKEND_FILE).write_text(BACKEND)
Path(_MODEL_FILE).write_text(LOOP_MODEL)
Path(_ADV_MODEL_FILE).write_text(ADV_MODEL)
Path(_PHASES_FILE).write_text(PHASES_SPEC)
log(f"backend={BACKEND} model={LOOP_MODEL or '<default>'} (persisted to {_BACKEND_FILE})")
log(f"backend={BACKEND} model={LOOP_MODEL or '<default>'} "
f"adv-model={ADV_MODEL or '<same>'} (persisted to {_BACKEND_FILE})")
log(f"phases='{all_ids()}' (persisted to {_PHASES_FILE})")
start_loops()
start_watchdog()
@ -734,7 +745,7 @@ def main():
launch.py logs builder|adversary|watchdog tail a log
launch.py watchdog run watchdog in foreground
Backend: {BACKEND} Model: {LOOP_MODEL or '<default>'}
Backend: {BACKEND} Model: {LOOP_MODEL or '<default>'} Adversary model: {ADV_MODEL or '<same>'}
Phase sequence ({len(PHASES)} phases, auto-advance on ## DONE, stop after last):
{all_ids()}
""")