Claude Skill

report

Summarize a run for a human — baseline val → best val → sealed test, the winning candidate, iterations spent, and pass^k. Use after finalize. Writes report.md and prints a compact JSON summary; the source of truth for "did this optimization actually work, and by how much".

LLM Mart · 0 points · 10 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download skillberry-ai-cap-evolve-skills_phases_report-49fcedb.zip · 18 KB
Part of skillberry-ai/cap-evolve — 22 skills

Install

skills CLI npx skills add https://github.com/skillberry-ai/cap-evolve/tree/main/skills/phases/report
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install skillberry-ai-cap-evolve@llmmart
Git git clone https://github.com/skillberry-ai/cap-evolve.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole skillberry-ai/cap-evolve collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

report — did it work, and by how much?

The result of a run is not "we made edits" — it is a defensible answer to did this actually work, and by how much. report lays three numbers side by side: where the seed started (val), where the best candidate landed (val), and the single held-out test number that counts. It is what a human reads to decide whether to ship.

Runs standalone as /cap-evolve:report, or headlessly as the last step of cap-evolve run — same scripts/run.py either way. report is a phase SCRIPT, not a cap-evolve subcommand; invoke the script.

How to read the three numbers

The honest reading is always test vs baseline, with val as a sanity check in between. scripts/run.py produces the numbers; this is the judgment you add on top:

  • test ≈ baseline → no real gain. The val improvement was overfitting or noise the gate let through. Do not ship; tighten gate_k_se or add trials.
  • test ≫ baseline → genuine improvement on data the optimizer never saw. Ship.
  • val ≫ test → the classic overfit signature: the optimizer learned the val set, not the capability. The reported val→test gap is the overfitting, quantified.
  • passk far below pass1 → the gain is fragile across trials; the agent sometimes succeeds but not reliably. A high mean with low pass^k is not a dependable win (τ-bench's point).

Every number is rendered with its stderr when one was measured, because "0.71" and "0.71 ± 0.08" support very different decisions. A gain smaller than the noise floor is not a result — say so plainly rather than quoting the point estimate alone.

Output contract

scripts/run.py owns both artifacts and writes them deterministically from the run dir — do not hand-write or paraphrase them, or two runs stop being comparable.

report.md is exactly this skeleton (bracketed lines appear only when they apply):

# cap-evolve run report — <run_id>

[> **NOT FINALIZED** — no held-out test number. Run the finalize phase first; …]
[> **No holdout** (train == val == test). The test number below is a *fit* metric, …]

- Best candidate: `<best_id>`
- Baseline val: <r> ± <se>
- Best val: <r> ± <se>
- **Held-out test (optimized skills): <r> ± <se>**  (pass^1=…, pass^k=…)
[- Held-out test (baseline `<baseline_id>` skills): <r> ± <se>]
[- **Test improvement (optimized − baseline): <+Δ>**]
[- Val→test gap: <+Δ> — selection optimism on val; this gap IS the overfitting]
- Iterations: <n>
[- Optimized for: <consuming model> (tier <t>)]

[<sealed note — omitted entirely when the run was never finalized>]

stdout is exactly one JSON object — cap-evolve run echoes it as its own result, so it is the machine contract for everything downstream. Keys (null for whatever the run dir does not carry; an unfinalized run is finalized: false with null test numbers): run_dir, best_id, finalized bool, no_holdout bool, baseline_val, baseline_val_stderr, best_val, test_reward, test_stderr, test_baseline_reward, test_baseline_stderr, test_delta, test_pass_k (k→float), val_test_gap, iterations, target_profile ({model,tier,resolution_note}), plus dashboard / dashboard_server / *_error on the paths that produce them.

How to run

python scripts/run.py --run-dir .capevolve/run_XXXX            # JSON + report.md + dashboard.html
python scripts/run.py --run-dir .capevolve/run_XXXX --terminal # colored in-chat ANSI report
python scripts/run.py --run-dir .capevolve/run_XXXX --no-dashboard

--dashboard-mode / --dashboard-port / --dashboard-url are orchestrator-supplied. Re-reporting by hand after cap-evolve run needs --dashboard-url <the URL run printed> or --no-dashboard — launching is deliberately not idempotent, so a bare re-run spawns a second server on a second port and reports that one instead.

The dashboard (dashboard.html)

One self-contained static file (inline CSS/JS/SVG, no CDN, no server, no network — opens from file://); the single shareable artifact. Eight panels reduced from the event log plus baseline/final, rollouts and the git store; every value passes a recursive secret redactor so a shared dashboard leaks no API keys; optional panels degrade silently when per-task data, diffs, or finalize are missing. --terminal renders the same reduction as an ANSI chart for in-chat progress.

References

  • references/concepts.md — why the val→test gap measures overfitting, pass^k fragility, reporting uncertainty, with sources. Load when writing the human interpretation and you want the reasoning or a citation.
  • references/dashboard.md — the reduced graph + summary schema, per-panel field sources, --terminal, redaction, degradation matrix. Load when changing or debugging the dashboard; not needed to run the phase.
Files (cap-evolve)
  • references
    • concepts.md 3.6 KB
      # Concepts — reading a run honestly
      
      > A report's job is not to make the run look good; it is to let a human decide
      > whether to ship. The honest reading is always test-vs-baseline, with the
      > val-test gap as the overfitting it is. Implementation: this skill's
      > `scripts/run.py` + the `cap_evolve.dashboard` builder (panels + schema documented
      > in `references/dashboard.md`).
      
      ## The three numbers, and what their relationships mean
      
      | relationship          | interpretation                                          | action            |
      |-----------------------|---------------------------------------------------------|-------------------|
      | test ≈ baseline       | no real gain (val gain was noise/overfit)               | don't ship; retune|
      | test ≫ baseline       | genuine improvement on unseen data                      | ship              |
      | val ≫ test            | overfitting — learned the val set, not the capability   | the gap is the leak|
      | high mean, low pass^k | gain is fragile across trials (unreliable)              | not a dependable win|
      
      - **baseline (val)** — where the unmodified seed started.
      - **best (val)** — where search landed *on the split it optimized against*. This
        is expected to be optimistic; it is not the result.
      - **test** — scored once, on data nothing was tuned against. This is the result.
      
      ## The val–test gap is overfitting, quantified
      
      Search selects the candidate that scores best on val, so the final val score is
      biased upward by exactly the amount of selection performed. The test score has no
      such bias. Their difference is therefore a direct measurement of how much the run
      overfit the val split. A small gap means the val gains generalized; a large gap
      means the optimizer learned the validation tasks rather than the capability. Report
      the gap honestly — it is one of the most informative numbers in the run, and
      hiding it (by quoting val as "the result") is the most common way optimization
      reports mislead.
      
      ## Reliability: pass^k, not just the mean
      
      A high mean reward can hide an unreliable agent — one that passes often but not
      *every* time. τ-bench showed strong agents whose per-run success looked fine but
      whose pass^k collapsed (succeeding on all of k repeated trials is much harder than
      succeeding once). At report time, a wide pass^1 → pass^k drop is a flag: the gain
      exists but is fragile. For any capability that must work repeatedly, pass^k is the
      number that decides shippability, not the mean.
      
      ## Always report uncertainty
      
      A point estimate without its standard error or confidence interval invites
      over-reading. "0.71" and "0.71 ± 0.08" justify different decisions; a 3-point test
      gain inside a ±8-point CI is not a result. Carry the combined stderr (or a
      bootstrap CI) from finalize into the report so the reader sees the noise floor, not
      just the headline.
      
      ## No-holdout runs
      
      If the run was configured with no holdout (test == train/val), say so plainly: the
      test number is a *fit* metric (how well it fits data it was tuned on), not an
      estimate of generalization. The dashboard and `report.md` should label it so no
      reader mistakes a fit for a held-out result.
      
      ## Sources
      - τ-bench (Yao et al., 2024) — pass^k reliability and the per-run-vs-multi-trial
        gap: https://arxiv.org/abs/2406.12045
      - Koehn, "Statistical Significance Tests for MT Evaluation" (EMNLP 2004) —
        reporting differences with uncertainty: https://aclanthology.org/W04-3250/
      - Hastie, Tibshirani, Friedman, *The Elements of Statistical Learning* — the
        validation–test gap as a measure of optimism/overfit:
        https://hastie.su.domains/ElemStatLearn/
      
    • dashboard.md 6 KB
      # Reference — the dashboard & terminal report
      
      Builder: `cap_evolve.dashboard` (engine-owned, stdlib-only), imported by the report
      skill via `scripts/dashboard.py`. The flow is **reduce → render**:
      
      ```
      reduce_run(run_dir)  → {"graph": {...}, "summary": {...}}   (redacted)
      render_html(reduced, run_dir)  → self-contained dashboard.html
      render_ansi(reduced)           → colored terminal report
      ```
      
      ## Contents
      - [Reduced run schema](#reduced-run-schema) — graph + summary the panels read from.
      - [What each panel reads](#what-each-panel-reads) — the eight HTML panels.
      - [Terminal report](#terminal-report) — the `--terminal` / `--ansi` mode.
      - [Secret redaction](#secret-redaction) — what is scrubbed and how.
      - [Graceful degradation](#graceful-degradation) — which panels hide when data is missing.
      
      ## Reduced run schema
      
      `reduce_run` folds the run dir's append-only `events.jsonl` (the source of truth)
      plus `baseline.json`, `final.json`, the persisted per-task `rollouts/`, and the git
      iteration store into one structure. It never trusts `state.json` for anything it can
      recompute from events.
      
      **graph**
      ```jsonc
      {"root": "seed", "best_id": "cand_0007",
       "nodes": [
         {"id": "cand_0001", "parent": "seed", "children": ["cand_0002"],
          "status": "accepted|rejected|seed|failed",
          "val": 0.62, "stderr": 0.04, "best_so_far": 0.62,
          "per_task": {"t1": 1.0, "t2": 0.0}, "feedback": {"t2": "…"},
          "cost_usd": 0.012, "tokens": 1840, "seconds": 4.1,
          "optimizer_seconds": 2.3, "runner_seconds": 1.8,
          "iteration": 1, "reason": "Δ=+0.12 (paired, p<0.05)",
          "parent_val": 0.50, "epoch": 0, "merge_of": ["cand_0003","cand_0005"]}
       ]}
      ```
      `epoch` appears only for skillopt; `merge_of` only for gepa merges (multi-parent).
      `status` is `failed` when a step produced neither a val score nor rollouts (e.g. the
      optimizer raised); `seed` is the baseline node.
      
      **summary**
      ```jsonc
      {"run_id", "baseline_val", "best_val", "best_id",
       "delta_abs", "delta_pct",                    // %Δ is null off a zero baseline → use delta_abs
       "test_reward", "test_stderr", "test_pass_k", "test_sealed",
       "counts": {"accepted","rejected","failed","seed","total"},
       "frontier",                                  // gated leaves with no accepted child
       "tasks": ["t1","t2", …],
       "wall_clock_seconds", "optimizer_seconds", "runner_seconds",
       "cost": {"optimizer_usd","runner_usd","total_usd"},
       "tokens",
       "gate_warnings": [{"reason","context","mode"}],
       "diagnoses": [{"kind","candidate","text"}],   // gate reasons + diagnose/optimizer_error
       "git_log": [{"hash","subject"}]}              // one row per iteration commit
      ```
      
      ## What each panel reads
      
      1. **KPI strip** — `summary` (best/baseline/Δ, counts by status, frontier, sealed
         test, wall-clock, optimizer-vs-runner cost split, tokens).
      2. **Score over iterations** — `graph.nodes[*].{iteration,val,best_so_far,status,
         parent_val}`. Running-best is an SVG step polyline; champion star + value label;
         record-holder rings on each new best; per-iteration scatter colored by status;
         hover → id / status / val / Δ-from-parent.
      3. **tasks×iterations heatmap** — `graph.nodes[*].per_task` + `summary.tasks`. Rows
         = tasks sorted worst-first by mean reward, cols = iterations; green=pass,
         red=fail, amber=partial, grey=not-run. Hover a cell → that task's feedback.
      4. **Diff vs parent** — `build_diffs(run_dir, graph)` diffs each candidate dir
         against its parent dir (unified, split/unified toggle, add/del/hunk coloring).
      5. **Lineage** — `graph` parent→child DAG; merges drawn as multi-parent edges; the
         best-lineage spine (best_id → root) highlighted gold.
      6. **Cost · tokens · latency** — per-iteration stacked bars (optimizer-seconds blue,
         runner-seconds green) + a cumulative-cost dashed line; plus a separate
         cumulative-cost-vs-best-score plot (shown only when total cost > 0).
      7. **Annotations & diagnoses** — `summary.gate_warnings` + `summary.diagnoses`
         rendered as an inline stream.
      8. **Candidates** — full leaderboard table (id, status badge, val, Δ-parent, iter,
         reason) + the git iteration-store log.
      
      ## Terminal report
      
      `python scripts/run.py --run-dir DIR --terminal` (alias `--ansi`) prints, instead of
      the JSON summary:
      - a one-line **KPI strip** (baseline / best / Δ / sealed test / counts / frontier /
        cost / tokens / wall),
      - a **cumulative-best** block chart (█ running best, ○ accept, · reject, x fail),
      - a **top-N** candidate table (`--top-n`, default 8), and
      - up to three **gate warnings**.
      
      It is sized to the terminal width and is **CLAUDECODE-margin-aware**: when
      `CLAUDECODE=1` it subtracts ~6 columns so lines don't wrap inside the tool-output
      frame. `--no-color` (or `NO_COLOR=1`) disables ANSI codes for piping/CI.
      
      ## Secret redaction
      
      `reduce_run` returns its result through `redact()` before it reaches the HTML or the
      terminal, so a shared `dashboard.html` never leaks a credential pulled in from
      config/env. The redactor walks dicts/lists/strings and:
      - replaces any **value under a secret-looking key** wholesale (`*api_key*`,
        `*secret*`, `*token*` — but not the `tokens` metric — `password`, `credential`,
        `watsonx`, `authorization`, `bearer`, `private_key`, `access_key`, `session`,
        `cookie`); and
      - masks **value-shaped secrets inside free text**: `sk-…`, `Bearer …`, JWTs, long
        hex/base64 blobs, and inline `KEY=value` / `KEY: value` leaks (e.g. an optimizer
        error echoing `RITS_API_KEY=…`), keeping the key name and masking only the value.
      
      Covers `RITS_API_KEY`, `BOBSHELL_API_KEY`, `WATSONX_*`, `OPENAI_API_KEY`,
      `ANTHROPIC_API_KEY`, etc.
      
      ## Graceful degradation
      
      Optional panels hide rather than crash when their data is absent:
      - no per-task rollouts → heatmap is empty/hidden, scores fall back to `baseline.json`
        per_task or the step event's `val`;
      - candidate dirs not snapshotted (e.g. a synthetic log) → `diffs` is `{}`, the diff
        panel hides;
      - not finalized → test KPI shows "—", the test-sealed flag reads "not finalized";
      - no git store → the iteration-store log is omitted;
      - zero cost → the cost-vs-score plot is omitted.
      
  • scripts
    • abstract.py 163 B
      """The 'report' phase composes the project adapter + shared harness; it declares no
      abstract methods of its own. check.py verifies the wiring instead of stubs."""
      
    • check.py 12.1 KB
      """Contract: report summarizes baseline → best val → sealed test and writes
      report.md; and the Wave-4 dashboard builder reduces a synthetic event log into a
      well-formed candidate graph + a self-contained, parseable dashboard.html with no
      leaked secrets.
      """
      
      from __future__ import annotations
      
      import html.parser
      import json
      import sys
      import tempfile
      from pathlib import Path
      
      import _bootstrap  # noqa: F401
      
      from cap_evolve.skillcheck import Checker, import_run, quiet, temp_run_dir
      
      
      def _synthetic_events():
          """A minimal but realistic event log: splits, baseline, an accept, a gate
          warning, a reject — plus an optimizer_error echoing a secret to prove redaction."""
          return [
              {"kind": "splits", "train": 4, "val": 2, "test": 2, "seed": 0},
              {"kind": "evaluate", "split": "val", "tag": "seed", "reward": 0.25,
               "stderr": 0.0, "cost_usd": 0.0, "tokens": 0, "seconds": 0.0},
              {"kind": "baseline", "val": 0.25, "stderr": 0.0},
              {"kind": "step", "candidate": "cand_0001", "accept": True, "reason": "Δ up",
               "val": 0.75, "parent": "seed", "parent_val": 0.25,
               "optimizer_seconds": 1.0, "runner_seconds": 0.5, "cost_usd": 0.01, "tokens": 500},
              {"kind": "gate_warning", "mode": "paired", "reason": "SE collapsed to 0", "context": "se=0"},
              {"kind": "step", "candidate": "cand_0002", "accept": False, "reason": "Δ<=0",
               "val": 0.6, "parent": "cand_0001", "parent_val": 0.75,
               "optimizer_seconds": 0.9, "runner_seconds": 0.4, "cost_usd": 0.008, "tokens": 400},
              {"kind": "optimizer_error", "candidate": "cand_0002",
               "error": "auth failed RITS_API_KEY=rits-supersecret0123456789 retry"},
          ]
      
      
      #: The stdout contract (CONTRIBUTING.md: "scripts/run.py must print a single JSON object").
      #: `dashboard` / `dashboard_server` are added only on the paths that produce them.
      _SUMMARY_KEYS = {
          "run_dir", "best_id", "finalized", "no_holdout", "baseline_val", "baseline_val_stderr",
          "best_val", "test_reward", "test_stderr", "test_baseline_reward", "test_baseline_stderr",
          "test_delta", "test_pass_k", "val_test_gap", "iterations", "target_profile",
      }
      
      
      def _parse_one_json(c, text: str, label: str):
          """Assert stdout is exactly one JSON object carrying the documented key set."""
          try:
              obj = json.loads(text)
          except Exception as e:  # noqa: BLE001
              c.check(False, f"[{label}] stdout is not a single JSON object ({e}): {text!r}")
              return None
          c.check(isinstance(obj, dict), f"[{label}] stdout JSON is not an object: {obj!r}",
                  note="stdout is exactly one JSON object")
          if isinstance(obj, dict):
              missing = _SUMMARY_KEYS - set(obj)
              c.check(not missing, f"[{label}] summary missing documented keys: {sorted(missing)}",
                      note="summary carries the documented key set")
          return obj if isinstance(obj, dict) else None
      
      
      def main() -> int:
          c = Checker("report")
          run = import_run()
          c.require_main(run)
      
          # --- 1. report.md + stdout contract: baseline → best val → sealed test ----
          with tempfile.TemporaryDirectory() as d:
              rd, _ = temp_run_dir(Path(d))
              rd.events_path.write_text(
                  "\n".join(json.dumps(e) for e in _synthetic_events()) + "\n", encoding="utf-8")
              (rd.root / "baseline.json").write_text(
                  json.dumps({"val": {"reward": 0.4, "stderr": 0.05}, "best_id": "seed"}),
                  encoding="utf-8")
              (rd.root / "final.json").write_text(json.dumps({
                  "test": {"reward": 0.8, "stderr": 0.06, "pass_k": 0.7},
                  "test_baseline": {"reward": 0.5, "stderr": 0.07},
                  "baseline_id": "seed", "test_delta": 0.3, "best_id": "cand_0001"}),
                  encoding="utf-8")
      
              with quiet() as out:
                  rc = run.main(["--run-dir", str(rd.root), "--no-dashboard"])
              c.check(rc == 0, "report returned nonzero")
      
              # stdout IS the machine contract: `cap-evolve run` prints report's stdout as its
              # own result (cli.py `print(last)`), so exactly one parseable JSON object with the
              # documented keys is the thing every automation downstream depends on.
              summary = _parse_one_json(c, out.getvalue(), "--no-dashboard")
      
              md_path = rd.root / "report.md"
              c.check(md_path.exists(), "report.md was not written", note="writes report.md")
              md = md_path.read_text()
              c.check("0.4" in md and "0.8" in md,
                      f"report.md missing baseline/test numbers:\n{md}",
                      note="report carries baseline → sealed test")
              c.check("sealed" in md.lower(),
                      "report does not state the test was scored once on the sealed split")
              # Uncertainty is the phase's own headline rule ("0.71" and "0.71 ± 0.08" justify
              # different decisions), so a bare point estimate beside a known stderr is a bug.
              c.check("0.8 ± 0.06" in md and "0.4 ± 0.05" in md,
                      f"report.md renders a bare point estimate despite a known stderr:\n{md}",
                      note="renders reward ± stderr when stderr is known")
              if summary is not None:
                  c.check(summary.get("test_stderr") == 0.06
                          and summary.get("test_baseline_stderr") == 0.07,
                          f"summary drops the stderr finalize measured: {summary}",
                          note="JSON summary carries test/baseline stderr")
                  c.check(summary.get("best_val") == 0.75
                          and summary.get("val_test_gap") == round(0.75 - 0.8, 6),
                          f"summary missing best_val / val→test gap: {summary}",
                          note="JSON summary carries best_val + val→test gap")
                  c.check(summary.get("finalized") is True, f"finalized not True: {summary}")
              c.check("Best val: 0.75" in md and "Val→test gap:" in md,
                      f"report.md missing best val / val→test gap:\n{md}",
                      note="report.md carries best val + the val→test gap")
      
          # --- 1b. an unfinalized run must NOT claim a seal that never happened ----
          with tempfile.TemporaryDirectory() as d:
              rd, _ = temp_run_dir(Path(d))
              (rd.root / "baseline.json").write_text(
                  json.dumps({"val": {"reward": 0.4}, "best_id": "seed"}), encoding="utf-8")
              with quiet() as out:
                  rc = run.main(["--run-dir", str(rd.root), "--no-dashboard"])
              c.check(rc == 0, "report on an unfinalized run returned nonzero")
              md = (rd.root / "report.md").read_text()
              c.check("sealed" not in md.lower(),
                      f"report claims the test split was sealed+scored on a run that never "
                      f"finalized:\n{md}", note="no false seal claim before finalize")
              c.check("NOT FINALIZED" in md, f"unfinalized report is not labelled:\n{md}")
              s = _parse_one_json(c, out.getvalue(), "unfinalized")
              if s is not None:
                  c.check(s.get("finalized") is False, f"finalized not False: {s}")
      
          # --- 1c. a no-holdout run's report.md must say the number is a fit metric --
          with tempfile.TemporaryDirectory() as d:
              rd, _ = temp_run_dir(Path(d))
              rd.events_path.write_text(json.dumps(
                  {"kind": "splits", "train": ["t1"], "val": ["t1"], "test": ["t1"], "seed": 0}
              ) + "\n", encoding="utf-8")
              (rd.root / "final.json").write_text(
                  json.dumps({"test": {"reward": 0.8}, "best_id": "seed"}), encoding="utf-8")
              with quiet():
                  run.main(["--run-dir", str(rd.root), "--no-dashboard"])
              md = (rd.root / "report.md").read_text()
              c.check("No holdout" in md,
                      f"no-holdout run's report.md presents a fit metric as held-out:\n{md}",
                      note="labels a no-holdout run as a fit metric")
      
          # --- 2. reducer → well-formed graph from a synthetic event log -------
          from cap_evolve import dashboard
          with tempfile.TemporaryDirectory() as d:
              rd, _ = temp_run_dir(Path(d))
              rd.events_path.write_text(
                  "\n".join(json.dumps(e) for e in _synthetic_events()) + "\n", encoding="utf-8")
              (rd.root / "baseline.json").write_text(json.dumps({
                  "val": {"reward": 0.25, "per_task": [
                      {"task_id": "t1", "reward": 0.0, "feedback": "wrong"},
                      {"task_id": "t2", "reward": 0.5, "feedback": ""}]}, "best_id": "seed"}),
                  encoding="utf-8")
              (rd.root / "final.json").write_text(
                  json.dumps({"test": {"reward": 0.8}, "best_id": "cand_0001"}), encoding="utf-8")
      
              reduced = dashboard.reduce_run(rd)
              g, s = reduced["graph"], reduced["summary"]
      
              c.check(set(g.keys()) == {"nodes", "root", "best_id"},
                      f"graph keys malformed: {sorted(g.keys())}", note="reducer → {nodes,root,best_id}")
              nodes = {n["id"]: n for n in g["nodes"]}
              c.check(set(nodes) == {"seed", "cand_0001", "cand_0002"},
                      f"graph nodes wrong: {sorted(nodes)}")
              required = {"id", "parent", "children", "status", "val", "per_task",
                          "cost_usd", "tokens", "seconds", "optimizer_seconds",
                          "runner_seconds", "iteration", "reason", "best_so_far"}
              for n in g["nodes"]:
                  missing = required - set(n)
                  c.check(not missing, f"node {n['id']} missing fields: {missing}")
                  c.check(n["status"] in ("seed", "accepted", "rejected", "failed"),
                          f"node {n['id']} bad status {n['status']!r}")
              c.check(nodes["seed"]["children"] == ["cand_0001"]
                      and nodes["cand_0001"]["parent"] == "seed",
                      "lineage edges not wired", note="parent↔child edges wired both ways")
              # Assert the buckets this synthetic log actually exercises, plus the total —
              # not dict equality. `reduce_run` seeds every status bucket it knows about
              # (`indecisive` among them), so an exact-dict assertion breaks whenever a new
              # status is added even though nothing about the reducer regressed.
              want = {"accepted": 1, "rejected": 1, "failed": 0, "seed": 1, "total": 3}
              c.check({k: s["counts"].get(k) for k in want} == want
                      and sum(v for k, v in s["counts"].items() if k != "total") == s["counts"]["total"],
                      f"status counts wrong: {s['counts']}")
              c.check(s["best_val"] == 0.75 and s["best_id"] == "cand_0001",
                      f"best wrong: {s['best_val']} / {s['best_id']}")
              c.check(len(s["gate_warnings"]) == 1, "gate warning not surfaced")
      
              # --- 3. self-contained, parseable HTML, no leaked secret ---------
              out = dashboard.write_dashboard(rd)
              c.check(out.exists(), "dashboard.html not written", note="renders dashboard.html")
              text = out.read_text(encoding="utf-8")
              try:
                  html.parser.HTMLParser().feed(text)
                  parsed = True
              except Exception:  # noqa: BLE001
                  parsed = False
              c.check(parsed, "dashboard.html is not parseable HTML")
              for marker in ('src="http', 'href="http', "<link", "cdn.", "fetch("):
                  c.check(marker not in text, f"dashboard pulls external resource ({marker})",
                          note="self-contained (no CDN / network)")
              for panel in ("Summary", "Score over iterations", "Per-task pass/fail", "Lineage"):
                  c.check(panel in text, f"dashboard missing panel: {panel}")
              c.check("supersecret" not in text,
                      "secret leaked into dashboard.html", note="secret redaction holds")
      
              # --- 4. ANSI terminal report renders --------------------------
              with quiet():
                  rc2 = run.main(["--run-dir", str(rd.root), "--terminal", "--no-color"])
              c.check(rc2 == 0, "report --terminal returned nonzero",
                      note="ANSI terminal report mode")
      
              # --- 5. the dashboard-on path keeps the one-JSON-object contract ---
              # This branch is where a stray print() would creep in (it launches/records a
              # server), and it is the path `cap-evolve run` actually takes.
              with quiet() as out:
                  rc3 = run.main(["--run-dir", str(rd.root)])
              c.check(rc3 == 0, "report with the dashboard on returned nonzero")
              _parse_one_json(c, out.getvalue(), "dashboard-on")
      
          return c.emit()
      
      
      if __name__ == "__main__":
          sys.exit(main())
      
    • dashboard.py 459 B
      """Thin shim: the dashboard builder lives in ``cap_evolve.dashboard``. 
      
      The report skill imports this module so the reducer/renderers stay engine-owned
      and unit-tested under ``core/tests`` while the skill keeps a stable import name.
      """
      
      from __future__ import annotations
      
      import _bootstrap  # noqa: F401
      
      from cap_evolve.dashboard import (  # noqa: F401
          build_diffs,
          reduce_run,
          redact,
          render_ansi,
          render_html,
          write_dashboard,
      )
      
    • run.py 10.5 KB
      """report — summarize a run: baseline → best val → sealed test, and the winner.
      
      Reads the run dir's baseline.json / final.json / events and prints a human and
      machine readable summary. Writes report.md next to them, plus (by default) a
      self-contained dashboard.html. ``--terminal`` / ``--ansi`` prints a colored
      in-terminal report instead (CLAUDECODE-margin-aware) for in-chat progress.
      """
      
      from __future__ import annotations
      
      import argparse
      import json
      import sys
      from pathlib import Path
      
      import _bootstrap  # noqa: F401
      
      from cap_evolve import RunDir
      
      
      def _pm(reward, stderr) -> str:
          """``0.71 ± 0.08`` when the stderr is known, else the bare point estimate.
      
          A point estimate with no noise floor invites over-reading: "0.71" and "0.71 ± 0.08"
          justify different ship decisions.
          """
          if reward is None:
              return "None"
          return f"{reward} ± {stderr}" if isinstance(stderr, (int, float)) else f"{reward}"
      
      
      def main(argv=None) -> int:
          p = argparse.ArgumentParser(prog="report")
          p.add_argument("--run-dir", required=True)
          p.add_argument("--no-dashboard", action="store_true", help="skip generating dashboard.html")
          p.add_argument("--terminal", "--ansi", dest="terminal", action="store_true",
                         help="print a colored ANSI terminal report (KPI strip + cumulative-best "
                              "chart + top-N table) instead of the JSON summary")
          p.add_argument("--no-color", action="store_true", help="disable ANSI colors in --terminal mode")
          p.add_argument("--top-n", type=int, default=8, help="rows in the --terminal candidate table")
          p.add_argument("--dashboard-mode", choices=("auto", "report-only", "off"), default="off",
                         help="ensure the live dashboard server is up (auto/report-only) at the final phase")
          p.add_argument("--dashboard-port", type=int, default=7878)
          p.add_argument("--dashboard-url", default="",
                         help="URL of a dashboard server the caller ALREADY started; recorded "
                              "as-is instead of launching a second one")
          args = p.parse_args(argv)
      
          run_dir = RunDir.open(Path(args.run_dir))
      
          # --- ANSI terminal mode: reduce → render_ansi → stdout, then return ---
          if args.terminal:
              import dashboard
              reduced = dashboard.reduce_run(run_dir)
              print(dashboard.render_ansi(reduced, color=not args.no_color, top_n=args.top_n))
              return 0
      
          baseline = json.loads((run_dir.root / "baseline.json").read_text()) if (run_dir.root / "baseline.json").exists() else {}
          final_path = run_dir.root / "final.json"
          final = json.loads(final_path.read_text()) if final_path.exists() else {}
          finalized = bool(final.get("test"))
      
          base_val_obj = baseline.get("val") or {}
          base_val = base_val_obj.get("reward")
          test = final.get("test") or {}
          test_reward = test.get("reward")
          # Baseline scored on the SAME sealed test split — the honest held-out improvement.
          test_baseline = final.get("test_baseline") or {}
          test_baseline_reward = test_baseline.get("reward")
          test_delta = final.get("test_delta")
          baseline_id = final.get("baseline_id")  # "seed" normally; == best_id if best IS the seed
      
          # best val, the no-holdout verdict and the consuming-LLM profile are already computed by
          # the engine's reducer (``cap_evolve.dashboard.reduce_run``). Read them; never recompute
          # them here and never ask the agent to re-derive them in prose — that is how the report
          # stopped being comparable between runs.
          # ponytail: reduces a second time when the dashboard is also written (write_dashboard
          # reduces again). Thread the reduced dict through write_dashboard if that ever profiles hot.
          best_val = best_stderr = no_holdout = target_profile = None
          try:
              import dashboard
              reduced = dashboard.reduce_run(run_dir)
              s = reduced["summary"]
              best_val = s.get("best_val")
              best_stderr = next((n.get("stderr") for n in reduced["graph"]["nodes"]
                                  if n.get("id") == s.get("best_id")), None)
              no_holdout = bool((s.get("splits") or {}).get("no_holdout"))
              target_profile = s.get("target_profile")
          except Exception:  # noqa: BLE001 — a broken reducer must never break the report
              pass
      
          # Search picks the candidate that scores best on val, so best_val is biased upward by
          # exactly the selection performed. test has no such bias, so the difference measures how
          # much the run overfit val.
          gap = (round(best_val - test_reward, 6)
                 if isinstance(best_val, (int, float)) and isinstance(test_reward, (int, float))
                 else None)
      
          summary = {
              "run_dir": str(run_dir.root),
              "best_id": run_dir.best_id,
              "finalized": finalized,
              "no_holdout": no_holdout,
              "baseline_val": base_val,
              "baseline_val_stderr": base_val_obj.get("stderr"),
              "best_val": best_val,
              "test_reward": test_reward,
              "test_stderr": test.get("stderr"),
              "test_baseline_reward": test_baseline_reward,
              "test_baseline_stderr": test_baseline.get("stderr"),
              "test_delta": test_delta,
              "test_pass_k": test.get("pass_k"),
              "val_test_gap": gap,
              "iterations": run_dir.spent.iterations,
              "target_profile": target_profile,
          }
      
          # pass^k for k > n_trials is UNDEFINED, so aggregate_scores omits it (see
          # loop.aggregate_scores) — never 0.0, which would read as "0% reliable" instead
          # of "not enough trials". Render exactly the ks that are PRESENT, in numeric
          # order: a hardcoded k range would drop a measured pass^3 and invent a
          # `pass^2=N/A` that was never requested (`ks` is a caller kwarg; gepa passes a
          # non-default one). final.json does not record which ks were asked for, so the
          # present keys are the only honest source.
          pk = test.get("pass_k") or {}
          if not isinstance(pk, dict):  # legacy run dirs stored a bare scalar
              pk = {"1": pk}
          pk_str = ", ".join(f"pass^{k}={float(pk[k]):.3f}" for k in sorted(pk, key=int))
      
          md = [f"# cap-evolve run report — {run_dir.root.name}", ""]
          if not finalized:
              # The sealed note below is the exact claim a reader relies on. Emitting it for a run
              # that never scored test is the worst failure mode this phase has — say the opposite.
              md += ["> **NOT FINALIZED** — no held-out test number. Run the finalize phase first; "
                     "everything below is val-only.", ""]
          elif no_holdout:
              md += ["> **No holdout** (train == val == test). The test number below is a *fit* "
                     "metric, not an estimate of generalization.", ""]
          md += [
              f"- Best candidate: `{run_dir.best_id}`",
              f"- Baseline val: {_pm(base_val, base_val_obj.get('stderr'))}",
              f"- Best val: {_pm(best_val, best_stderr)}",
              f"- **Held-out test (optimized skills): {_pm(test_reward, test.get('stderr'))}**"
              + (f"  ({pk_str})" if pk else ""),
          ]
          # When the best candidate IS the seed (no accepted gain), baseline_id == best_id and
          # baseline == optimized — label accordingly rather than implying a separate comparison.
          best_is_seed = baseline_id is not None and baseline_id == run_dir.best_id
          if test_baseline_reward is not None and not best_is_seed:
              baseline_label = f"baseline `{baseline_id}` skills" if baseline_id else "baseline skills"
              md.append(f"- Held-out test ({baseline_label}): "
                        f"{_pm(test_baseline_reward, test_baseline.get('stderr'))}")
              md.append(
                  f"- **Test improvement (optimized − baseline): {test_delta:+}**"
                  if isinstance(test_delta, (int, float)) else f"- Test improvement: {test_delta}"
              )
              sealed_note = (
                  "Test was scored exactly once on the sealed split, for BOTH the baseline "
                  f"(`{baseline_id}`) and the optimized skills — the improvement above is on "
                  "held-out tasks the optimizer never saw."
              )
          else:
              sealed_note = (
                  "Test was scored exactly once on the sealed split. The best candidate is the "
                  "seed (no accepted improvement), so baseline and optimized are identical here."
                  if best_is_seed else
                  "Test was scored exactly once on the sealed split."
              )
          if gap is not None:
              md.append(f"- Val→test gap: {gap:+} — selection optimism on val; this gap IS the overfitting")
          md.append(f"- Iterations: {run_dir.spent.iterations}")
          if target_profile and target_profile.get("model"):
              # The consuming LLM the capabilities were optimized FOR — a different LLM role from
              # the optimizer model that proposed the edits.
              md.append(f"- Optimized for: {target_profile['model']}"
                        + (f" (tier {target_profile['tier']})" if target_profile.get("tier") else ""))
          if finalized:
              md += ["", sealed_note]
          (run_dir.root / "report.md").write_text("\n".join(md) + "\n", encoding="utf-8")
      
          if not args.no_dashboard:
              try:
                  import dashboard
                  dash = dashboard.write_dashboard(run_dir)
                  summary["dashboard"] = str(dash)
              except Exception as e:  # noqa: BLE001 — never let the dashboard break the report
                  summary["dashboard_error"] = str(e)
      
          # Final phase: guarantee the live dashboard server is up and opened, so "the
          # dashboard is created automatically in the last phase" holds even when early
          # auto-start was disabled. Best-effort; never fails the report.
          #
          # NOT idempotent, which is why --dashboard-url exists: maybe_launch() deliberately
          # steps past an occupied port (a stale server there would serve the wrong run), so
          # calling it again after `cap-evolve run` already launched one spawns a SECOND
          # server on a SECOND port and leaks it. When the caller hands us the URL it got,
          # record that and launch nothing.
          if args.dashboard_url:
              summary["dashboard_server"] = args.dashboard_url
          elif args.dashboard_mode in ("auto", "report-only"):
              try:
                  from cap_evolve import dashboard_launch
                  base = run_dir.root.resolve().parent  # absolute: subprocess cwd may differ
                  status = dashboard_launch.maybe_launch(
                      base, mode=args.dashboard_mode, port=args.dashboard_port, open_browser=True
                  )
                  summary["dashboard_server"] = status.get("dashboard")
              except Exception as e:  # noqa: BLE001
                  summary["dashboard_server_error"] = str(e)
      
          print(json.dumps(summary, indent=2))
          return 0
      
      
      if __name__ == "__main__":
          sys.exit(main())
      
    • _bootstrap.py 3.6 KB
      """Thin shim: locate cap_evolve, then defer to cap_evolve._bootstrap.
      
      Skill scripts ``import _bootstrap`` first. The real path-resolution logic lives
      ONCE in ``cap_evolve._bootstrap`` (so it can't drift across skills); this shim
      only has to find that package, which means a minimal upward walk for ``core/`` —
      the single bit of bootstrapping that genuinely must run before cap_evolve is
      importable. Everything else delegates.
      """
      
      from __future__ import annotations
      
      import os
      import sys
      from pathlib import Path
      
      
      def _seed_path() -> None:
          """Minimal: put a dir containing the cap_evolve package on sys.path.
      
          ``CAPEVOLVE_CORE`` is honoured BEFORE any ambient import. An editable install of a
          *different* cap-evolve checkout registers a ``sys.meta_path`` finder, which outranks
          both ``sys.path`` and ``PYTHONPATH`` — so "cap_evolve imports fine" is not evidence that
          it imports the checkout you are standing in. Deferring to the ambient package here made
          an explicit override unreachable, and the symptom was a stale core silently answering
          for this one (``ModuleNotFoundError: cap_evolve.constraints`` from a checkout that
          predates that module). An explicit env var wins.
          """
          env = os.environ.get("CAPEVOLVE_CORE")
          want = Path(env).resolve() if env else None
          if want and (want / "cap_evolve" / "__init__.py").exists():
              loaded = sys.modules.get("cap_evolve")
              already = getattr(loaded, "__file__", None)
              if already and Path(already).resolve().parent.parent == want:
                  return                      # right checkout already imported: touch nothing
              p = str(want)
              if p in sys.path:
                  sys.path.remove(p)
              sys.path.insert(0, p)
              if loaded is not None:
                  # Evicting a module makes a re-import yield a DIFFERENT object, so anything
                  # already holding a reference fails an `is` check. Only ever do it when the
                  # loaded package really is the wrong checkout — otherwise this "fix" becomes
                  # the bug (it broke two identity assertions in core/tests exactly once).
                  for name in [m for m in sys.modules
                               if m == "cap_evolve" or m.startswith("cap_evolve.")]:
                      sys.modules.pop(name, None)
              for finder in list(sys.meta_path):
                  if "cap_evolve" in getattr(finder, "MAPPING", {}):
                      sys.meta_path.remove(finder)
              return
          # A checkout's own core outranks an ambient install. Without this, a skill script run
          # from checkout X silently executed against checkout Y's cap_evolve (an editable install
          # registers a sys.meta_path finder, which outranks sys.path), and the only symptom was
          # missing modules — or, worse, a green result measured against the wrong tree.
          here = Path(__file__).resolve()
          own = next((p / "core" for p in here.parents
                      if (p / "core" / "cap_evolve" / "__init__.py").exists()), None)
          if own is not None:
              os.environ.setdefault("CAPEVOLVE_CORE", str(own))
              return _seed_path()
          try:
              import cap_evolve  # noqa: F401
              return
          except Exception:
              pass
          cands = []
          for parent in here.parents:
              cands.append(parent / "core")
              cands.append(parent)
          for c in cands:
              if (c / "cap_evolve" / "__init__.py").exists():
                  p = str(c)
                  if p not in sys.path:
                      sys.path.insert(0, p)
                  return
      
      
      _seed_path()
      from cap_evolve._bootstrap import ensure_core  # noqa: E402
      
      # Anchor the upward walk at THIS skill script's location (not the core module's).
      ensure_core(Path(__file__).resolve())
      
  • meta.yaml 723 B
    component: phase
    name: report
    summary: Summarize baseline to best-val to sealed-test and name the winner.
    entry: scripts/run.py
    abstract: scripts/abstract.py
    check: scripts/check.py
    # The real dependency is finalize's sealed test number: without final.json this phase
    # can only emit a val-only, NOT-FINALIZED report. That ordering is deliberately NOT
    # expressed as a token today because finalize also `provides: [report]`, so there is no
    # distinct token to need. Expressing it means finalize `provides: [sealed_test]` and
    # report `needs: [sealed_test]` — a two-file change owned by finalize, tracked in #336.
    needs: []
    provides: [report]
    compatible_with:
      capabilities: ["*"]
      optimizers: ["*"]
      algorithms: ["*"]
    
  • SKILL.md 5.2 KB
    ---
    name: report
    description: Summarize a run for a human — baseline val → best val → sealed test, the winning candidate, iterations spent, and pass^k. Use after finalize. Writes report.md and prints a compact JSON summary; the source of truth for "did this optimization actually work, and by how much".
    component: phase
    argument-hint: "--run-dir DIR [--terminal] [--no-dashboard]"
    allowed-tools: Read, Write, Bash
    provides: [report]
    needs: []
    sources: [evo]
    ---
    
    # report — did it work, and by how much?
    
    The result of a run is not "we made edits" — it is a defensible answer to *did this
    actually work, and by how much.* report lays three numbers side by side: where the
    seed started (val), where the best candidate landed (val), and the single **held-out
    test** number that counts. It is what a human reads to decide whether to ship.
    
    Runs standalone as `/cap-evolve:report`, or headlessly as the last step of
    `cap-evolve run` — same `scripts/run.py` either way. report is a phase SCRIPT, not a
    `cap-evolve` subcommand; invoke the script.
    
    ## How to read the three numbers
    The honest reading is always **test vs baseline**, with val as a sanity check in
    between. `scripts/run.py` produces the numbers; this is the judgment you add on top:
    
    - **test ≈ baseline** → no real gain. The val improvement was overfitting or noise
      the gate let through. Do not ship; tighten `gate_k_se` or add trials.
    - **test ≫ baseline** → genuine improvement on data the optimizer never saw. Ship.
    - **val ≫ test** → the classic overfit signature: the optimizer learned the val set,
      not the capability. The reported val→test gap *is* the overfitting, quantified.
    - **pass^k far below pass^1** → the gain is *fragile* across trials; the agent
      sometimes succeeds but not reliably. A high mean with low pass^k is not a
      dependable win (τ-bench's point).
    
    Every number is rendered with its stderr when one was measured, because "0.71" and
    "0.71 ± 0.08" support very different decisions. A gain smaller than the noise floor
    is not a result — say so plainly rather than quoting the point estimate alone.
    
    ## Output contract
    `scripts/run.py` owns both artifacts and writes them deterministically from the run
    dir — do not hand-write or paraphrase them, or two runs stop being comparable.
    
    `report.md` is exactly this skeleton (bracketed lines appear only when they apply):
    
    ```
    # cap-evolve run report — <run_id>
    
    [> **NOT FINALIZED** — no held-out test number. Run the finalize phase first; …]
    [> **No holdout** (train == val == test). The test number below is a *fit* metric, …]
    
    - Best candidate: `<best_id>`
    - Baseline val: <r> ± <se>
    - Best val: <r> ± <se>
    - **Held-out test (optimized skills): <r> ± <se>**  (pass^1=…, pass^k=…)
    [- Held-out test (baseline `<baseline_id>` skills): <r> ± <se>]
    [- **Test improvement (optimized − baseline): <+Δ>**]
    [- Val→test gap: <+Δ> — selection optimism on val; this gap IS the overfitting]
    - Iterations: <n>
    [- Optimized for: <consuming model> (tier <t>)]
    
    [<sealed note — omitted entirely when the run was never finalized>]
    ```
    
    stdout is **exactly one** JSON object — `cap-evolve run` echoes it as its own result, so
    it is the machine contract for everything downstream. Keys (null for whatever the run
    dir does not carry; an unfinalized run is `finalized: false` with null test numbers):
    `run_dir`, `best_id`, `finalized` bool, `no_holdout` bool, `baseline_val`,
    `baseline_val_stderr`, `best_val`, `test_reward`, `test_stderr`,
    `test_baseline_reward`, `test_baseline_stderr`, `test_delta`, `test_pass_k` (k→float),
    `val_test_gap`, `iterations`, `target_profile` (`{model,tier,resolution_note}`), plus
    `dashboard` / `dashboard_server` / `*_error` on the paths that produce them.
    
    ## How to run
    ```
    python scripts/run.py --run-dir .capevolve/run_XXXX            # JSON + report.md + dashboard.html
    python scripts/run.py --run-dir .capevolve/run_XXXX --terminal # colored in-chat ANSI report
    python scripts/run.py --run-dir .capevolve/run_XXXX --no-dashboard
    ```
    `--dashboard-mode` / `--dashboard-port` / `--dashboard-url` are orchestrator-supplied.
    Re-reporting by hand after `cap-evolve run` needs `--dashboard-url <the URL run printed>`
    or `--no-dashboard` — launching is deliberately not idempotent, so a bare re-run spawns
    a second server on a second port and reports that one instead.
    
    ## The dashboard (`dashboard.html`)
    One self-contained static file (inline CSS/JS/SVG, no CDN, no server, no network — opens
    from `file://`); the single shareable artifact. Eight panels reduced from the event log
    plus baseline/final, rollouts and the git store; every value passes a recursive secret
    redactor so a shared dashboard leaks no API keys; optional panels degrade silently when
    per-task data, diffs, or finalize are missing. `--terminal` renders the same reduction
    as an ANSI chart for in-chat progress.
    
    ## References
    - `references/concepts.md` — why the val→test gap measures overfitting, pass^k
      fragility, reporting uncertainty, with sources. Load when writing the human
      interpretation and you want the reasoning or a citation.
    - `references/dashboard.md` — the reduced graph + summary schema, per-panel field
      sources, `--terminal`, redaction, degradation matrix. Load when changing or
      debugging the dashboard; not needed to run the phase.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related