Claude
Skill
run
Sustained metric-improvement loop with atomic commits, auto-rollback, and experiment logging. Iterates with specialist agents, commits atomically, auto-rolls back on regression. Accepts a program.md file path. Supports --resume, --team, --colab, --codex, --researcher, --architect
Virus-scanned
Reviewed automatically before listing.
Download
Borda-AI-Rig-plugins_cc_research_skills_run-39e3a48.zip · 37 KB
Install
skills CLI
npx skills add https://github.com/Borda/AI-Rig/tree/main/plugins/cc_research/skills/run
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install borda-ai-rig@llmmart
Git
git clone https://github.com/Borda/AI-Rig.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole borda/ai-rig collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Files (ai-rig)
-
modes
-
codex-copilot.md 6.6 KB
<!-- Codex co-pilot mode include: loaded by research:run when --codex flag is set --> <!-- Implements Phase 2c of the R5 iteration loop --> ## Phase 2c — Codex co-pilot (`--codex` only) > **Cost-bounded gate.** Run when `--codex` confirmed at R2 AND both gates pass: > > 1. **Cost ceiling** — `CODEX_ITER < MAX_CODEX_RUNS` (default `MAX_CODEX_RUNS=10`; even with `MAX_ITERATIONS=20`, Codex runs max 10 times). > 2. **Diminishing returns** — last 2 Codex passes both produced no code changes → skip Codex remaining iterations, append note to `diary.md`: `"Codex skipped from iter N — 2 consecutive no-ops"`. **Counters live in a file, never in prose.** `CODEX_ITER` and `CODEX_NOOP_STREAK` are persisted to `.experiments/state/<run-id>/codex-state` as JSON and re-read at every gate check. Prose-tracked counters are lost to mid-run compaction — silently re-opens whole Codex budget. `CODEX_DISABLED` is **derived** at read time (`CODEX_NOOP_STREAK >= 2`), never stored — one less value that can go stale. Run at the **first** Phase 2c of the run, ahead of the gate check. The write is guarded on the file's absence, so re-running it after a resume or a compaction cannot reset a budget that is already part-spent: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r RUN_ID < "${TMPDIR:-/tmp}/research-run-id-${CSID}" 2>/dev/null || RUN_ID="" [ -z "$RUN_ID" ] && { echo "! BLOCKED — run-id sentinel missing; R2 must run before Phase 2c"; exit 1; } mkdir -p ".experiments/state/${RUN_ID}" # timeout: 3000 [ -f ".experiments/state/${RUN_ID}/codex-state" ] || printf '{"codex_iter": 0, "noop_streak": 0}\n' > ".experiments/state/${RUN_ID}/codex-state" ``` **Gate check** — reload the counters at the top of every Phase 2c, before deciding anything (bash state dies between calls; context memory dies at compaction). A missing file reads as `0/0`, so an interrupted run resumes with a fresh budget rather than a hard stop: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r RUN_ID < "${TMPDIR:-/tmp}/research-run-id-${CSID}" 2>/dev/null || RUN_ID="" _CODEX_STATE=".experiments/state/${RUN_ID}/codex-state" CODEX_ITER=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/read_state_field.py" "$_CODEX_STATE" codex_iter --default 0 2>/dev/null || echo 0) # timeout: 5000 CODEX_NOOP_STREAK=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/read_state_field.py" "$_CODEX_STATE" noop_streak --default 0 2>/dev/null || echo 0) # timeout: 5000 CODEX_DISABLED=false; [ "$CODEX_NOOP_STREAK" -ge 2 ] 2>/dev/null && CODEX_DISABLED=true echo "CODEX_ITER=$CODEX_ITER CODEX_NOOP_STREAK=$CODEX_NOOP_STREAK CODEX_DISABLED=$CODEX_DISABLED" ``` Gate fail (`CODEX_DISABLED=true` or `CODEX_ITER >= MAX_CODEX_RUNS`): skip Phase 2c, continue to Phase 3. Else print narration, update R5b before Agent call: ```text [→ Iter N/max · Phase 2c: Codex co-pilot — running (CODEX_ITER/MAX_CODEX_RUNS)] ``` TaskUpdate R5b subject: `R5b: Codex co-pilot — iter N/max_iterations running`, status: `in_progress` Codex runs second pass when active — builds on Claude's kept change or fresh attempt after revert/no-op. Codex commit evaluated by Phase 7 against `best_metric` (same rule as any iteration); "delta ≥ 0.1%" = delta against `best_metric`, not previous Claude iteration. Codex wins only if delta ≥ 0.1% AND guard passes. - Claude Phase 2 **kept**: Codex second pass on current state — builds on Claude's work. - Claude Phase 2 **reverted/no-op**: working tree restored; Codex fresh attempt on clean tree. Run Codex ideation: ```text Skill(skill="bridge:implement", args="Goal: <goal>. Run clarification: <clarification_prompt> when present. Current metric: <metric_key>=<current_value> (baseline: <baseline>, direction: <higher|lower>). Scope files: <scope_files>. Compute: <compute>. Colab hardware: <colab_hw> when active. Read .experiments/state/<run-id>/context-<i>.md. Starting state: Claude's change was [kept|reverted|no-op]. Propose and implement one atomic optimization most likely to improve the metric without breaking <guard_cmd>. Write full reasoning to .experiments/state/<run-id>/codex-ideation-<i>.md.") ``` - Claude **kept** + Codex proposes: proceed Phases 3–7 (commit, verify, guard, decide). Codex wins only if delta ≥ 0.1% AND guard passes. - Claude **kept** + Codex no-op: append `codex-no-op` record, continue — Claude's result stands. - Claude **reverted/no-op** + Codex proposes: proceed Phases 3–7. - Claude **reverted/no-op** + Codex no changes: append `status: codex-no-op` (`ideation_source: "codex"`), continue. - Set `"ideation_source": "codex"` in Phase 8 JSONL record for any Codex-proposed change. After Codex completes (any outcome): **Persist the counters** — run exactly one of the two blocks below, chosen by the outcome just recorded. Both increment `CODEX_ITER`; they differ only in what happens to the no-op streak. Never edit the numbers by hand: the blocks re-read the file so the increment survives a compaction that landed mid-iteration. Codex produced **no code changes** (`codex-no-op`) — streak grows: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r RUN_ID < "${TMPDIR:-/tmp}/research-run-id-${CSID}" 2>/dev/null || RUN_ID="" _CODEX_STATE=".experiments/state/${RUN_ID}/codex-state" _ITER=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/read_state_field.py" "$_CODEX_STATE" codex_iter --default 0 2>/dev/null || echo 0) # timeout: 5000 _STREAK=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/read_state_field.py" "$_CODEX_STATE" noop_streak --default 0 2>/dev/null || echo 0) # timeout: 5000 printf '{"codex_iter": %d, "noop_streak": %d}\n' "$((_ITER + 1))" "$((_STREAK + 1))" > "$_CODEX_STATE" ``` Codex **proposed a change** (kept, reverted, or still under evaluation) — streak resets: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r RUN_ID < "${TMPDIR:-/tmp}/research-run-id-${CSID}" 2>/dev/null || RUN_ID="" _CODEX_STATE=".experiments/state/${RUN_ID}/codex-state" _ITER=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/read_state_field.py" "$_CODEX_STATE" codex_iter --default 0 2>/dev/null || echo 0) # timeout: 5000 printf '{"codex_iter": %d, "noop_streak": 0}\n' "$((_ITER + 1))" > "$_CODEX_STATE" ``` Streak reaching 2 is what disables Codex for the rest of the run — the gate check above derives that, so nothing else needs writing. TaskUpdate R5b subject: `R5b: Codex co-pilot — iter N done (<outcome>)` **Stuck escalation with `--codex`**: Phase 9 detects `STUCK_THRESHOLD` discards and `--codex` active → increase Codex effort — add to prompt: "Previous N attempts all reverted. Focus on fundamentally different approach (different file, different algorithm, different abstraction)." -
colab-setup.md 1.9 KB
<!-- file: colab-setup.md — consumers: plugins/cc_research/skills/run/SKILL.md --> Execute this section only when `--colab` flag is set. Skip entirely for local or docker runs. **Purpose**: route metric verification and GPU code testing to Colab runtime instead of local. Essential for ML training metrics, CUDA benchmarks, GPU-required workloads. **Hardware selection** (`--colab=HW`): specify GPU type (optional). Known: `H100`, `L4`, `T4`, `A100`. Omitted → Colab picks default. Advisory — actual hardware configured in notebook UI. Claude Code validates GPU identity at Phase 5 via `torch.cuda.get_device_name()` assertion; halts if mismatch. <!-- policy-sibling: run/SKILL.md, plan/SKILL.md, sweep/SKILL.md:4, judge/SKILL.md — known-hardware set restated in each; keep in sync (plugins/CLAUDE.md §Policy Duplication Marker). --> **Setup** (before running `--colab`): 1. Add `"colab-mcp"` to `enabledMcpjsonServers` in `settings.local.json`: ```json { "enabledMcpjsonServers": [ "colab-mcp" ] } ``` 2. Ensure `colab-mcp` server defined in `.mcp.json` under `mcpServers` (see project `.mcp.json`). 3. Open Colab notebook with runtime connected and execute MCP connection cell. **How it works during a run:** - Step R2 (preconditions): checks for `mcp__colab-mcp__runtime_execute_code` availability. - Phase 5 (verify metric): calls `mcp__colab-mcp__runtime_execute_code` with `metric_cmd` instead of local `timeout <cmd>`. - Phase 2 (ideate): `research:scientist` agent can call `mcp__colab-mcp__runtime_execute_code` to prototype GPU code before committing. - `VERIFY_TIMEOUT_SEC` = 300 (vs 120 local) to account for network + GPU startup latency. If Colab MCP unavailable at R2, print: ```markdown ⚠ Colab MCP not available. To enable: 1. Add "colab-mcp" to enabledMcpjsonServers in settings.local.json 2. Open a Colab notebook and connect the runtime 3. Execute the MCP connection cell in the notebook Then re-run with --colab. ``` -
compute-docker.md 1.4 KB
<!-- file: compute-docker.md — consumers: run/SKILL.md --> # Docker Sandbox Phases (compute: docker mode) These phases execute only when `sandbox_mode = "docker"`. When `sandbox_mode = "local"`, skip this entire file. ## Phase 2a — Sandbox validate Skip entirely if `sandbox_mode = "local"`. If Phase 2 returned non-empty `"scripts"`: run each in Docker sandbox with read-only project mount. Per script (use `${SANDBOX_NETWORK}` initialized at R2 — Phase 5 uses identical pattern): ```bash SANDBOX_NETWORK="${SANDBOX_NETWORK}" python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/docker_sandbox_run.py" --mode explore ".experiments/state/${RUN_ID}/scripts/${script}" ``` Use Bash tool `timeout`: `timeout: $VERIFY_TIMEOUT_MS` (computed in R2 from `$VERIFY_TIMEOUT_SEC`). Not shell `timeout` command. If any script exits non-zero: append `status: sandbox-failed` to `ideation-<i>.md`, skip to Phase 8 with `status: sandbox-failed`. Do not proceed to 2b. If `"scripts"` empty or absent: 2a no-op — proceed to 2b. ## Phase 2b — Apply change Skip if `sandbox_mode = "local"` (Phase 2 already applied changes). Spawn same specialist agent (R3), `maxTurns: 10`: ```text Read proposed change in `.experiments/state/<run-id>/ideation-<i>.md`. Apply proposed change to source files. Use Write and Edit tools ONLY — no Bash execution on codebase files. Scope files (read and modify only these): <scope_files> Return ONLY: {"files_modified":[...]} ``` -
hypothesis-pipeline.md 5 KB
# Hypothesis Pipeline — run/SKILL.md sidecar Loaded by Step R0 when `--researcher` or `--architect` active. Contains oracle agent orchestration, feasibility annotation, queue filtering, checkpoint resume. > **Research run directory**: outputs (`hypotheses.jsonl`, `checkpoint.json`, `journal.md`) go to `.experiments/<run-id>/` — timestamped dir created at R0 start, distinct from `.experiments/state/<run-id>/`. Called `<RUN_DIR>` throughout. See `protocol.md` (companion file, same skill dir) for layout. **Spawn note**: oracle agents run in the background — issue the batch, then end the turn; no filler call, no "waiting" line, no sleep (CLAUDE.md §6). On completion notifications, check each oracle's output (e.g. `<RUN_DIR>/oracle-researcher.md`); missing or empty → surface with ⏱, continue with partial hypotheses or empty queue if none written. 1. **Build hypothesis queue** — if `--hypothesis <path>` provided, read as pre-built queue (skip oracle phase). Otherwise spawn oracle agents per active flags — parallel if both set: **If `--researcher` set** — spawn `research:scientist` (`maxTurns: 15`): ```text Read the program file and the project codebase. Generate 5–10 ML experiment hypotheses grounded in SOTA literature and the specific metric goal. Write to `<RUN_DIR>/hypotheses.jsonl` — one JSON object per line, each with fields: hypothesis, rationale, confidence (float 0–1), expected_delta, priority (int, 1=highest), source: "oracle", feasible (bool — grounded in the codebase you just read), blocker (str|null, required if feasible=false), codebase_mapping (str — files/classes/functions the change touches). Write your full analysis, reasoning, and Confidence block to `<RUN_DIR>/oracle-researcher.md` using the Write tool. Return ONLY: {"status":"done","file":"<path>","count":N,"feasible":N,"confidence":0.N} ``` **If `--architect` set** — spawn `foundry:solution-architect` (`maxTurns: 15`) as hypothesis generator (not just feasibility annotator): ```text Read the program file and the project codebase. Analyze the architecture, coupling, and structural design. Generate 5–10 architectural optimization hypotheses (refactoring opportunities, coupling reductions, abstraction improvements) that could improve the metric. Write to `<RUN_DIR>/hypotheses-arch.jsonl` — one JSON object per line with the same schema as the research oracle (hypothesis, rationale, confidence, expected_delta, priority, source: "architect", feasible, blocker, codebase_mapping — annotate feasibility yourself from the codebase you just read). Write your full analysis, reasoning, and Confidence block to `<RUN_DIR>/oracle-solution-architect.md` using the Write tool. Return ONLY: {"status":"done","file":"<path>","count":N,"confidence":0.N} ``` **Both `--researcher` and `--architect` set**: run both oracle agents parallel. After both done, merge JSONL files into `<RUN_DIR>/hypotheses.jsonl`, interleaving by priority (lower = higher priority, round-robin on ties). Update priorities to reflect interleaved order. No separate feasibility-annotation spawn — each oracle annotates its own hypotheses (`feasible`/`blocker`/`codebase_mapping` are in both prompts above; both oracles already read the codebase). Dedicated `foundry:solution-architect` annotator pass costs ~120,851 tok fixed overhead to re-read same codebase for facts generating oracle already had in hand — annotation is factual codebase mapping, not adversarial review. Entries missing the fields after an oracle returns (older queue files, partial output): backfill `feasible: true`, `blocker: null`, `codebase_mapping: ""` and flag the count in the R0 summary. Both agents follow handoff envelope protocol (CLAUDE.md §2). Schema: `protocol.md` (companion file, same skill dir). 2. **Filter and sort** — load annotated queue. Infeasible (`feasible: false`) stay for audit, excluded from execution. Sort by `priority` ascending (1 = first). 3. **Resume skip** — if `<RUN_DIR>/checkpoint.json` exists (resuming crashed run), read it. Skip any hypothesis whose 0-indexed position matches `hypothesis_id` in checkpoint. 4. Store active queue in memory as `RESEARCH_QUEUE`. 5. **Bridge availability re-check** — downstream review skill dispatched after this pipeline (Step 6+) → re-verify the bridge is reachable before invoking. Silent stalls happen when it is absent at dispatch time despite being present at run start. Probe the exact installed selector, not the Codex CLI: the CLI on `PATH` says nothing about whether `bridge@borda-ai-rig` is installed and enabled. ```bash CODEX_STATUS=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/check_bridge.py" --status 2>/dev/null || echo "absent") # timeout: 5000 [ "$CODEX_STATUS" = "available" ] && CODEX_AVAILABLE=1 || { CODEX_AVAILABLE=""; echo "⚠ bridge@borda-ai-rig is ${CODEX_STATUS} — skipping bridge review step"; } ``` On empty `CODEX_AVAILABLE`: skip review dispatch, continue with claude-only pipeline. Never block — fall back to single-source review. -
phase5-metric.md 2.9 KB
# Phase 5 — Verify metric — run/SKILL.md sidecar Loaded by the main iteration loop (Phase 5 step) in `run/SKILL.md`. #### Phase 5 — Verify metric **If `sandbox_mode = "docker"`**: ```bash SANDBOX_NETWORK="${SANDBOX_NETWORK}" python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/docker_sandbox_run.py" --mode verify "$METRIC_CMD" ``` Wrapper mounts project read-only, `.experiments` read-write, runs under `python:3.11-slim` with read-only rootfs, dropped Linux caps, `no-new-privileges` (network via `SANDBOX_NETWORK`, default `none`). No CPU/memory caps. Use Bash tool `timeout` parameter (not shell `timeout`): `timeout: $VERIFY_TIMEOUT_MS`. **If `sandbox_mode = "local"`**: Run `metric_cmd` via Bash (`timeout: $VERIFY_TIMEOUT_MS`). Not shell `timeout`. Different CWD → separate `cd <path>` call first. Complex metric parsing → write parser to `.experiments/state/<run-id>/scripts/parse-metric-<i>.py`, run with `python <path>` — no inline one-liner. **If `--colab` active**: routes through `mcp__colab-mcp__runtime_execute_code`; Docker not used (`--colab` + `--compute=docker` conflict caught at R2). `colab_hw` non-null → prepend GPU identity check via `mcp__colab-mcp__runtime_execute_code` — substitute configured hardware name (env var `COLAB_HW` overrides config) into assertion string before sending: ```python import os, torch expected_hw = os.environ.get("COLAB_HW", "") # falls back to colab_hw from state.json injected at call site actual = torch.cuda.get_device_name(0) if expected_hw and expected_hw not in actual: raise AssertionError(f"Wrong GPU: expected {expected_hw!r}, got {actual!r}") ``` Assertion raises → print `"⚠ GPU mismatch: requested ${colab_hw} but runtime has {actual}. Change the Colab runtime type and re-run."` Stop — do not proceed to Phase 6. `colab_hw` null or `COLAB_HW` env var unset → check is no-op (environment-specific validation skipped). <!-- Colab assertion: MCP call, not Bash — exempt from the script-file rule; correct as an inline one-liner. --> Timeout expires → refresh sentinel (use REPO_SLUG and BRANCH_SLUG from `<constants>` — re-derive per canonical formula, then `touch "${TMPDIR:-/tmp}/claude-commit-auth-${REPO_SLUG}-${BRANCH_SLUG}"` <!-- tmpdir-exempt: user-shell-boundary -->), append `status: timeout`, then revert only if not already reverted this iteration: <!-- policy-sibling: plugins/cc_research/skills/run/SKILL.md §Phase 7 double-revert guard --> ```bash # revert subject embeds the original ("Revert \"experiment(...)\"") — anchor at subject start, never substring [ -n "$I" ] || { echo "! BLOCKED — Phase 5: iteration number unset, cannot scope revert guard"; exit 1; } if git log -1 --format=%s 2>/dev/null | grep -qE "^experiment\(optimize/i${I}\):"; then git revert HEAD --no-edit # timeout: 15000 else echo "Phase 5: HEAD is not iteration ${I}'s experiment commit — already reverted; skipping double-revert." fi ``` Continue loop. -
report.md 1.9 KB
# Campaign Report Format — run/SKILL.md sidecar Loaded by Step R6 at end of campaign run. Report structure and terminal summary format. ## Report structure ```markdown --- Run — [goal] Date: [YYYY-MM-DD] Scope: [program.md path] / [N] iterations planned Focus: ML optimization run Agents: [agents actually dispatched this run — from the R3 strategy resolution + any R0/team spawns, never this placeholder verbatim] Outcome: GOAL_ACHIEVED | IMPROVED | STALLED | DIVERGED Best: [metric_key] = [best] ([delta]% improvement) Confidence: [score] — [key gaps] Next steps: /research:retro | /research:fortify | /research:run --resume Path: → .reports/research/run-<branch>-<date>.md --- ## Run: <goal> **Run ID**: <run-id> **Date**: <date> **Iterations**: <total> (<kept> kept, <reverted> reverted, <other> other) **Baseline**: <metric> = <baseline value> **Best**: <metric> = <best value> (<delta>% improvement) **Best commit**: <sha> **Diary**: ".experiments/state/<run-id>/diary.md" **Codex co-pilot**: active (ran every iteration) — <N> Codex passes run (omit line if --codex not used) **Codex wins**: <N> Codex proposals kept vs <N> Claude proposals kept ### Experiment History | # | Metric | Delta | Status | Description | Agent | Confidence | | --- | ------ | ------ | -------- | ----------- | ----- | ---------- | | N | value | +X.X% | status | desc | agent | 0.N | ### Summary [2-3 sentences on what strategies worked, what didn't, what to try next] ### Recommended Follow-ups - [next action] ``` ## Terminal summary format ```text --- Run — <goal> Iterations: <total> Kept: <kept> Reverted: <reverted> Baseline: <metric_key> = <baseline> Best: <metric_key> = <best> (<delta>% improvement, commit <sha>) Agent: <agent type used> → saved to .reports/research/run-<branch>-<date>.md → diary: .experiments/state/<run-id>/diary.md --- ``` -
resume.md 1.5 KB
# Resume Mode — run/SKILL.md sidecar Loaded by `run/SKILL.md` when `--resume` flag is set. ## Resume Mode Triggered by `--resume` flag (with optional `<file.md>` argument). **Locating the run**: - `resume` (no argument): scan `.experiments/state/`, select run with latest `started_at` and `status: running`. - `resume <file.md>`: resolve path to absolute. Scan all run dirs, filter by `"program_file"` matching. Pick latest `started_at`. If no match: stop with error. 1. Read `state.json`. Restore `clarification_prompt` and `colab_hw` from it (may be null). 2. **Re-parse program file**: if `program_file` non-null, re-read/re-parse (R1 rules), update config — applies edits made between runs. Edits during active loop take effect only on next `resume`. 3. **Validate `experiments.jsonl`**: read last line, parse as JSON. If truncated or invalid: invoke `AskUserQuestion` tool — question: "experiments.jsonl last line appears corrupt (truncated or invalid JSON). How to proceed?", (a) label: `truncate corrupt entry and resume`, (b) label: `abort — fix manually`. If (a), remove last line; if (b), stop. 4. Validate git HEAD: if diverged from `state.json.best_commit` unexpectedly, invoke `AskUserQuestion` tool — question: "HEAD has diverged from best_commit in state.json. Continue anyway?", (a) label: `yes, continue from current HEAD`, (b) label: `no, abort`. If (b), stop. 5. Continue loop from `state.json.iteration + 1`. `diary.md` NOT re-initialized — entries append to existing file. -
team.md 13.1 KB
<!-- Team mode include: loaded by research:run when --team flag is set --> <!-- Implements one mode extension: Team Mode (--team flag, Phases A–D) --> <!-- Triggered from Step R5 of Default Mode when --team is active --> ## Team Mode (`--team`) **When to trigger**: goal spans multiple optimization axes (e.g., "improve training speed" = model architecture + data pipeline + compute efficiency), OR user passes `--team`. **Architecture**: two-phase pipeline — parallel hypothesis generation (read-only, no code changes) then sequential implementation on live codebase ordered minimal→largest change scope. No cross-axis conflicts: no worktrees, no cherry-picking; each implementation step sees cumulative state of all prior kept changes. **Team Mode directory layout**: - `<RUN_DIR>` (`.experiments/run-team-<timestamp>/`, from `make_run_dir "run-team"`) — hypothesis artifacts: `hypotheses-<axis-slug>.jsonl`, `hypothesis-analyst-<axis-slug>.md`, `team-queue.jsonl`, `team-results.jsonl` - `.experiments/state/<run-id>/` — standard iteration artifacts: `ideation-team-<M>.md`, `diary.md`, `experiments.jsonl` **Workflow:** ### Phase A: Parallel Hypothesis Generation (read-only) 1. Lead completes Steps R1–R4 (config, preconditions, baseline) solo. 2. Lead identifies 2–3 distinct optimization axes from goal + codebase analysis. Example for "reduce training time": model architecture · data pipeline · compute efficiency. 3. Lead creates run output directory: ```bash RUN_DIR=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/make_run_dir.py" "run-team" ".experiments") # timeout: 5000 ``` Store `RUN_DIR` as run-level variable — do not re-evaluate at later phases. All `<RUN_DIR>` refs in Phases B, C, D use same value. Write `phase: "A"` to `state.json` immediately after creating `RUN_DIR` — enables Phase A resume: ```json {"team_mode": {"phase": "A", "run_dir": "<RUN_DIR>"}} ``` 4. Spawn 2–3 hypothesis agents in parallel (reasoning agents at `opus` per CLAUDE.md §Agent Teams). **No worktrees** — read-only analysis only. Each agent gets one axis + matching specialist type (same `agent_strategy` mapping from SKILL.md constants). **Axis-slug rule — the lead owns it, the agent never derives it.** For each axis the lead computes `<axis-slug>` from the axis name by: lowercasing, replacing every run of characters outside `[a-z0-9]` with a single `-`, then trimming leading and trailing `-`. Example: axis `Data Loading & Aug` → `data-loading-aug`. The lead then **substitutes the resulting literal paths into that agent's prompt before constructing the `Agent()` call**: the template below must reach the agent with `<RUN_DIR>` and `<axis-slug>` already replaced by concrete text (e.g. `.experiments/run-team-2026-05-13T10-00-00Z/hypotheses-data-loading-aug.jsonl`). Unsubstituted placeholder reaches agent as literal text: agent writes to wrongly-named path, liveness check below reports false ⏱ on agent that in fact succeeded. The lead **records the exact filename pair it put in each prompt** (jsonl path, md path) and checks those recorded paths — never a slug re-derived after the fact. Each hypothesis agent's spawn prompt: ```markdown Read ~/.claude/TEAM_PROTOCOL.md and use AgentSpeak v2. You are a hypothesis analyst. Your axis: <axis description>. Agent type: <agent type>. READ-ONLY: do NOT modify source files. You may only write to your designated output files. Analyze the codebase through the lens of your axis. Generate 3–5 concrete, implementable hypotheses. Baseline metric: <metric_cmd key> = <baseline>. Direction: <higher|lower>. Scope files: <scope_files>. Run clarification: <clarification_prompt> ← omit this line entirely if clarification_prompt is null Program constraints: read <program_file> — especially ## Notes, ## Config. For each hypothesis, produce a JSON object with ALL these fields: - hypothesis: concrete description of the change - rationale: why this should improve the metric - confidence: float 0–1 - expected_delta: expected metric change (e.g. "+1–3% val_loss") - priority: int (1 = highest within this axis) - source: "team" - axis: "<axis name>" - agent_type: "<your agent type>" - change_scope: "small" | "medium" | "large" - feasible: true | false - blocker: null | "<blocker description if feasible=false>" - codebase_mapping: "<files, classes, or functions to change>" change_scope guide: - small: 1–2 files, localized change (parameter tweak, single-function edit) - medium: 3–5 files, cross-cutting but bounded (module refactor, data path change) - large: 6+ files or architectural restructuring Write all hypotheses as JSONL (one JSON object per line) to `<RUN_DIR>/hypotheses-<axis-slug>.jsonl` — use the path exactly as given, do not rename it. Write your full analysis, reasoning, and Confidence block to `<RUN_DIR>/hypothesis-analyst-<axis-slug>.md` using the Write tool. Return ONLY: {"status":"done","axis":"<axis>","count":N,"file":"<jsonl path>","confidence":0.N} Call TaskUpdate(in_progress) when starting; TaskUpdate(completed) when done. ``` **Spawn note**: hypothesis agents run in the background — issue the batch, then end the turn; no filler call, no "waiting" line, no sleep (CLAUDE.md §6). On completion notifications, check jsonl path **recorded for that agent at spawn time** (step 4's axis-slug rule) — not a freshly re-derived slug. Missing or empty → glob `<RUN_DIR>/hypotheses-*.jsonl` for a near-match first, since a mismatched name is a naming bug rather than a dead agent; still nothing → that agent timed out: read any partial output, surface with ⏱ in the Phase D report, never silently omit. 5. Collect compact JSON envelopes from all hypothesis agents. Do not read `.md` analysis files into lead context — inputs to Phase B queue assembly only. **`--researcher` / `--architect` interaction**: if R0 pre-phase ran before Team Mode, R0 hypotheses in `<RUN_DIR>/hypotheses.jsonl` included in Phase B queue assembly alongside axis hypotheses. R0 entries lacking `axis`/`agent_type`/`change_scope` backfilled: `axis: "cross-cutting"`, `agent_type` inferred from `source` field, `change_scope` inferred from `codebase_mapping` length (1–2 targets = small, 3–5 = medium, 6+ = large). ### Phase B: Queue Assembly + User Gate 1. Read all `<RUN_DIR>/hypotheses-*.jsonl` files (and `<RUN_DIR>/hypotheses.jsonl` if present from R0). 2. Filter: exclude `feasible: false` (retain in file for audit). Move `confidence < 0.7` entries to queue end. 3. Sort combined queue: - Validate `change_scope` before sorting: must be one of `{small, medium, large}`; other value → warn (`⚠ Unknown change_scope '<value>' on hypothesis '<hypothesis>' — defaulting to medium`) and set to `medium`. - Primary: `change_scope` ascending — `small` first, then `medium`, then `large` - Secondary: `expected_delta` descending within scope tier (parse delta string to extract numeric midpoint, e.g., "+1–3%" → 2.0; unparsable → treat as 0, sort to end of scope tier) - Tertiary (tiebreaker): `confidence` descending 4. Assign sequential `queue_position` (1-indexed) in sorted order. 5. Print queue as formatted table: ```text # · Hypothesis · Axis · Scope · Expected Delta · Conf. · Agent 1 · Cache embeddings in forward pass · data pipeline · small · +2-4% speed · 0.90 · perf-opt 2 · Fuse batch-norm + conv layers · model architecture · small · +1-2% speed · 0.85 · research:scientist ... · ... · ... · ... · ... · ... · ... Total: N hypotheses (N small, N medium, N large) across N axes ``` Before user gate, update `state.json` `team_mode.phase` to `"B"` — enables resume re-display of queue if interrupted: ```json {"team_mode": {"phase": "B", "run_dir": "<RUN_DIR>"}} ``` 6. Present user gate via `AskUserQuestion`: ```text Proceed with implementation? (a) Run all N hypotheses in order shown (b) Select specific hypotheses (enter numbers, e.g. "1,3,5-7") (c) Abort ``` - (a) proceeds with full queue - (b) filters to selected entries, preserving sort order - (c) stops; write partial report noting hypotheses generated but not tested 7. Write final ordered queue to `<RUN_DIR>/team-queue.jsonl` (one JSON object per line, execution order). Add `team_mode` to `state.json`: ```text { "team_mode": { "axes": ["<axis-1>", "<axis-2>"], "phase": "C", "queue_file": "<RUN_DIR>/team-queue.jsonl", "current_hypothesis": 0, "total_hypotheses": N } } ``` ### Phase C: Sequential Implementation + Guard For each hypothesis in `<RUN_DIR>/team-queue.jsonl` (sorted order, 1-indexed as M of N): 1. **Print header**: `[→ Team Hyp M/N · axis: <axis> · scope: <change_scope> · "<hypothesis short>"]` 2. **Spawn specialist agent** matching `agent_type` from hypothesis. **On real codebase** — no worktree. Spawn prompt follows R5 Phase 2 ideation template with hypothesis pre-specified: ```markdown Goal: <goal> Run clarification: <clarification_prompt> ← omit if null Current metric: <metric_cmd key> = <current_value> (baseline: <baseline>, direction: <higher|lower>) Scope files: <scope_files> Program constraints: read <program_file> Focus this iteration on implementing this hypothesis: "<hypothesis text>" Rationale: <rationale> Expected change scope: <change_scope> Target files: <codebase_mapping> Propose and implement ONE atomic change. Write analysis to `.experiments/state/<run-id>/ideation-team-<M>.md` using the Write tool. Return ONLY: {"description":"...","files_modified":[...],"scripts":[],"confidence":0.N} ``` 3. **Run R5 Phases 3–7a identically** (verify changed files → commit → run metric → run guard → keep/rework/rollback → write diary entry). Phase 8 writes to `experiments.jsonl` and `state.json` as in standard mode; Phase 9 progress checks apply as in standard mode (stuck detection, diminishing returns, context compaction). Phase C does not duplicate this logic — uses same per-phase steps with hypothesis-driven ideation output from step 2. 4. **Log outcome** to `<RUN_DIR>/team-results.jsonl` (append, one line per hypothesis): ```json { "queue_position": 1, "hypothesis": "<text>", "axis": "<axis>", "agent_type": "<agent>", "change_scope": "<scope>", "metric_before": 0.0, "metric_after": 0.0, "delta_pct": 0.0, "status": "kept|reverted|rework|no-op|hook-blocked|timeout", "commit": "<sha or null>", "timestamp": "<ISO>" } ``` 5. **If kept**: update running current metric value for next hypothesis. Each subsequent hypothesis sees cumulative state of all prior kept changes. 6. Update `state.json` `team_mode.current_hypothesis` after each hypothesis (enables resume). 7. `--codex`, `--colab`, `--journal` flags apply identically to standard R5. ### Phase D: Consolidated Report After all hypotheses processed (or user stops early with Ctrl-C / user abort): 1. Read `<RUN_DIR>/team-results.jsonl`. 2. Write full report to `.reports/research/run-team-<branch>-<YYYY-MM-DD>.md`: ```bash mkdir -p .reports/research # timeout: 3000 ``` ```markdown ## Team Run: <goal> **Run ID**: <run-id> **Date**: <date> **Axes**: <comma-separated list> **Hypotheses tested**: <kept> kept · <reverted> reverted · <other> other (of <total>) **Baseline**: <metric> = <baseline> **Final**: <metric> = <final> (<total delta>%) ### Per-Hypothesis Results | # | Hypothesis | Axis | Scope | Δ% | Status | Commit | |----|-----------------------|---------|--------|--------|----------|--------| | 1 | Cache embeddings … | data | small | +2.1% | kept | abc123 | ### Per-Axis Summary | Axis | Tested | Kept | Best Δ% | Cumulative Δ% | |--------------------|--------|------|---------|----------------| | data pipeline | 3 | 2 | +2.1% | +2.9% | ### Summary [2–3 sentences on what strategies worked, cross-axis interactions observed] ### Recommended Follow-ups - [next action if metric goal not fully reached] ``` 3. Print compact terminal summary: ```text --- Team Run — <goal> Hypotheses tested: <total> Kept: <kept> Reverted: <reverted> Axes: <comma-separated list> Baseline: <metric_key> = <baseline> Final: <metric_key> = <final> (<total delta>% improvement) → saved to .reports/research/run-team-<branch>-<date>.md --- ``` 4. No teammates to shut down — hypothesis agents completed in Phase A; Phase C implementation agents are one-shot spawns. **CLAUDE.md §6**: Phase A health monitoring described above (after step 4). Phase C implementation agents = standard single-iteration spawns — same timeouts as R5. **Resume support**: `resume` mode reads `state.json.team_mode` to determine phase: - `phase: "A"` — re-run Phase A from scratch (read-only, cheap to repeat) - `phase: "B"` — re-display queue, re-prompt user gate - `phase: "C"` — resume from `current_hypothesis + 1` (completed entries already in `team-results.jsonl`) - `phase: "D"` — re-generate report
-
-
protocol.md 8.6 KB
--- description: JSONL schema for hypotheses.jsonl, checkpoint.json, and journal.md entry format used by research:run --researcher and --architect paths: - .experiments/** - plugins/cc_research/skills/run/** --- ## Run Directory Layout Canonical layout — other run/mode files point here rather than re-explaining it. A run uses two sibling dirs under `.experiments/`, keyed by same `<run-id>`: ```text .experiments/state/<run-id>/ ← per-iteration state (all modes) state.json ← iteration count, best metric, status (resume anchor scanned by find_run_id.py) experiments.jsonl ← one line per iteration diary.md ← human-readable research diary .experiments/<run-id>/ ← hypothesis-pipeline artifacts (--researcher / --architect / --journal) hypotheses.jsonl ← annotated hypothesis queue (oracle + feasibility) checkpoint.json ← per-iteration state for --resume journal.md ← structured learning log, appended after every iteration (when --journal is set) ``` > **Planned unification (code alignment needed — not yet migrated)**: two run-scoped dirs are known duplication (re-explained across several files). Target = one dir per run — merge pipeline artifacts under `.experiments/state/<run-id>/`, leaving `state.json` in place so `find_run_id.py` / `read_state_field.py` resume discovery untouched. Blocked on coordinated change: producers (`run/SKILL.md` `RUN_DIR`, `hypothesis-pipeline.md`, `team.md`), every `<RUN_DIR>` substitution, README/protocol docs must move together; team mode also derives its own `.experiments/run-team-<ts>/` via `make_run_dir`, stores that `run_dir` path in `state.json` — migration must not break team-mode resume. ## hypotheses.jsonl Schema One JSON obj per line. Single-pass write — each oracle annotates its own entries (a separate solution-architect annotation spawn was removed: generating oracle already read codebase; second agent pass costs full spawn overhead to re-derive same facts): **Core fields (every oracle):** | Field | Type | Description | | -- | -- | -- | | `hypothesis` | `str` | What to test — concrete, implementable change | | `rationale` | `str` | Literature or experiment grounding | | `confidence` | `float` | Oracle confidence [0–1]; entries < 0.7 deprioritized to queue end | | `expected_delta` | `str` | Expected metric change (e.g. `"+1–3% val_loss"`) | | `priority` | `int` | Execution order (1 = highest); journal-sourced entries use lower values than oracle entries | | `source` | `str` | `"oracle"` for researcher; `"journal"` for journal-sourced; `"team"` for team-mode hypothesis agents (Phase A); `"retro"` for `/research:retro` output (feasibility fields absent — treated as `feasible: true`); `"architect"` for architect-only entries | **Feasibility fields (written by the same oracle in the same pass):** | Field | Type | Description | | -- | -- | -- | | `feasible` | `bool` | `true` if codebase supports change with reasonable effort | | `blocker` | `str \| null` | Required if `feasible: false`; names specific architectural blocker | | `codebase_mapping` | `str` | Files, classes, or functions needing change | Entries missing the feasibility fields (retro output, older queue files) are treated as `feasible: true`, `blocker: null`, `codebase_mapping: ""`. **Complete valid oracle entry:** ```json { "hypothesis": "...", "rationale": "...", "confidence": 0.85, "expected_delta": "+2% val_acc", "priority": 1, "source": "oracle", "feasible": true, "blocker": null, "codebase_mapping": "src/model.py:Encoder.forward" } ``` ## Feasibility Filter Rules - `feasible: false` entries skipped in execution; remain for audit - `confidence < 0.7` → end of queue, not removed - Move low-confidence: assign `priority` > max in queue; don't reorder lines (preserve JSONL append order for audit) - Solution-architect must **preserve hypothesis order** when annotating; no re-rank - `blocker` required when `feasible: false` — blank/null blocker on false entry = schema violation - `source: "architect"` and `source: "retro"` entries may omit `feasible`/`blocker`/`codebase_mapping`; absent fields treated as `feasible: true` by all consumers ## checkpoint.json Schema Written after every iteration; `--resume` uses to skip completed: ```json { "iteration": 3, "hypothesis_id": 2, "metric_before": 0.842, "metric_after": 0.861, "status": "passed" } ``` | Field | Type | Values | | -- | -- | -- | | `iteration` | `int` | 1-indexed; monotonically increasing | | `hypothesis_id` | `int` | 0-indexed position in `hypotheses.jsonl` | | `metric_before` | `float` | Metric value before applying hypothesis | | `metric_after` | `float` | Metric value after applying hypothesis | | `status` | `str` | `"passed"` or `"rolled_back"` | - Completed iteration in `checkpoint.json` = idempotent — skip, don't re-run - `status: "rolled_back"` must still write — partial results = audit data - `status: "rolled_back"` = idempotent on `--resume` same as `passed`; only hypotheses with no checkpoint entry execute ## journal.md Entry Format Active with `--journal`. Appended after EVERY iteration (kept and reverted). Location: `<RUN_DIR>/journal.md`. Never overwrite — always append. Each entry: ```markdown ## Iteration N — YYYY-MM-DD **Approach**: <agent's description from Phase 2 JSON — the proposed change> **Outcome**: <kept | reverted | rework | no-op | hook-blocked | timeout> **Metric delta**: <metric_before> → <metric_after> (<+/->X.X%) — or "n/a" if no metric was measured **Why kept / why reverted**: <one sentence — e.g. "Metric improved 1.2% with guard passing" or "Reverted: guard failed after 2 rework attempts; test_model.py broke" or "No files changed"> **Avoid repeating**: <yes | no> — yes if outcome was reverted/blocked/no-op AND approach was not a transient failure (e.g. hook issue); no if kept or if the failure was infrastructure (timeout, hook), not the approach itself **Pattern**: <cross-iteration observation if ≥3 journal entries exist, otherwise "n/a"> --- ``` Rules: - `Avoid repeating: yes` → Phase 2 ideation skips similar approaches (same file, technique, abstraction) - `Pattern` emerges after 3+ entries — synthesize what works/doesn't across run - At iteration 3, Pattern required if trend observable. Write "n/a" only if \<3 entries — not placeholder when entries exist but no trend; if no trend at 3+ entries, write "insufficient signal — no consistent pattern across N iterations" - No threshold filtering — all iterations recorded regardless of delta - `Why kept / why reverted` must be substantive — not "it worked"/"it failed"; name mechanism or failure mode > **Note**: `diary.md` (`.experiments/state/<run-id>/diary.md`) = Phase 7a always-active iteration record. `journal.md` via `--journal` = structured learning log in `.experiments/<run-id>/`, distinct from state diary. ## Journal-Sourced Hypothesis Rules - Never execute journal hypothesis without feasibility annotation — `feasible` required before campaign loop; the lead annotates journal entries itself when queuing them (it has full run context — no annotator spawn) - Journal hypotheses inherit oracle JSONL schema; `source: "journal"` only distinguishing field - `priority` must be numerically higher than all oracle entries — journal hypotheses run after queue exhausted ## Team Mode Extensions `--team` active: Phase A hypothesis agents produce `source: "team"` entries with 3 **required** additional fields: | Field | Type | Description | | -- | -- | -- | | `axis` | `str` | Optimization axis (e.g., `"model architecture"`) | | `agent_type` | `str` | Specialist agent for implementation (e.g., `"perf-optimizer"`, `"researcher"`) | | `change_scope` | `str` | Estimated blast radius: `"small"` (1–2 files), `"medium"` (3–5 files), `"large"` (6+ files or architectural) (primary Phase B sort key — small runs first) | Team entry missing any of 3 fields = schema violation (like missing `blocker` on infeasible entry). **Backfill rule** (R0 `--researcher`/`--architect` entries merged into team queue): see Phase A Step 5 in `./modes/team.md`. **Team-mode output files** (in `<RUN_DIR>/`, alongside `hypotheses.jsonl`): | File | Written by | Description | | -- | -- | -- | | `hypotheses-<axis-slug>.jsonl` | Phase A agents | Per-axis raw hypothesis output; merged into queue in Phase B | | `hypothesis-analyst-<axis-slug>.md` | Phase A agents | Full analysis and Confidence block (file-handoff protocol) | | `team-queue.jsonl` | Phase B lead | Final ordered execution queue (post-sort, post-user-gate) | | `team-results.jsonl` | Phase C lead | Per-hypothesis outcome log (kept/reverted, metric delta, commit SHA) | -
SKILL.md 49.3 KB
--- name: run description: Sustained metric-improvement loop with atomic commits, auto-rollback, and experiment logging. Iterates with specialist agents, commits atomically, auto-rolls back on regression. Accepts a program.md file path. Supports --resume, --team, --colab, --codex, --researcher, --architect, --journal, --hypothesis. argument-hint: <program.md> [clarification] [--resume <program.md>] [--team] [--compute=local|colab|docker] [--colab[=H100|L4|T4|A100]] [--codex] [--researcher] [--architect] [--journal] [--hypothesis <path>] [--scientist] [--codemap] [--no-codemap] [--keep "<items>"] effort: xhigh allowed-tools: Read, Write, Edit, Bash, Grep, Glob, Agent, TaskCreate, TaskUpdate, AskUserQuestion disable-model-invocation: true --- <objective> Sustained metric-improvement loop — reads `program.md`, iterates specialist ideation agents, commits atomically, auto-rolls back on regression. For long-running automated improvement campaigns. NOT for: methodology validation before run (use `/research:judge`); hypothesis generation (use `research:scientist` agent); one-off feature work (use `/develop:feature`); no `program.md` yet / starting from a bare goal (use `/research:sweep`). </objective> <constants> Campaign mode only: ```yaml MAX_ITERATIONS: 50 (hard cap); DEFAULT 20 when max_iterations unset in program.md; program.md may raise up to 50; values above 50 clamped to 50 with a warning MAX_CODEX_RUNS: 10 (cost ceiling for --codex Phase 2c — disable Codex once exceeded) STUCK_THRESHOLD: 5 consecutive discards → escalation GUARD_REWORK_MAX: 2 attempts before revert VERIFY_TIMEOUT_SEC: 120 (local), 300 (--colab) COLAB_KNOWN_HW: H100, L4, T4, A100 SUMMARY_INTERVAL: 10 iterations DIMINISHING_RETURNS_WINDOW: 5 iterations < 0.5% each → warn user and suggest stopping STATE_DIR: .experiments/state/<run-id>/ (timestamped dir per run — see .claude/rules/foundry-artifact-lifecycle.md) SENTINEL_SLUG_FORMULA: | eval "$(bash "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/git_slugs.sh")" # Sentinel path: ${TMPDIR:-/tmp}/claude-commit-auth-${REPO_SLUG}-${BRANCH_SLUG} # tmpdir-exempt: user-shell-boundary # Bash state is lost between tool calls — re-source git_slugs.sh at each use site; it is the only authorized slug form. ``` <!-- Note: STATE_DIR (.experiments/state/) holds per-iteration artifacts (diary, experiments.jsonl). Hypothesis pipeline outputs (hypotheses.jsonl, checkpoint.json, journal.md) go to .experiments/<run-id>/ (RUN_DIR). These are two separate directories by design — see protocol.md for layout. --> <!-- policy-sibling: run/modes/colab-setup.md, plan/SKILL.md, sweep/SKILL.md:4, judge/SKILL.md — COLAB_KNOWN_HW set restated in each; keep in sync (plugins/CLAUDE.md §Policy Duplication Marker). --> **Agent strategy mapping** (`agent_strategy` in config → ideation agent to spawn): | `agent_strategy` | Specialist agent | When to use | | -- | -- | -- | | `auto` | heuristic | Default — infer from metric_cmd keywords | | `perf` | `foundry:perf-optimizer` | latency, throughput, memory, GPU utilization | | `code` | `foundry:sw-engineer` | coverage, complexity, lines, coupling | | `ml` | `research:scientist` | accuracy, loss, F1, AUC, BLEU | | `arch` | `foundry:solution-architect` | coupling, cohesion, modularity metrics | **Auto-inference keyword heuristics** (when `agent_strategy: auto` or omitted; checked against `## Goal` text AND metric command): **Precedence order** (first match wins; ML keywords beat test-framework keywords). ML-specific compound terms (not bare tokens) required — prevents over-triggering on `eval`/`train`/`val` as common words: - contains `accuracy`, `loss` (paired with `train_loss`/`val_loss`/`eval_loss`), `f1_score`, `auc_roc`, `auroc`, `train_step`, `val_acc`, `eval_loss`, `epoch`, `gradient`, `tensor`, `overfit`, `generaliz`, `regulariz`, `validation`, `dropout`, `weight_decay`, `lr_schedule`, `cross_val`, `precision`, `recall`, OR explicit `--scientist` flag → `ml` → `research:scientist` - contains `time`, `latency`, `bench`, `throughput`, `memory` → `perf` → `foundry:perf-optimizer` - contains `pytest`, `coverage`, `complexity` → `code` → `foundry:sw-engineer` - no keyword match → `perf` (default fallback) — rationale: perf-optimizer profiles before changing and is the least assumption-laden generic improver; code/ml metrics are reliably keyword-detectable, so an unmatched goal is most often timing-shaped. **WARN**: print `⚠ No keyword match — defaulting to 'perf' strategy. If this is an ML task, set agent_strategy: ml in program.md.` Log resolved agent + reason in state.json `strategy_resolution`. Bare tokens `eval`, `train`, `val` (without compound suffix) do NOT trigger `ml` routing — too common in non-ML contexts (test eval scripts, training-environment configs, validator command names). **Stuck escalation sequence** (at STUCK_THRESHOLD consecutive discards): 1. Switch agent type. Rotation by current strategy: | Current strategy | Next strategy | Escalation agent | | -- | -- | -- | | `code` | `ml` | `research:scientist` | | `ml` | `perf` | `foundry:perf-optimizer` | | `perf` | `code` | `foundry:sw-engineer` | | `arch` | `code` | `foundry:sw-engineer` (fallback `general-purpose` if sw-engineer unavailable) | | `auto` | infer from resolved strategy | follow rotation row for whichever concrete strategy `auto` heuristics resolved to at Step R3 (e.g. `auto` → resolved `ml` → next `perf` → `foundry:perf-optimizer`) | 2. Spawn 2 agents parallel, competing strategies; each writes full analysis to `.experiments/state/<run-id>/stuck-escalation-<i>-<agent-type>.md`, returns ONLY compact JSON envelope. Use this spawn prompt verbatim (substitute `<run-id>`, `<i>`, and strategy): ```text Stuck-escalation handoff — iteration <i> after STUCK_THRESHOLD consecutive discards. Read `.experiments/state/<run-id>/state.json` for goal, best_metric, baseline, config. Read `.experiments/state/<run-id>/experiments.jsonl` for full iteration history. Read `.experiments/state/<run-id>/diary.md` for qualitative context (what was tried, why reverted). Read `.experiments/state/<run-id>/context-<i>.md` for current iteration's context block. Continue from the last completed iteration (do NOT restart from iteration 0). Write your full analysis and proposed change to `.experiments/state/<run-id>/stuck-escalation-<i>-<your-strategy>.md`. Write a resume point to `.experiments/state/<run-id>/resume.json`: {iteration: <i>, strategy: "<your-strategy>", proposed_change: "<one-line description>"}. Return ONLY: {"strategy":"<your-strategy>","description":"...","files_modified":[...],"confidence":0.N,"file":".experiments/state/<run-id>/stuck-escalation-<i>-<your-strategy>.md"} ``` Consolidation: pick whichever returns delta ≥ 0.1% AND guard pass; if both qualify, pick higher delta. 3. Stop, report progress, surface to user — no blind looping </constants> <compaction> - Key boundary: end of each Phase 8 in R5 iteration loop — JSONL record appended and `state.json` updated. Overwrite each iteration; contract always reflects latest in-progress state. Long metric-improvement loops are the primary auto-compact risk. - Preserve at each boundary: RUN_ID (TMPDIR key), STATE_DIR path, program.md path, current iteration#, best metric, best-commit SHA, experiments.jsonl path. - Clear at R1 start (stale prior run) and after R6/R7 campaign completion. </compaction> <workflow> <!-- Agent resolution: see _RESEARCH_SHARED/agent-resolution.md --> ## Agent Resolution **Agent resolution**: load and follow the protocol below. Contains: foundry check + fallback table. If foundry not installed: use table to substitute each `foundry:X` with `general-purpose`. Agents this skill uses: `foundry:sw-engineer`, `foundry:linting-expert`, `foundry:perf-optimizer`, `foundry:solution-architect`, `research:scientist`. ```bash # loads: compaction-contract.md export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" _RESEARCH_SHARED=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/resolve_shared.py" 2>/dev/null) # timeout: 5000 [ -z "$_RESEARCH_SHARED" ] && { echo "! Plugin path resolution failed — ensure research plugin installed and CLAUDE_PLUGIN_ROOT set, or invoke from project root."; exit 1; } echo "$_RESEARCH_SHARED" > "${TMPDIR:-/tmp}/research-shared-${CSID}" # cold resolve — every later site reads this sentinel instead of re-running python cat "$_RESEARCH_SHARED/agent-resolution.md" ``` **`CLAUDE_SKILL_DIR` resolution** — constants block provides default `plugins/cc_research/skills/run` (source-tree path). Resolve to installed path before use: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" CLAUDE_SKILL_DIR=$(ls -td ~/.claude/plugins/cache/borda-ai-rig/research/*/skills/run 2>/dev/null | head -1) [ -z "$CLAUDE_SKILL_DIR" ] && CLAUDE_SKILL_DIR="$(git rev-parse --show-toplevel 2>/dev/null)/plugins/cc_research/skills/run" echo "$CLAUDE_SKILL_DIR" > "${TMPDIR:-/tmp}/research-run-skill-dir-${CSID}" ``` ## Default Mode (Steps R1–R7) Triggered by `run <goal|file.md>`. **Task tracking**: create tasks R0–R7 at start. If no `--researcher`/`--architect`, mark R0 skipped. If `--codex` active, create task `R5b: Codex co-pilot (iter ?/max)` status `pending`. ### Step R0: Hypothesis pre-phase (`--researcher` / `--architect`) If no `--researcher`/`--architect`, skip to R1. **Flag combination note**: every oracle self-annotates feasibility (`feasible`/`blocker`/`codebase_mapping` are part of the oracle schema — no separate annotation spawn). `--researcher` alone, `--architect` alone, both together — all valid; both together adds architectural hypotheses alongside research ones. Follow `modes/hypothesis-pipeline.md`: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r CLAUDE_SKILL_DIR < "${TMPDIR:-/tmp}/research-run-skill-dir-${CSID}" 2>/dev/null || CLAUDE_SKILL_DIR="" cat "$CLAUDE_SKILL_DIR/modes/hypothesis-pipeline.md" # timeout: 5000 ``` **Per-iteration hypothesis selection** (when `--researcher`/`--architect` set, inside R5 loop): pop next from `RESEARCH_QUEUE`. Append to Phase 2 prompt: "Focus this iteration on testing this hypothesis: `<hypothesis text>`." **Per-iteration journal hook** (inside R5, after Phase 7): if `--journal` active, append entry to `<RUN_DIR>/journal.md` after EVERY iteration — regardless of outcome. Entry format: `protocol.md` (companion file, same skill dir). # loads: protocol.md Journals record kept and reverted iterations so ideation agent learns failed approaches. **Per-iteration checkpoint write** (after Phase 7): if `--researcher`/`--architect` active, append one line to `<RUN_DIR>/checkpoint.json` per schema in `protocol.md` (companion file, same skill dir): `{iteration, hypothesis_id, metric_before, metric_after, status: "passed"|"rolled_back"}`. ### Step R1: Load / build config **`--resume` flag detection**: if `--resume` in args, extract optional program.md path. Jump to `## Resume Mode`. Rest of R1 and R2–R7 skipped. **`--hypothesis <path>` parsing**: if `--hypothesis` in args, extract path token following it. Verify file exists: `[ -f "$HYPOTHESIS_PATH" ]`. If not found: print `! --hypothesis <path>: file not found` and stop. If found: set `hypothesis_override = true`. In R5 Phase 2 (Propose change), replace oracle-generated hypothesis with loaded file content — prepend to ideation agent prompt: "Use this pre-specified hypothesis as starting hypothesis for iteration N: <contents of HYPOTHESIS_PATH>. Validate, refine, implement it. Do not generate new hypothesis from scratch." **Auto-detect**: first non-flag arg ends in `.md` → parse as program file. Otherwise → text goal. **Clarification prompt** (`.md` file only): after extracting `.md` path, inspect next token (before `--` flags): - Absent or starts with `--` → `clarification_prompt = null` - Quoted string (starts/ends with `"`) → extract as `clarification_prompt`, strip quotes - Bare unquoted token (no `--`, no `"`) → accept as `clarification_prompt`; print: `ℹ clarification set to "<token>" (tip: quote multi-word hints — e.g. "/research:run program.md \"focus on sort\" --codex")` After clarification extraction, remaining non-flag tokens (not starting `--`) are unrecognized. For each, print: ```markdown ⚠ Unrecognized argument "<token>" — ignored. Known positional args: <program.md path> [clarification] Known flags: --resume <program.md>, --team, --compute=local|colab|docker, --colab[=HW], --codex, --researcher, --architect, --journal, --hypothesis <path>, --scientist, --codemap, --no-codemap, --keep "<items>" If you meant to override the algo, edit the ## Config block in your program.md (algo: sort) and update ## Metric to match. If you meant to set a clarification hint, pass it as a quoted string: "/research:run program.md \"sort improvements\" --codex" ``` ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" # runs under bash — zsh never populates ${BASH_REMATCH[1]}, so --keep "..." was silently resolving empty python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/extract-keep-flag.py" research-run "$ARGUMENTS" # timeout: 5000 — parses --keep, clears a stale contract, persists for Phase 8 ``` **Unsupported flag check**: load and follow the protocol below. Supported flags for this skill: `--resume`, `--team`, `--compute`, `--colab`, `--codex`, `--researcher`, `--architect`, `--journal`, `--hypothesis`, `--scientist`, `--codemap`, `--no-codemap`, `--keep`. ```bash # loads: unsupported-flag-protocol.md export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r _RESEARCH_SHARED < "${TMPDIR:-/tmp}/research-shared-${CSID}" 2>/dev/null || _RESEARCH_SHARED="" # warm read (Check 41) cat "$_RESEARCH_SHARED/unsupported-flag-protocol.md" ``` **Codemap auto-detection** — structural blast-radius context for modules the experiment edits; on by default when codemap installed + index found. `--no-codemap` opts out; `--codemap` is strict (fail if unavailable). ```bash # timeout: 5000 export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" # writes true/false to research-run-codemap-enabled-${CSID}; strict mode exits 1 (already printed ! BLOCKED) if unavailable CODEMAP_RAW=$(python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/codemap-flag.py" research-run "$ARGUMENTS") || exit 1 ``` > loads: codemap-gates.md When `CODEMAP_RAW` ≠ `off`: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r _RESEARCH_SHARED < "${TMPDIR:-/tmp}/research-shared-${CSID}" 2>/dev/null || _RESEARCH_SHARED="" # warm read (Check 41) cat "$_RESEARCH_SHARED/codemap-gates.md" ``` Follow Gate A and Gate B. **If argument is a `.md` file** — read and parse with these rules: 1. Find each `## <Section>` heading (case-insensitive). 2. Extract first fenced code block following that heading. 3. Parse block as `key: value` lines; multi-value = indented ` - value` items. Paths with spaces: wrap in double quotes. 4. Missing required fields (`command` under `## Metric`/`## Guard`) → stop with error. 5. `agent_strategy: auto` (or omitted) → apply keyword heuristics from `<constants>` to `## Goal` text and metric command. 6. `target` under `## Metric`: `direction: higher` → stop when metric ≥ target; `direction: lower` → stop when metric ≤ target. If `target` omitted, run until `max_iterations`. 7. Unrecognized keys/headings → warn once, ignore. 8. `## Notes` and `# Program:` title never parsed — human-only. (`# Campaign:` accepted as alias.) **If argument is text** — auto-detect `metric_cmd`/`guard_cmd` from goal string and codebase scan (same as P-P1, non-interactive). `config.json` not read. **`--colab[=HW]` parsing**: `--colab` (no `=`) → `compute = "colab"`, `colab_hw = null`. `--colab=<value>` → `compute = "colab"`, `colab_hw = <value>` (uppercased). Unknown `<value>` (not in `{H100, L4, T4, A100}`) → print `"⚠ Unknown Colab hardware '<value>' — proceeding with default GPU. Known: H100, L4, T4, A100"`, set `colab_hw = null`. `--compute=colab` (no HW) → `compute = "colab"`, `colab_hw = null`. `colab_hw` in `## Config` sets hardware preference (`H100`, `L4`, `T4`, `A100`); CLI `--colab=HW` overrides. Generate `run-id` = `$(date -u +%Y-%m-%dT%H-%M-%SZ)`. Assign immediately: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" RUN_ID=$(date -u +%Y-%m-%dT%H-%M-%SZ) RUN_DIR=".experiments/${RUN_ID}" # hypothesis pipeline + journal outputs (per <constants> note) STATE_DIR=".experiments/state/${RUN_ID}" # per-iteration artifacts (state.json, experiments.jsonl, diary.md) mkdir -p "$RUN_DIR" "$STATE_DIR" # timeout: 5000 — both dirs created before any Write to either echo "$RUN_ID" > "${TMPDIR:-/tmp}/research-run-id-${CSID}" # persist for Phase 8 contract write (Check 41: fresh shell) ``` Note: `STATE_DIR` (`.experiments/state/${RUN_ID}/`) is per-iteration artifact dir — distinct from `RUN_DIR`. Both coexist; see `<constants>` block. Create run directory: ```text .experiments/state/<run-id>/ state.json ← iteration count, best metric, status experiments.jsonl ← one line per iteration diary.md ← human-readable research diary (hypothesis → outcome → decision) ``` Convert `program_file` to absolute path: `realpath "$PROGRAM_FILE"` — Resume Mode matches on absolute path. Write initial `state.json` (`program_file` = absolute path to `.md` or `null` for text goal): ```json { "run_id": "<run-id>", "goal": "<goal>", "config": {}, "program_file": "<absolute path to program.md, or null>", "iteration": 0, "best_metric": null, "best_commit": null, "status": "initializing", "started_at": "<ISO timestamp>", "clarification_prompt": null, "colab_hw": null, "sandbox_mode": "local" } ``` Note: status is `"initializing"` until all R2 precondition checks pass — resume treats `"initializing"` as failed-init, not active run. Update to `"running"` at end of R2 (after all checks pass). ### Step R2: Precondition checks Run all checks before touching code. Fail fast with clear message: 01. **Clean git**: `git status --porcelain` → must be empty. If dirty: print dirty files and stop. 02. **Not detached HEAD**: `git rev-parse --abbrev-ref HEAD` → must not be `HEAD`. 03. **Metric command numeric**: run `metric_cmd` once; parse stdout for float. If no float: show output and stop. 04. **Guard passes**: run `guard_cmd` once; must exit 0. If fails: show output and stop. 05. **`--colab` check**: verify `mcp__colab-mcp__runtime_execute_code` available. If not, print setup instructions (see Colab MCP section) and stop. If `--colab=HW` (`colab_hw` non-null): print: ` Hardware requested: --colab=<colab_hw>. Ensure your Colab notebook running with <colab_hw> GPU.` 06. **`--codex` check**: distinguish the installed-and-enabled bridge target from absence. `claude` not on `PATH` → print `⚠ 'claude' CLI not in PATH — bridge availability cannot be verified.` and **stop**. If `claude plugin list` lacks `bridge@borda-ai-rig`, print `⚠ bridge@borda-ai-rig not installed. Install it from the Borda AI Rig marketplace.` and **stop**. If it is disabled, print `⚠ bridge@borda-ai-rig is disabled. Enable it and reload plugins.` and **stop**. 07. **`compute: docker` check**: run `docker ps` via Bash (`timeout: 5000`). If non-zero: print `⚠ Docker daemon not running. Start Docker Desktop and retry.` and **stop**. 08. **Flag conflict**: if `--colab` and `--compute=docker` both active: print `⚠ --colab and --compute=docker are mutually exclusive. Use one or the other.` and **stop**. 09. **`--colab` + `--codex` compatibility note** (non-blocking): if both flags active, print `ℹ --colab + --codex active: Codex Phase 2c will receive colab_hw context so generated code can target the right GPU (H100/T4 bf16 vs fp16). Phase 5 metric verification runs through Colab MCP as usual.` and continue. Pass `colab_hw` to Codex spawn prompt (Phase 2c — see `modes/codex-copilot.md`). 10. **`--journal` prerequisite**: verify `--researcher`/`--architect` also set. If neither: print `⚠ --journal requires --researcher or --architect — omit --journal or add a hypothesis pipeline flag.` and **stop**. **`--codex-delegation` warning** (non-blocking): `codex-delegation.md` ships inside this plugin's own `skills/_shared/`, so R7 needs no other plugin installed. Verify it resolves: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r _RESEARCH_SHARED < "${TMPDIR:-/tmp}/research-shared-${CSID}" 2>/dev/null || _RESEARCH_SHARED="" # warm read (Check 41) [ -f "$_RESEARCH_SHARED/codex-delegation.md" ] || echo "⚠ codex-delegation.md not found under $_RESEARCH_SHARED — R7 Codex delegation will be skipped; reinstall the research plugin." ``` Set `CODEX_DELEGATION_AVAILABLE=true` if found, `false` otherwise. Continue regardless. **Initialize sandbox + timeout variables** (after all checks pass — constants YAML block not auto-exported to bash; assign explicitly with `${VAR:-default}` to honour environment overrides; ADV-L15 / ADV-M20): ```bash SANDBOX_NETWORK="${SANDBOX_NETWORK:-none}" # override via program.md Config or environment variable # Verify timeout — 120s local, 300s Colab per <constants>; bash overrides via VERIFY_TIMEOUT_SEC env var if [ "${compute:-local}" = "colab" ]; then VERIFY_TIMEOUT_SEC="${VERIFY_TIMEOUT_SEC:-300}" else VERIFY_TIMEOUT_SEC="${VERIFY_TIMEOUT_SEC:-120}" fi VERIFY_TIMEOUT_MS=$((VERIFY_TIMEOUT_SEC * 1000)) # Ideation Agent() calls run in background — spawn, end turn, no filler call; on each notification check its output file, mark timed_out (⏱) if empty. ``` **Initialize `sandbox_mode`**: - `compute: docker` (daemon check passed in step 7) → `sandbox_mode = "docker"`. Print: `sandbox: Docker daemon reachable — sandbox mode active` - All other cases (`compute: local`, `compute: colab`) → `sandbox_mode = "local"` **Update state.json status to `"running"`** — write only after ALL checks above pass. Resume treats `"initializing"` as failed-init and skips such runs. ### Step R3: Select ideation agent Apply `agent_strategy` mapping from `<constants>`. If `auto`, apply keyword heuristics to `metric_cmd`. Log selected agent to `state.json`. ### Step R4: Establish baseline (iteration 0) Run `metric_cmd` and `guard_cmd`. Parse metric value. Append to `experiments.jsonl`: ```json { "iteration": 0, "commit": "<HEAD sha>", "metric": 0.0, "delta": 0.0, "guard": "pass", "status": "baseline", "description": "baseline", "agent": null, "confidence": null, "timestamp": "<ISO>", "files": [] } ``` Update `state.json`: `best_metric = <baseline>`, `best_commit = <HEAD sha>`. Print: `Baseline: <metric_cmd key> = <value>`. Write initial diary header to `.experiments/state/<run-id>/diary.md`: ```markdown # Research Diary — <goal> **Run**: <run-id> **Started**: <ISO timestamp> **Baseline**: <metric_key> = <baseline value> --- ``` Then proceed to R5. ### Step R5: Iteration loop ```bash # REPO_SLUG / BRANCH_SLUG: source the single authorized slug form (see <constants>) eval "$(bash "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/git_slugs.sh")" # timeout: 3000 COMMIT_SENTINEL="${TMPDIR:-/tmp}/claude-commit-auth-${REPO_SLUG}-${BRANCH_SLUG}" # tmpdir-exempt: user-shell-boundary touch "$COMMIT_SENTINEL" # timeout: 3000 # trap doesn't survive across Bash calls — commit-guard.js hook (foundry-owned) handles protection instead ``` > **Dependency — `commit-guard.js` (requires `foundry` plugin)**: the commit-sentinel dance above (touch at R5, re-touch each phase, `rm` at cleanup) is enforced by foundry's `commit-guard.js` `PreToolUse` hook. That hook ships with the `foundry` plugin only — research does not bundle it. **Standalone install (foundry absent): the sentinel touches become inert and `git commit` proceeds unguarded.** The sentinel logic is still safe to run (touch/`rm` on a temp file are harmless no-ops without the hook); it simply provides no protection. If you rely on atomic-commit guarding during `research:run`, install `foundry`. **Sentinel liveness**: touch `$COMMIT_SENTINEL` after each Phase 8 result write to extend monitoring window — do NOT rely solely on sentinel touched at loop start; slow iterations exceed 15-min TTL. Re-derive slug per SENTINEL_SLUG_FORMULA from `<constants>` (bash state lost between calls). **`--team` mode**: If `--team` active, follow `modes/team.md` and execute Phases A–D in place of standard iteration loop below. ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r CLAUDE_SKILL_DIR < "${TMPDIR:-/tmp}/research-run-skill-dir-${CSID}" 2>/dev/null || CLAUDE_SKILL_DIR="" cat "$CLAUDE_SKILL_DIR/modes/team.md" # timeout: 5000 ``` **`--team` + `--hypothesis` combination**: combinable. Team mode uses provided hypothesis path and skips oracle/hypothesis-generation phase — `hypothesis_override = true` applies inside team.md Phase A same as solo mode. For each iteration `i` from 1 to `max_iterations`: **Phase overview** (all phases run per iteration): | Phase | Name | Trigger / description | | -- | -- | -- | | 0 | Print header | Always — print `[→ Iter N/max · starting]`; TaskUpdate R5 subject with current iteration | | 1 | Build context | Always — build compact context: git log, JSONL history, recent diff | | 2 | Propose change | Always — spawn specialist agent: read code, research, investigate, generate hypothesis with optional sandbox scripts | | 2a | Sandbox validate | `compute: docker` only — run agent's exploratory scripts in Docker sandbox (read-only mount) | | 2b | Apply change | `compute: docker` only — agent applies validated proposal to real codebase via Write/Edit tools only; no Bash on codebase | | 2c | Codex co-pilot | `--codex` only — required each iteration up to `MAX_CODEX_RUNS`; after cap reached, continue without Codex | | 3 | Verify files | Always — check `git diff --stat`; skip to Phase 8 if no files changed (no-op) | | 4 | Commit change | Always — stage modified files and commit before verifying metric | | 5 | Verify metric | Always — run `metric_cmd` via `compute` mode (local/colab/docker); revert on timeout | | 6 | Run guard | Always — run `guard_cmd` via `compute` mode; record pass or fail | | 7 | Evaluate outcome | Always — keep, rework, or revert based on metric + guard result | | 7a | Write diary | Always — append one structured entry to `diary.md` recording hypothesis, outcome, decision rationale | | 8 | Write log | Always — append JSONL record, update `state.json`, print iteration summary, TaskUpdate R5 with result | | 9 | Progress checks | Always — summary every SUMMARY_INTERVAL, stuck detection, diminishing-returns warn, early-stop check | **Command execution rules** (apply to ALL phases running external commands): 1. **Use Bash tool `timeout` parameter**: Never shell `timeout` wrapper. Pass `timeout: <ms>` on Bash tool call itself. (Compound commands are already barred globally — see `claude-config.md` §Directory Navigation Commands.) 2. **No inline multi-line Python**: Python logic >3 lines → write to `.experiments/state/<run-id>/scripts/script-<i>.py` via Write tool, execute with `python <path>` or `uv run python <path>`. Two triggers Claude Code always flags: (a) `=([0-9.]+)` inside `-c "..."` (false Zsh substitution); (b) multi-line `-c "..."` with `#`-prefixed comment lines. Writing to file sidesteps both. 3. **No Zsh constructs**: Never use `=()`, `<()`, `>()` in Bash commands — even inside quoted strings; Claude Code scans raw command text. 4. **Local exploratory scripts writing to real files** (scanning config combos, patching JSON, temp overrides): write to `.experiments/state/<run-id>/scripts/`, run locally with `python <path>`. Legitimately modify project files — NOT in Docker sandbox. 5. **Docker sandbox** (when available — see Phase 2a): Phases 4–6 route `metric_cmd`/`guard_cmd` through Docker when `compute: docker`. Phase 2a: read-only hypothesis scripts in sandbox. Scripts writing to project files always run locally. 6. **One change per iteration**: Never batch-loop over config variants/combos in single Bash/Python call. Each variant = one campaign iteration — loop/measure/compare is campaign framework's job, not ideation agent's. #### Phase 0 — Print header Print iteration header, update R5 task: ```text [→ Iter N/max_iterations — best so far: <best_metric> (Δ<best_delta_pct>% vs baseline)] ``` TaskUpdate R5 subject: `R5: Iteration N/max_iterations — running` #### Phase 1 — Build context Build context for ideation agent, write to file — do NOT accumulate inline in main context: ```bash git log --oneline -10 >.experiments/state/${RUN_ID}/context-${I}.md # timeout: 3000 tail -10 .experiments/state/${RUN_ID}/experiments.jsonl >>.experiments/state/${RUN_ID}/context-${I}.md # timeout: 5000 # Fresh repos have <5 commits — fall back to full HEAD diff when shallow if [ "$(git rev-list HEAD --count 2>/dev/null)" -gt 5 ]; then git diff --stat HEAD~5 HEAD >>.experiments/state/${RUN_ID}/context-${I}.md # timeout: 3000 else git diff --stat HEAD >>.experiments/state/${RUN_ID}/context-${I}.md # timeout: 3000 fi ``` **Codemap structural context** (only if `CODEMAP_ENABLED=true` — re-read from `${TMPDIR:-/tmp}/research-run-codemap-enabled-${CSID}`). Cat once, first iteration only — the file is static and stays in context; re-cat only if it is no longer in context (e.g. after a compaction). Re-catting every iteration re-bills ~800 tok × N iterations for identical text: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r _RESEARCH_SHARED < "${TMPDIR:-/tmp}/research-shared-${CSID}" 2>/dev/null || _RESEARCH_SHARED="" # warm read (Check 41) cat "$_RESEARCH_SHARED/codemap-context.md" ``` Execute its block. Leave `TARGET_MODULE`/`TARGET_FN` empty for the global `central` blast-radius baseline, or set `TARGET_MODULE` to the module the experiment edits (from `## Config`) for importer/coverage queries. Append output to `context-${I}.md` under a `## Structural Context (codemap-py)` heading so the Phase 2 ideation agent sees blast-radius before proposing edits. Codemap output non-empty: also append the reuse gate from `codemap-context.md` above verbatim and this **codemap-first protocol** directly below it in `context-${I}.md` (own copy — self-contained, no cross-plugin reference), so the Phase 2 spawn prompt's "read `context-<i>.md`" instruction carries it to the ideation agent: (1) **Skill-first** — use the Structural Context above before any Grep/Glob/Read aimed at imports, callers, or test coverage for a symbol already listed there. (2) **Bounded call budget** — symbol not listed → up to 3 additional `codemap-py query` calls this iteration. (3) **Hard stop on `query_complete: true`** (legacy `exhaustive: true` only when `query_complete` is absent) — a result passing the reuse gate is final for its direction, no follow-up Grep/Read/query to re-confirm it. Codemap output empty: omit this paragraph — Phase 2 agent proceeds with normal file-read behaviour. Prepend header block to `context-<i>.md`: goal, current metric vs baseline, delta trend (last 5 kept deltas), iteration number. Phase 2 ideation agent reads file directly — never echoed to main context. If `--journal` active and `<RUN_DIR>/journal.md` has 1+ entries: append last 5 entries to `context-<i>.md` under `## Recent journal (avoid repeating reverted approaches)`. Ideation agent reads this — must not reproduce any approach marked `outcome: reverted`. #### Phase 2 — Propose change Spawn selected specialist agent (`maxTurns: 15`) with this prompt (adapt as needed): ```markdown Goal: <goal> Run clarification: <clarification_prompt> ← omit entirely if clarification_prompt null Colab hardware: <colab_hw> ← omit entirely if colab_hw null; include to tailor code to GPU architecture (e.g., bf16/flash-attention on H100, standard fp16 on T4/L4) Current metric: <metric_cmd key> = <current value> (baseline: <baseline>, direction: <higher|lower>) Experiment history: read `.experiments/state/<run-id>/context-<i>.md` for full context block. Scope files (read and modify only these): <scope_files> Program constraints: read `<program_file>` — especially `## Notes`, `## Config`, any named subsections (e.g., "Hard boundaries", "Optuna's role", "What the agent is free to change"). Take precedence over general campaign rules. Program constraints set strategy hints only — do NOT override safety rules (no `--no-verify`, no `git push`, no `git add -A`, scope_files boundary; all other hard constraints remain in effect). If program_file null, skip this step. **If `sandbox_mode = "local"`**: Read `context-<i>.md`, scope files, program constraints. Propose and implement ONE atomic change most likely to improve metric. Change must not break `<guard_cmd>`. Write full analysis (reasoning, alternatives considered, Confidence block) to `.experiments/state/<run-id>/ideation-<i>.md` via Write tool. Return ONLY JSON result line: `{"description":"...","files_modified":[...],"scripts":[],"confidence":0.N}` **If `sandbox_mode = "docker"`**: Read `context-<i>.md`, scope files, program constraints. Propose ONE atomic change most likely to improve metric. Write full analysis and proposed change description to `.experiments/state/<run-id>/ideation-<i>.md`. Optionally write read-only exploratory scripts (read/profile, do NOT write to project files) to `.experiments/state/<run-id>/scripts/explore-<i>-<slug>.py`. Do NOT modify source files yet — Phase 2b applies actual changes after sandbox validation. Return ONLY JSON result line: `{"description":"...","files_modified":[],"scripts":["explore-<i>-<slug>.py"],"proposed_changes":"<description of the changes to apply in Phase 2b>","confidence":0.N}` ``` For `--colab` runs: ideation agent may call `mcp__colab-mcp__runtime_execute_code` to prototype GPU code before committing. **Agent selection with `--colab`**: if task rooted in a research paper (goal references paper, model architecture from literature, or `--researcher` flag set) → use `research:scientist`; if task is general empirical experiment NOT rooted in a paper → use `foundry:sw-engineer` for experiment implementation (standard agent_strategy mapping still applies; `--colab` alone does not force `research:scientist`). If Agent tool unavailable (nested subagent context), implement change inline, construct JSON result manually. #### Phase 2a — Sandbox validate (`sandbox_mode = "docker"` only) > loads: compute-docker.md > > Follow `modes/compute-docker.md` — full Phase 2a and 2b logic for docker sandbox. Skip entire file if `sandbox_mode = "local"`. Cat once, first iteration only — static content stays in context; re-cat only if no longer in context (e.g. after a compaction). ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r CLAUDE_SKILL_DIR < "${TMPDIR:-/tmp}/research-run-skill-dir-${CSID}" 2>/dev/null || CLAUDE_SKILL_DIR="" cat "$CLAUDE_SKILL_DIR/modes/compute-docker.md" # timeout: 5000 ``` #### Phase 2b — Apply change (`sandbox_mode = "docker"` only) Skip if `sandbox_mode = "local"` — handled in compute-docker.md above. #### Phase 2c — Codex co-pilot (`--codex` only) Follow `modes/codex-copilot.md` — contains full Phase 2c logic, cost-bounded gate, Codex dispatch prompt, outcome handling, and stuck escalation. Cat once, first `--codex` iteration only — static content stays in context; re-cat only if no longer in context (e.g. after a compaction). ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r CLAUDE_SKILL_DIR < "${TMPDIR:-/tmp}/research-run-skill-dir-${CSID}" 2>/dev/null || CLAUDE_SKILL_DIR="" cat "$CLAUDE_SKILL_DIR/modes/codex-copilot.md" # timeout: 5000 ``` #### Phase 3 — Verify files changed `git diff --stat`. If no files changed (no-op): append to JSONL with `status: no-op`, skip to Phase 8 (log), continue loop. #### Phase 4 — Commit change Refresh commit sentinel before staging — R5 loop can exceed the 15-min sentinel TTL set in R5 setup. Slug computation unavoidably re-run (bash state lost between tool calls); path pattern identical to R5 setup block above: ```bash # refresh sentinel — bash state lost between calls, re-source slug (R5 form) eval "$(bash "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/git_slugs.sh")" # timeout: 3000 touch "${TMPDIR:-/tmp}/claude-commit-auth-${REPO_SLUG}-${BRANCH_SLUG}" # timeout: 3000 # tmpdir-exempt: user-shell-boundary ``` Stage only modified files (never `git add -A`): ```bash git add <files_modified from agent JSON> # timeout: 3000 git commit -m "experiment(optimize/i<N>): <description>" # timeout: 90000 ``` If pre-commit hooks fail: - Delegate to `foundry:linting-expert`: provide failing hook output and modified files; ask to fix. Max 2 attempts. - If still failing after 2 attempts: `git restore --staged <files_modified>` + `git restore <files_modified>` to clean up (`# <files_modified>` = list of files returned by the iteration agent; restricts discard to iteration scope only), append `status: hook-blocked`, continue loop. #### Phase 5 — Verify metric > loads: phase5-metric.md # also loads: codex-copilot.md, colab-setup.md, compute-docker.md, hypothesis-pipeline.md, report.md, resume.md, team.md > > Follow `modes/phase5-metric.md` — metric verification logic for docker, local, and colab sandbox modes. Cat once, first iteration only — static content stays in context; re-cat only if no longer in context (e.g. after a compaction). ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r CLAUDE_SKILL_DIR < "${TMPDIR:-/tmp}/research-run-skill-dir-${CSID}" 2>/dev/null || CLAUDE_SKILL_DIR="" cat "$CLAUDE_SKILL_DIR/modes/phase5-metric.md" # timeout: 5000 ``` #### Phase 6 — Run guard **If `sandbox_mode = "docker"`**: run `guard_cmd` in same Docker container as Phase 5 (same flags; no resource limits). Check exit code only. **If `sandbox_mode = "local"`**: run `guard_cmd` directly. Record pass (exit 0) or fail (non-zero). #### Phase 7 — Evaluate outcome Top-to-bottom; **first match wins**. | Condition | Action | | -- | -- | | metric improved AND guard fail | Rework: re-spawn agent with guard failure output. Max `GUARD_REWORK_MAX` (2) attempts. If still failing after all rework attempts: revert (`git revert HEAD --no-edit`); diary status = `"reverted"`, decision = `"Guard failed after GUARD_REWORK_MAX rework attempts — reverted"`. | | metric improved AND gain < 0.1% AND change > 50 lines | Refresh sentinel; discard: `git revert HEAD --no-edit`. (Line count computed via `CHANGE_LINES` — see note below table.) | | metric improved AND guard pass | Keep commit. Update `state.json`: `best_metric`, `best_commit`. "Improved" = `new_metric > best_metric` when `direction: higher`; `new_metric < best_metric` when `direction: lower`. | | no improvement | Refresh sentinel; revert: `git revert HEAD --no-edit`. | **Line count computation** (for "gain < 0.1% AND change > 50 lines" row): run before evaluating the condition: ```bash DIFF_SUMMARY=$(git diff --stat HEAD~1..HEAD | tail -1) # timeout: 3000 INSERTIONS=$(echo "$DIFF_SUMMARY" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo 0) DELETIONS=$(echo "$DIFF_SUMMARY" | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo 0) CHANGE_LINES=$(( INSERTIONS + DELETIONS )) ``` `git revert HEAD --no-edit` — never `git reset --hard` (preserves history, not in deny list). **Double-revert guard** (ADV-H19) — Phase 7 rework→revert can collide with a partial Phase 5 timeout revert performed in the same iteration. Always check before issuing the revert. <!-- policy-sibling: plugins/cc_research/skills/run/modes/phase5-metric.md --> ```bash # revert subject embeds the original ("Revert \"experiment(...)\"") — anchor at subject start, never substring [ -n "$I" ] || { echo "! BLOCKED — Phase 7: iteration number unset, cannot scope revert guard"; exit 1; } if git log -1 --format=%s 2>/dev/null | grep -qE "^experiment\(optimize/i${I}\):"; then git revert HEAD --no-edit # timeout: 15000 else echo "Phase 7: HEAD is not iteration ${I}'s experiment commit — Phase 5 already reverted; skipping double-revert." fi ``` The guard fires on `metric improved AND guard fail` (after `GUARD_REWORK_MAX` attempts exhausted), `no improvement`, and `gain < 0.1% AND change > 50 lines` paths — any path that issues a revert after Phase 5 may have already reverted. (Known gap, not solved here: whether a rework attempt re-commits or amends is undefined elsewhere in this file — if rework re-commits, a single revert on exhaustion only reverts the last of up to `GUARD_REWORK_MAX` + 1 commits.) #### Phase 7a — Write diary After Phase 7 decision, append one entry to `diary.md`: ```markdown ## Iteration N — <ISO timestamp> **Hypothesis**: <agent's description from Phase 2 JSON — the proposed change and expected improvement> **Outcome**: <metric_key> = <value> (Δ<delta>% vs baseline) — <kept|reverted|rework|no-op|hook-blocked|timeout> **Decision**: <one sentence: why the outcome was accepted or rejected — e.g. "Metric improved 1.2% with guard passing" or "Reverted: metric regressed by 0.5%" or "Guard failed after 2 rework attempts"> --- ``` For `no-op` iterations (no file changes): ```markdown ## Iteration N — <ISO timestamp> **Hypothesis**: <description> — no files modified **Outcome**: no-op **Decision**: Skipped (no changes made) --- ``` #### Phase 8 — Write log Append one JSONL record to `experiments.jsonl` (same schema as baseline record in Step R4, plus `ideation_source`): ```json { "iteration": 1, "commit": "<sha of experiment commit or revert>", "metric": 0.0, "delta": 0.0, "guard": "pass|fail", "status": "kept|reverted|rework|no-op|hook-blocked|timeout", "description": "<agent description>", "agent": "<agent type>", "confidence": 0.0, "timestamp": "<ISO>", "files": [], "ideation_source": "claude" } ``` `ideation_source`: `"claude"` = Claude specialist proposed; `"codex"` = Phase 2c proposed. Update `state.json`: `iteration = i`, `status = running`. Print iteration summary: ```text [✓ Iter N/max — <kept|reverted|no-op|...> · metric=<value> (Δ<delta>%) · agent=<agent_type>] ``` TaskUpdate R5 subject: `R5: Iter N/max — last: <status>, best: <best_metric>` ```bash # compaction contract — overwritten each iteration, always latest state (compaction-contract.md §Lifecycle) export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r _RUN_ID < "${TMPDIR:-/tmp}/research-run-id-${CSID}" 2>/dev/null || _RUN_ID="" IFS= read -r _KEEP < "${TMPDIR:-/tmp}/research-run-keep-items-${CSID}" 2>/dev/null || _KEEP="" _STATE_JSON=".experiments/state/${_RUN_ID}/state.json" _ITER=$(jq -r '.iteration // 0' "$_STATE_JSON" 2>/dev/null || echo "?") _BEST=$(jq -r '.best_metric // "?"' "$_STATE_JSON" 2>/dev/null || echo "?") _PROG=$(jq -r '.program_file // ""' "$_STATE_JSON" 2>/dev/null || echo "") _KEEP_APPEND=""; [ -n "$_KEEP" ] && _KEEP_APPEND="; user-keep: $_KEEP" python "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/write_skill_contract.py" "research:run" "iteration-loop (after iter ${_ITER})" ".experiments/${_RUN_ID}" "state-json=${_STATE_JSON}, program=${_PROG}, iter=${_ITER}, best-metric=${_BEST}, cat-once-files=_shared/codemap-context.md + modes/compute-docker.md + modes/codex-copilot.md + modes/phase5-metric.md (re-cat after compaction only)${_KEEP_APPEND}" "continue R5 from iter $(( _ITER + 1 )) or proceed to R6 when loop done" # timeout: 5000 ``` #### Phase 9 — Progress checks - **Summary every SUMMARY_INTERVAL iterations**: print compact table (iteration, metric, delta, status) for last N iterations. - **Stuck detection**: if last `STUCK_THRESHOLD` entries all have `status: reverted|no-op|hook-blocked`, trigger escalation (see `<constants>`). Log escalation action. - **Diminishing returns**: if last `DIMINISHING_RETURNS_WINDOW` kept entries each improved < 0.5%, warn and suggest stopping. No auto-stop — user decides. - **Early stop**: if `target` set, stop when metric crosses it. Mark `state.json` `status: goal-achieved`. - **Context compaction** (every SUMMARY_INTERVAL): write full iteration summary to `.experiments/state/<run-id>/progress-<i>.md`, discard verbose per-iteration details from working memory. Retain only: current metric, iteration count, JSONL path, `best_commit`. Full history recoverable from `experiments.jsonl` and `ideation-<i>.md`. **After campaign loop completes** (outside per-iteration loop): ```bash # fresh shell — $COMMIT_SENTINEL gone, re-derive path before rm or cleanup is a silent no-op on "" eval "$(bash "${CLAUDE_PLUGIN_ROOT:-plugins/cc_research}/bin/git_slugs.sh")" # timeout: 3000 rm -f "${TMPDIR:-/tmp}/claude-commit-auth-${REPO_SLUG}-${BRANCH_SLUG}" # timeout: 3000 (best-effort; commit-guard.js owns lifecycle) # tmpdir-exempt: user-shell-boundary ``` ### Step R6: Results report Pre-compute branch before writing: `BRANCH=$(git branch --show-current 2>/dev/null | tr '/' '-' || echo 'main')` — deliberate second slug form, report paths only; commit sentinels use `git_slugs.sh`/`BRANCH_SLUG` (SENTINEL_SLUG_FORMULA). Not a bypass — retracted audit finding. ```bash mkdir -p .reports/research # timeout: 3000 ``` Write full report to `.reports/research/run-$BRANCH-$(date +%Y-%m-%d).md` via Write tool. Do not print to terminal. Anti-overwrite: if file exists, append counter suffix (e.g. `-2.md`): `OUT=".reports/research/run-$BRANCH-$(date +%Y-%m-%d).md"; BASE="$OUT"; COUNT=2; while [ -f "$OUT" ]; do OUT="${BASE%.md}-${COUNT}.md"; COUNT=$((COUNT+1)); done` Follow `modes/report.md`: ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r CLAUDE_SKILL_DIR < "${TMPDIR:-/tmp}/research-run-skill-dir-${CSID}" 2>/dev/null || CLAUDE_SKILL_DIR="" cat "$CLAUDE_SKILL_DIR/modes/report.md" # timeout: 5000 ``` `state.json`: `status = completed`. ### Step R7: Codex delegation (optional) Skip R7 if `CODEX_DELEGATION_AVAILABLE=false` (warning already printed at R2 — no further action needed). Inspect applied changes (`git diff <baseline_commit>...<best_commit> --stat`), identify tasks Codex can complete (comments on non-obvious changes, docstrings for modified functions, test coverage). ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r _RESEARCH_SHARED < "${TMPDIR:-/tmp}/research-shared-${CSID}" 2>/dev/null || _RESEARCH_SHARED="" # warm read (Check 41) — bash state lost between Bash() calls cat "$_RESEARCH_SHARED/codex-delegation.md" # timeout: 5000 ``` Apply criteria loaded above. Print next-step suggestions as plain text — do NOT call `AskUserQuestion`: both `/research:retro` and `/research:verify` ship `disable-model-invocation: true` and `run`'s `allowed-tools` has no `Skill` entry, so neither is dispatchable this turn. ```text Next: /research:retro <run-id> — post-run retrospective analysis Next: /research:verify <paper> — verify implementation matches paper claims ``` ```bash rm -f .temp/state/skill-contract.md # clear contract — campaign complete (compaction-contract.md §Lifecycle) # timeout: 5000 ``` ## Resume Mode > loads: resume.md > > Follow and execute `modes/resume.md`. ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r CLAUDE_SKILL_DIR < "${TMPDIR:-/tmp}/research-run-skill-dir-${CSID}" 2>/dev/null || CLAUDE_SKILL_DIR="" cat "$CLAUDE_SKILL_DIR/modes/resume.md" # timeout: 5000 ``` ## Mode: colab > loads: colab-setup.md > > Execute only when `--colab` flag active. Follow and execute `modes/colab-setup.md`. ```bash export CSID="${CLAUDE_CODE_SESSION_ID:-$PPID}" IFS= read -r CLAUDE_SKILL_DIR < "${TMPDIR:-/tmp}/research-run-skill-dir-${CSID}" 2>/dev/null || CLAUDE_SKILL_DIR="plugins/cc_research/skills/run" cat "$CLAUDE_SKILL_DIR/modes/colab-setup.md" # timeout: 5000 ``` </workflow> <notes> - **Commit before verify** — enables clean `git revert HEAD` if metric doesn't improve. Never verify before committing. - **`git revert` over `git reset --hard`** — preserves experiment history, not in deny list. - **Never `git add -A`** — always stage specific files returned by agent JSON. - **Never `--no-verify`** — if pre-commit hook blocks, delegate to `foundry:linting-expert` and fix. - **Guard ≠ Verify** — guard checks regressions (tests, lint); verify checks target metric. Both must pass to keep commit. - **metric_cmd exit code ignored** — R2 validates metric_cmd by parsing stdout for a float, not exit code. Piping metric output through grep/awk/tr is acceptable; only final stdout float matters. - **Guard/metric scripts protected** — ideation agent must not modify files referenced in `guard_cmd`/`metric_cmd`; exclude them from `scope_files`. New test files may be created within `scope_files` for coverage-improvement campaigns. - **JSONL over TSV** — richer structured fields, `jq`-parseable, no delimiter ambiguity; query with `jq -c 'select(.status == "kept")' experiments.jsonl`. - **State persistence enables resume** — if loop crashes/times out, `resume` picks up exactly where it stopped. - **Safety break**: hard cap = 50 iterations (values above 50 in program.md clamped to 50 with a warning); default 20 when max_iterations unset in program.md; skill never exceeds MAX_ITERATIONS. - **Unbounded cross-skill chain**: `run` → `/research:retro` → `/research:run --hypothesis` / `/research:fortify` → re-run `/research:run` has no campaign-level iteration cap (unlike `sweep`'s `MAX_REFINE = 3` or `run`'s own `MAX_ITERATIONS`). Human-gated each hop — cannot spin autonomously. No counter by design — would need `retro` to write `.experiments/state/`, breaking its read-only invariant. - **Explicit flags = hard requirements**: all flags (`--colab`, `--compute=docker`, `--codex`, `--researcher`, `--architect`) must be available at R2. If unavailable, stop — never silently degrade. - R7 Codex delegation needs no other plugin — `codex-delegation.md` ships in this plugin's own `skills/_shared/` and resolves via `bin/resolve_shared.py`. R7 is silently skipped only if that file is missing (broken install). </notes>
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.