Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
19e28c2
feat(sdebench): first regression task — ratelimiter, validated
nicoloboschi Jun 25, 2026
aa6dc78
feat(sdebench): Docker grading image + harness (full/squashed A/B)
nicoloboschi Jun 25, 2026
77490bc
feat(sdebench): feedback-loop eval — measure interventions, not binar…
nicoloboschi Jun 25, 2026
f0cc8b7
feat(sdebench): task #2 ttlcache — history-dependent, underdetermined…
nicoloboschi Jun 26, 2026
9682226
fix(sdebench): ttlcache uses non-guessable 287 (was 300) so the metri…
nicoloboschi Jun 26, 2026
4e1f96c
feat(sdebench): split tokens (cached/input/output/reasoning) + captur…
nicoloboschi Jun 26, 2026
8b03395
feat(sdebench): browse runs in the AMB UI (agent-trace view)
nicoloboschi Jun 26, 2026
8883823
feat(sdebench): USD cost (gemini-3.5-flash prices) + per-tool-call to…
nicoloboschi Jun 26, 2026
63b0eec
fix(sdebench ui): readable trajectory — single-line tool rows, less p…
nicoloboschi Jun 26, 2026
ffc95dc
fix(sdebench ui): make per-step token direction explicit (↑input ↓out…
nicoloboschi Jun 26, 2026
133d076
docs(sdebench): verify token accounting matches provider (no double-c…
nicoloboschi Jun 26, 2026
4598719
feat(sdebench): show intermediate (rejected) patches per round in the UI
nicoloboschi Jun 26, 2026
2003bad
feat(sdebench): hindsight memory arm — ingest git history, recall via…
nicoloboschi Jun 26, 2026
e544c03
feat(sdebench): task #3 ledger — history-dependent regression (roundi…
nicoloboschi Jun 26, 2026
479efa7
fix(sdebench): remove non-discriminating ratelimiter task; make ledge…
nicoloboschi Jun 26, 2026
1c42239
feat(sdebench): show the repo's git history (source commits) per task…
nicoloboschi Jun 26, 2026
ae98fc6
feat(sdebench): larger shared codebase (billing) with noisy history +…
nicoloboschi Jun 26, 2026
2cf92e9
feat(sdebench ui): show per-step context delta (+Δ) next to cumulativ…
nicoloboschi Jun 26, 2026
70a51c3
feat(sdebench ui): per-turn trajectory view + reasoning-token count
nicoloboschi Jun 26, 2026
f7ddc00
feat(sdebench ui): surface cached tokens (per-turn ⚡ + run-level Cach…
nicoloboschi Jun 26, 2026
f249de4
feat(sdebench): memtool arm — local recall_intent over raw-commit index
nicoloboschi Jun 26, 2026
a0708d8
feat(sdebench): minicalc — bigger/harder codebase (spreadsheet formul…
nicoloboschi Jun 26, 2026
81ffcf4
docs(sdebench): document codebases (small/billing/minicalc) and tasks
nicoloboschi Jun 26, 2026
a154fd4
feat(sdebench): inject arm — PUSH memory (relevant commit diffs in th…
nicoloboschi Jun 27, 2026
b03f15f
feat(sdebench): oracle arm — inject the KNOWN cause commit (memory up…
nicoloboschi Jun 27, 2026
0125091
feat(sdebench): behavioral prompt variants (base/hypothesis/minimal) …
nicoloboschi Jun 28, 2026
2749ea1
docs(sdebench): FINDINGS — push>pull memory, bottleneck taxonomy, lev…
nicoloboschi Jun 28, 2026
1f3ae84
feat(sdebench): SDE_INJECT_K + SDE_INJECT_RICH knobs for push-retriev…
nicoloboschi Jun 28, 2026
b18bccc
docs(sdebench): retrieval ceiling — symptom-distant causes unretrieva…
nicoloboschi Jun 28, 2026
1a49398
feat(sdebench): hybrid arm — push policy context + pull recall_intent…
nicoloboschi Jun 28, 2026
503abe2
docs(sdebench): Exp5 benchmark-wide — inject+minimal robustly best; m…
nicoloboschi Jun 28, 2026
0044916
docs(sdebench): Exp6 hybrid — recovers symptom-distant causes but add…
nicoloboschi Jun 28, 2026
e8041c3
docs(sdebench): solidified headline (n=7-10) — inject+minimal beats g…
nicoloboschi Jun 28, 2026
5bdf5fb
docs(sdebench): Finding 5 — model-invariance (gpt-5.4-mini); knowledg…
nicoloboschi Jun 28, 2026
ccc2ef9
feat(sdebench): minicalc-rangemf-001 — multi-hop / multi-file regression
nicoloboschi Jun 29, 2026
e17be78
docs(sdebench): Finding 6 — multi-hop task; naive push worse than git…
nicoloboschi Jun 29, 2026
c65caa2
feat(sdebench): memory-SOURCE tasks (H/K/F/X) + DATASET_DESIGN.md
nicoloboschi Jun 29, 2026
470ad8e
feat(sdebench): index arm — git-derived DECISIONS.md (non-circular me…
nicoloboschi Jun 29, 2026
12b7b8b
feat(sdebench): properly-isolated 4-SOURCE tasks (H/K/X/F) + provided…
nicoloboschi Jun 29, 2026
823c6dc
feat(sdebench): confmerge-listunion-001 — K task (respect a rejected …
nicoloboschi Jun 29, 2026
cc7eb31
feat(sdebench): retryx-budget-001 — H task (deliberate measured value)
nicoloboschi Jun 29, 2026
eb1c906
feat(sdebench): parsex-sizeunit-001 — X task (cross-feature pattern)
nicoloboschi Jun 29, 2026
a546429
feat(sdebench): F task with a conversation log + conversations arm
nicoloboschi Jun 29, 2026
b10f9d2
feat(sdebench): skill arm — recall_conversations as a discoverable to…
nicoloboschi Jun 29, 2026
fab9ada
feat(sdebench): task generator + auto-validator over a trap library
nicoloboschi Jun 29, 2026
8678685
feat(sdebench): +2 traps (slugify, budget); generator now yields 12 a…
nicoloboschi Jun 29, 2026
5c67db9
feat(sdebench): local file-based memory system (memsys) + harness arm
nicoloboschi Jun 29, 2026
a4cde69
feat(memsys): symbol-aware recall + realistic distractor store
nicoloboschi Jun 29, 2026
2c2cc26
docs(sdebench): memsys results — local file memory beats baseline 15-…
nicoloboschi Jun 29, 2026
739698d
refactor(sdebench): remove the K source (CONVENTIONS.md) — unrealistic
nicoloboschi Jun 30, 2026
391bdd7
docs(sdebench): memsys results refreshed for H/F taxonomy (K removed)
nicoloboschi Jun 30, 2026
b3b72df
chore(sdebench): variant-aware UI export; gitignore .env
nicoloboschi Jun 30, 2026
cda8948
feat(sdebench): realistic verbose sessions + LLM story-summarizer (co…
nicoloboschi Jun 30, 2026
7bed633
chore(sdebench): regenerate F task.json with realistic conversations
nicoloboschi Jun 30, 2026
87c1798
feat(sdebench): boltons real-codebase host suite (4 tasks)
nicoloboschi Jun 30, 2026
d249d2d
feat(sdebench): +discount trap (non-guessable percent-before-fixed) -…
nicoloboschi Jun 30, 2026
2797ba2
feat(sdebench): +parseflag trap (case-sensitive 'true' only) -> 6-tas…
nicoloboschi Jun 30, 2026
a16a52f
docs(sdebench): boltons suite results — vanilla 6 -> memsys 0 interve…
nicoloboschi Jun 30, 2026
c2a5c06
fix(sdebench): parseflag non-guessable ({true,on} set) -> 6/6 boltons…
nicoloboschi Jun 30, 2026
7669f84
docs: boltons suite 6/6 signal in caveats
nicoloboschi Jun 30, 2026
84d3eba
feat(sdebench): +3 real-function traps (pluralize, under2camel, findh…
nicoloboschi Jun 30, 2026
8717022
docs(sdebench): professional dataset packaging — metadata, manifest, …
nicoloboschi Jun 30, 2026
60c71fa
feat(sdebench): dataset tooling — aggregate.py (n-run results) + vali…
nicoloboschi Jun 30, 2026
f12364f
feat(sdebench): long realistic sessions for real-function chats (cons…
nicoloboschi Jun 30, 2026
e7fa5e8
fix(memsys): seed boltons-only store (drop gen-* scaffolding that pol…
nicoloboschi Jun 30, 2026
517eacc
fix(sdebench): sharpen under2camel + findhashtags to be genuinely non…
nicoloboschi Jun 30, 2026
09ce30c
docs(sdebench): final n=5 results — vanilla 56 -> memsys 1 interventi…
nicoloboschi Jul 1, 2026
c59101d
docs: fix DATASET.md task table for sharpened under2camel/findhashtag…
nicoloboschi Jul 1, 2026
03be60b
feat(sdebench): +H-source task (bytes2human) from REAL boltons git hi…
nicoloboschi Jul 1, 2026
9c31487
feat(sdebench): H-source task boltons-omdeq (hard, speedup) from real…
nicoloboschi Jul 1, 2026
f7bf524
feat(sdebench): hard H task boltons-omdset (fork + shabby commit, unt…
nicoloboschi Jul 1, 2026
e83f2e2
feat(sdebench): harden omdset invariant + add oracle baseline, drop o…
nicoloboschi Jul 2, 2026
8b3afc0
feat(sdebench): language-agnostic memsys + realistic history noise
nicoloboschi Jul 2, 2026
725f3a7
feat(sdebench): add hindsight reflect/coding arms + AMB UI export
nicoloboschi Jul 2, 2026
73d4eaf
docs(sdebench): sync datasheet + MANIFEST to current 10-task set
nicoloboschi Jul 2, 2026
b9e2020
refactor(sdebench): extract dataset to the sde-bench submodule
nicoloboschi Jul 2, 2026
eceb6fc
chore(sdebench): drop vendored gen/ (moved into sde-bench submodule) …
nicoloboschi Jul 2, 2026
5de4128
chore(sdebench): bump sde-bench submodule (GENERATING.md + tier docs)
nicoloboschi Jul 2, 2026
376c567
feat(omb): integrate sdebench into the OMB runner (coding task_type)
nicoloboschi Jul 2, 2026
4999cab
feat(omb): wire git+chat docs through the memory provider (coding)
nicoloboschi Jul 2, 2026
14c775e
feat(omb): ingest each task's planted commits into the coding memory …
nicoloboschi Jul 2, 2026
7aa8c3a
results(sdebench): repro via omb runner — vanilla 12 -> hindsight 1 i…
nicoloboschi Jul 2, 2026
0f17df1
feat(omb): focus the coding reflect query on the full decision contex…
nicoloboschi Jul 2, 2026
058ca74
refactor(omb): coding mode runs opencode+plugin (hscoding), AMB never…
nicoloboschi Jul 2, 2026
acac00c
docs(sdebench): coding runs opencode+plugin over the provider-ingeste…
nicoloboschi Jul 2, 2026
0984699
chore(sdebench): remove memsys — Hindsight (provider + plugin) is the…
nicoloboschi Jul 2, 2026
cfca33f
results(sdebench): plugin-architecture sweep — vanilla 11 -> hindsigh…
nicoloboschi Jul 2, 2026
2c58a2e
refactor(omb): coding does zero AMB ingestion — plugin owns backfill …
nicoloboschi Jul 2, 2026
8839472
chore(sdebench): complete the memsys removal (run.py arms + submodule…
nicoloboschi Jul 2, 2026
883764c
feat(sdebench): run the coding agent in a container (isolated + resum…
nicoloboschi Jul 2, 2026
37f2438
feat(sdebench): session seeding, new-plugin JSON config, bank reuse, …
nicoloboschi Jul 2, 2026
cf0a458
chore(sdebench): overnight prep — claude-code image + findings log
nicoloboschi Jul 2, 2026
d673edd
results(sdebench): overnight n=3 vanilla vs hindsight — interv 8.3->6…
nicoloboschi Jul 2, 2026
b9fa165
results(sdebench): overnight n=3 sweep raw results (ov-none-*, ov-hs-*)
nicoloboschi Jul 2, 2026
5487232
chore(sdebench): bump sde-bench submodule (+5 H-source tasks -> 15 to…
nicoloboschi Jul 2, 2026
8909da4
chore(sdebench): bump sde-bench submodule (+4 realfn-H -> 19 tasks, 9…
nicoloboschi Jul 2, 2026
ae57ff2
docs(sdebench): overnight summary — sweep results, UI, claude-code bl…
nicoloboschi Jul 2, 2026
f69bc75
docs(sdebench): correct token/cost analysis — -40% tokens is cheap ca…
nicoloboschi Jul 2, 2026
97685c3
feat(sdebench): claude-code agent support (--agent claude-code)
nicoloboschi Jul 2, 2026
d8814e5
docs(sdebench): claude-code support implemented + validated (comparis…
nicoloboschi Jul 2, 2026
dc15c67
results(sdebench): claude-code (sonnet-5) vanilla vs hindsight — inte…
nicoloboschi Jul 2, 2026
1486719
feat(omb): ingest decoy conversations as chat noise + --agent passthr…
nicoloboschi Jul 2, 2026
fa1318c
fix(sdebench): expanduser on plugin/creds paths + docker-start retry
nicoloboschi Jul 2, 2026
b6ffea2
docs(sdebench): noise/decoys + claude-in-UI + tilde bugfix; full rost…
nicoloboschi Jul 2, 2026
eac506d
docs(sdebench): new H tasks pass full discrimination matrix (naive fa…
nicoloboschi Jul 2, 2026
0a1f586
docs(sdebench): H-task investigation — not too easy overall (vanilla …
nicoloboschi Jul 2, 2026
c1e8406
results(sdebench): opencode full 19-task roster w/ noise — vanilla 26…
nicoloboschi Jul 2, 2026
cc1f081
docs(sdebench): full-roster noise results + H-task verdict (budget-h …
nicoloboschi Jul 2, 2026
8a9f174
docs(sdebench): H-hardening nuance — symptom->cause distance is the r…
nicoloboschi Jul 2, 2026
86ee039
results(sdebench): claude full roster — H tasks too easy for strong a…
nicoloboschi Jul 2, 2026
fb5364a
docs(sdebench): correct finding #2 — memory content was accurate; cla…
nicoloboschi Jul 2, 2026
cbe465f
docs(sdebench): morning summary — full-roster results, H-tasks-too-ea…
nicoloboschi Jul 2, 2026
6c529c4
fix(sdebench): claude memory via --append-system-prompt, not a distru…
nicoloboschi Jul 2, 2026
53d0aa2
docs(sdebench): root-cause claude memory bug (hook additionalContext …
nicoloboschi Jul 2, 2026
190c709
fix(sdebench): claude was running BLIND — allow Bash so it can run py…
nicoloboschi Jul 3, 2026
e7f173f
docs(sdebench): two claude-integration bugs (distrusted memory + bloc…
nicoloboschi Jul 3, 2026
dae323a
fix(sdebench): number intervention feedback so a resumed agent never …
nicoloboschi Jul 3, 2026
e2f0cad
results(sdebench): CORRECTED claude — both fixes -> memory 12->3 inte…
nicoloboschi Jul 3, 2026
15a4726
docs(sdebench): correct morning summary — claude memory 12->3 after b…
nicoloboschi Jul 3, 2026
2c1fe1d
chore(sdebench): bump sde-bench submodule (source H/F -> history/conv…
nicoloboschi Jul 3, 2026
cb6387f
fix(omb): sdebench primary category = source (2: history/conversation…
nicoloboschi Jul 3, 2026
3626aae
results(sdebench): keep only the final 19-task runs (vanilla+hindsigh…
nicoloboschi Jul 3, 2026
3f96fe0
feat(ui): coding-specific table on the dataset page (sdebench)
nicoloboschi Jul 3, 2026
95f57a3
fix(ui): correct claude runs' memory_provider (none/hscoding) so vani…
nicoloboschi Jul 3, 2026
2bb0875
feat(scripts): reusable sdebench comparison charts (interventions/cos…
nicoloboschi Jul 3, 2026
f3b3a29
chore(scripts): charts use Hindsight brand color/label + model names …
nicoloboschi Jul 3, 2026
20e3b88
docs(sdebench): measure Hindsight's own cost — ~$1.14 one-time ingest…
nicoloboschi Jul 3, 2026
42516ab
docs(sdebench): mark Hindsight cost figures internal-only (pricing no…
nicoloboschi Jul 3, 2026
14ff3a6
fix(omb): copy agent/model into coding QueryResult.meta (so claude ru…
nicoloboschi Jul 3, 2026
8ac5797
sdebench: n=3 sweep results (all 4 arms, 3 runs each)
nicoloboschi Jul 3, 2026
9cac007
sdebench: normalize wall to 10s/task retrieval (Cloud p50); time clau…
nicoloboschi Jul 3, 2026
cdaf030
sdebench: wall = turn-normalized (vanilla measured, memory arm at van…
nicoloboschi Jul 3, 2026
4286449
sdebench charts: report per-task averages instead of run totals
nicoloboschi Jul 3, 2026
acba08b
sdebench charts: rename per-task outputs to *-pertask.png (cache-bust)
nicoloboschi Jul 3, 2026
61e00c6
feat(sdebench): hard tier + conversation-amended source; multi-chat b…
nicoloboschi Jul 3, 2026
080d829
chore(sdebench): datasets -> 9cc3d38 (enrichment-preserving emitters)…
nicoloboschi Jul 3, 2026
a4ea9a2
feat(sdebench): surface memory-arm reflect diagnostics; corrections c…
nicoloboschi Jul 4, 2026
8c47d89
fix(sdebench): delete per-task repo/grade copies after grading
nicoloboschi Jul 4, 2026
ae418d6
feat(sdebench): dz campaign results (n=3, 33 tasks, injection-verifie…
nicoloboschi Jul 4, 2026
cdab5c8
data(sdebench): force-add dz campaign result files (12 runs)
nicoloboschi Jul 4, 2026
c605a32
docs(sdebench): capped-task post-mortem in JOURNEY
nicoloboschi Jul 4, 2026
72460de
data(sdebench): claude n=5 runs (none 4-5, hs 4-5); journal closed
nicoloboschi Jul 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ data/tempo/
.data/
.vercel
.env*.local
.env
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "sdebench/datasets"]
path = sdebench/datasets
url = https://github.com/vectorize-io/sde-bench.git
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
159 changes: 159 additions & 0 deletions scripts/sdebench_charts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""Generate comparison charts for the sdebench coding results (vanilla vs memory, both agents).

Reusable across reruns: each "arm" is matched by a run-name GLOB, so an n=3 rerun that produces
nz-oc-none-1/-2/-3 is averaged automatically (with error bars). Point --outputs at the results dir
and --out at where the PNGs go (defaults to the doc's charts/ folder).

Usage:
uv run --with matplotlib python scripts/sdebench_charts.py \
[--outputs outputs/sdebench] [--out ~/Documents/charts]
"""
import argparse, glob, gzip, json, os, statistics as st
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

# 4 arms: (agent, arm, run-name glob). Globs so n>1 reruns (nz-oc-none-1/-2/-3) auto-average.
ARMS = [
("OpenCode", "vanilla", "dz-oc-none*"),
("OpenCode", "memory", "dz-oc-hs*"),
("Claude", "vanilla", "dz-cc-none*"),
("Claude", "memory", "dz-cc-hs*"),
]
AGENTS = ["OpenCode", "Claude"]
AGENT_LABELS = {"OpenCode": "OpenCode\nGemini 3.5 Flash", "Claude": "Claude Code\nClaude Sonnet"}
ARM_LABELS = {"vanilla": "Vanilla", "memory": "Hindsight"}
VANILLA_C, MEMORY_C = "#9aa0a6", "#0080b0" # muted gray vs Hindsight brand blue
TOK_COLORS = {"Input": "#3b82f6", "Cached": "#a5b4fc", "Output": "#f59e0b"}


def load_run(path):
d = json.load(gzip.open(path, "rt") if path.endswith(".gz") else open(path))
a = dict(interventions=0, cost=0, turns=0, wall=0, tok_input=0, tok_cached=0, tok_output=0,
solved=0, tasks=len(d["results"]))
import re as _re
for r in d["results"]:
m = r.get("meta")
if not m: # dz-* runs: metrics live in the runner's reasoning string, not a meta blob
s = r.get("reasoning") or ""
g = lambda pat: _re.search(pat, s)
m = {"interventions": int(g(r"interventions=(\d+)").group(1)) if g(r"interventions=(\d+)") else 0,
"cost_usd": float(g(r"cost=\$([\d.]+)").group(1)) if g(r"cost=\$([\d.]+)") else 0,
"turns": int(g(r"turns=(\d+)").group(1)) if g(r"turns=(\d+)") else 0,
"solved": r.get("answer") == "solved"}
t = m.get("tokens") or {}
a["interventions"] += m.get("interventions") or 0
a["cost"] += m.get("cost_usd") or 0
a["turns"] += m.get("turns") or 0
a["wall"] += m.get("wall_s") or 0
a["tok_input"] += (t.get("input") or 0) + (t.get("cache_write") or 0) # fresh prompt tokens
a["tok_cached"] += t.get("cache_read") or 0 # re-read from cache (cheap)
a["tok_output"] += (t.get("output") or 0) + (t.get("reasoning") or 0)
a["solved"] += 1 if m.get("solved") else 0
return a


def arm_stats(outputs, run_glob):
paths = sorted(glob.glob(f"{outputs}/{run_glob}/coding/boltons.json.gz")
+ glob.glob(f"{outputs}/{run_glob}/coding/boltons.json"))
# de-dup (prefer .gz) and skip a raw .json if its .gz twin exists
seen, runs = set(), []
for p in paths:
key = p.replace(".json.gz", "").replace(".json", "")
if key in seen:
continue
seen.add(key); runs.append(load_run(p))
if not runs:
return None
# Report per-task averages (divide summed metrics by task count) — more interpretable than run totals.
nt = runs[0]["tasks"]
keys = [k for k in runs[0] if k != "tasks"]
mean = {k: st.mean(r[k] for r in runs) / nt for k in keys}
std = {k: (st.stdev([r[k] for r in runs]) / nt if len(runs) > 1 else 0.0) for k in keys}
mean["tasks"] = nt; mean["_n"] = len(runs)
return mean, std


def _pct(v, m):
return f"{(m - v) / v * 100:+.0f}%" if v else ""


def grouped_bar(data, metric, title, ylabel, fmt, out, note=""):
"""One grouped bar per agent: [vanilla, memory]. Value labels + % change on the memory bar."""
fig, ax = plt.subplots(figsize=(6.2, 4))
x = range(len(AGENTS)); w = 0.36
for i, (arm, color, off) in enumerate((("vanilla", VANILLA_C, -w/2), ("memory", MEMORY_C, w/2))):
vals = [data[(ag, arm)][0][metric] for ag in AGENTS]
errs = [data[(ag, arm)][1][metric] for ag in AGENTS]
bars = ax.bar([xi + off for xi in x], vals, w, label=ARM_LABELS[arm], color=color,
yerr=errs if any(errs) else None, capsize=4, error_kw=dict(ecolor="#555", lw=1))
for b, v in zip(bars, vals):
ax.text(b.get_x() + b.get_width()/2, b.get_height(), fmt(v), ha="center", va="bottom", fontsize=9)
# % change annotation above each agent group
for xi, ag in zip(x, AGENTS):
v = data[(ag, "vanilla")][0][metric]; m = data[(ag, "memory")][0][metric]
ax.text(xi, max(v, m) * 1.14, _pct(v, m), ha="center", fontsize=10, fontweight="bold", color=MEMORY_C)
ax.set_xticks(list(x)); ax.set_xticklabels([AGENT_LABELS[a] for a in AGENTS], fontsize=9)
ax.set_ylabel(ylabel); ax.set_title(title, fontweight="bold")
ax.margins(y=0.20); ax.legend(frameon=False, loc="upper right")
ax.spines[["top", "right"]].set_visible(False)
if note:
ax.text(0.0, -0.16, note, transform=ax.transAxes, fontsize=8, color="#666")
fig.tight_layout(); fig.savefig(out, dpi=160, bbox_inches="tight"); plt.close(fig)
print(" wrote", out)


def tokens_chart(data, out):
"""Distinguish Input vs Cached vs Output — grouped bars per run, log y (spans M to k)."""
short = {"OpenCode": "OpenCode·Gemini", "Claude": "Claude·Sonnet"}
runs = [(f"{short[ag]}\n{ARM_LABELS[arm]}", (ag, arm)) for ag in AGENTS for arm in ("vanilla", "memory")]
cats = ["Input", "Cached", "Output"]; keys = {"Input": "tok_input", "Cached": "tok_cached", "Output": "tok_output"}
fig, ax = plt.subplots(figsize=(7.5, 4.2))
x = range(len(runs)); w = 0.26
for j, cat in enumerate(cats):
vals = [data[k][0][keys[cat]] for _, k in runs]
bars = ax.bar([xi + (j - 1) * w for xi in x], vals, w, label=cat, color=TOK_COLORS[cat])
for b, v in zip(bars, vals):
lab = f"{v/1e6:.1f}M" if v >= 1e6 else (f"{v/1e3:.0f}k" if v >= 1e3 else str(int(v)))
ax.text(b.get_x() + b.get_width()/2, b.get_height(), lab, ha="center", va="bottom", fontsize=7.5)
ax.set_yscale("log"); ax.set_ylabel("tokens / task (log scale)")
ax.set_title("Tokens per task: input vs cached vs output", fontweight="bold")
ax.set_xticks(list(x)); ax.set_xticklabels([lbl for lbl, _ in runs], fontsize=9)
ax.legend(frameon=False, ncol=3, loc="upper center", bbox_to_anchor=(0.5, 1.14))
ax.spines[["top", "right"]].set_visible(False)
fig.tight_layout(); fig.savefig(out, dpi=160, bbox_inches="tight"); plt.close(fig)
print(" wrote", out)


def main():
ap = argparse.ArgumentParser()
ap.add_argument("--outputs", default="outputs/sdebench")
ap.add_argument("--out", default=os.path.expanduser("~/Documents/charts"))
args = ap.parse_args()
out = Path(args.out); out.mkdir(parents=True, exist_ok=True)

data = {}
for ag, arm, glb in ARMS:
s = arm_stats(args.outputs, glb)
if s is None:
raise SystemExit(f"no runs matched glob {glb!r} under {args.outputs}")
data[(ag, arm)] = s
n = data[("OpenCode", "vanilla")][0]["_n"]
nt = data[("OpenCode", "vanilla")][0]["tasks"]
note = f"average per task (over {nt} tasks)" + (f", mean of n={n} runs (error bars = std)" if n > 1 else ", n=1")
print(f"charts -> {out} ({note})")

grouped_bar(data, "interventions", "Corrections needed per task", "corrections / task",
lambda v: f"{v:.2f}", out / "interventions-pertask.png", note)
grouped_bar(data, "cost", "Cost per task", "USD / task", lambda v: f"${v:.2f}", out / "cost-pertask.png", note)
grouped_bar(data, "turns", "Tool-turns per task", "turns / task", lambda v: f"{v:.0f}",
out / "turns-pertask.png", note)
# wall + tokens panels dropped for the dz campaign (not uniformly backed; doc reports
# corrections/cost/turns/solve only)


if __name__ == "__main__":
main()
7 changes: 7 additions & 0 deletions sdebench/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Deterministic grading environment for sdebench tasks.
# The ratelimiter lib is dependency-free; pytest + git is all we need.
FROM python:3.11-slim
RUN pip install --no-cache-dir pytest==8.* && apt-get update \
&& apt-get install -y --no-install-recommends git \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /work
16 changes: 16 additions & 0 deletions sdebench/Dockerfile.agent
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Isolated coding-agent environment: runs opencode (+ the Hindsight coding plugin) in a container,
# so each run gets a fresh, seedable opencode session store (no host-store bleed). Grading stays in
# the separate sdebench-base image.
FROM node:22-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# opencode CLI (the npm package fetches the right platform binary)
RUN npm i -g opencode-ai@1.16.2
# permissive config + the Hindsight coding plugin (mounted at /opt/hindsight-coding-opencode at
# runtime; inert unless HINDSIGHT_DISABLED is unset + a bank is set). The built plugin has no runtime
# deps (@opencode-ai/plugin is types-only), so mounting its dir is enough.
RUN mkdir -p /root/.config/opencode \
&& printf '{"plugin":["/opt/hindsight-coding-opencode"],"permission":{"bash":"allow","edit":"allow","webfetch":"allow"}}' \
> /root/.config/opencode/opencode.json
WORKDIR /work
15 changes: 15 additions & 0 deletions sdebench/Dockerfile.agent-claude
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Isolated claude-code agent (parallels sdebench-agent for opencode). Auth (ANTHROPIC_API_KEY or a
# mounted ~/.claude/.credentials.json) is provided at runtime — none baked in.
FROM node:22-slim
RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates python3 \
&& rm -rf /var/lib/apt/lists/*
RUN npm i -g @anthropic-ai/claude-code
# settings: ALLOW Bash/Edit/etc so claude can run the tests it's fixing (pytest). Without a permissions
# allow-list, headless claude blocks Bash ("running commands needs approval") and can't verify its work
# — `--dangerously-skip-permissions` is refused as root, so the allow-list is the way. Memory is injected
# by the HARNESS via --append-system-prompt (a TRUSTED channel; a UserPromptSubmit hook's additionalContext
# is treated by claude as a prompt-injection and REFUSED). Auth (OAuth creds) mounted at runtime.
RUN mkdir -p /root/.claude \
&& printf '{"permissions":{"allow":["Bash","Edit","Write","Read","Glob","Grep","MultiEdit","LS"],"defaultMode":"acceptEdits"}}' \
> /root/.claude/settings.json
WORKDIR /work
Loading