diff --git a/cc-ci-plan/launch.py b/cc-ci-plan/launch.py index 71fb6ba..706d244 100644 --- a/cc-ci-plan/launch.py +++ b/cc-ci-plan/launch.py @@ -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 ''} (persisted to {_BACKEND_FILE})") + log(f"backend={BACKEND} model={LOOP_MODEL or ''} " + f"adv-model={ADV_MODEL or ''} (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 ''} +Backend: {BACKEND} Model: {LOOP_MODEL or ''} Adversary model: {ADV_MODEL or ''} Phase sequence ({len(PHASES)} phases, auto-advance on ## DONE, stop after last): {all_ids()} """)