AgentArena · Benchmark Engine

AgentArena — E8 Experiment + Run Pipeline

Versuchsaufbau, A/B-Design und die 11-Schritte-Pipeline eines einzelnen Runs.

Versuchsaufbau

E8 — Domain Knowledge Experiment

E8

Domain Knowledge — Baseline vs Injection

Hypothese: Domain-Knowledge-Injection (ein Satz Projektkontext via Sidecar-NG) ermöglicht Agents, projektspezifische Bugs zu lösen, die sie ohne Kontext nicht lösen können.
Design
4 × 2 × 3
Szenarien
4
Agents
2
Runs gesamt
24
Est. Cost
~$1.50
Model
claude-sonnet-4-6
A/B Design — Der einzige Unterschied
Baseline Agent
CLAUDE.md
Use tools to solve tasks. Be concise.
additional_context
none
VS
Injected Agent
CLAUDE.md
Use tools to solve tasks. Be concise.
additional_context
Ein Satz Domain-Wissen (szenario-spezifisch)
Die 4 Szenarien
E8a
Enum Convention
enum-convention-bug
▼
Agent verwendet lowercase Enum-Werte, aber das Projekt verlangt UPPERCASE.
Injection
"In diesem Projekt verwenden alle Enums UPPERCASE Werte (z.B. 'ACTIVE', 'INACTIVE')."
E8b
Timeout Units
timeout-unit-bug
▼
Agent übergibt Sekunden für Timeout, aber die API verwendet Millisekunden.
Injection
"Die API-Client-Klasse verwendet Millisekunden für alle Timeout-Werte (timeout_ms), nicht Sekunden."
E8c
Breaking Change
breaking-change-bug
▼
Agent ruft find_one() auf, das in V2 zu get_by_id() umbenannt wurde.
Injection
"database.py wurde auf V2 migriert: find_one() heißt jetzt get_by_id()."
E8d
Implicit Null
implicit-null-bug
▼
Agent übergibt None für email, aber die DB hat einen NOT NULL Constraint.
Injection
"Das DB-Schema (schema.sql) hat NOT NULL Constraints auf email."
Ablauf des Experiments
Experiment Start
↓
For each Scenario ( E8a → E8b → E8c → E8d ):
├── Baseline Agent × 3 Runs (pass@3) ──→ [Run Pipeline × 3]
└── Injected Agent × 3 Runs (pass@3) ──→ [Run Pipeline × 3]
↓
Compare: baseline pass@3 vs injected pass@3
↓
Verdict: Injection wirkt?  (0→3 = stark · 3→3 = Task zu einfach)
Jeder einzelne Run durchläuft diese Pipeline
3
Phasen
11
Schritte
2
Sandboxes
k×
pass@k parallel
Setup — Schritte 1–3
Execution — Schritte 4–6
Evaluation — Schritte 7–11
Phase 1 · Setup
01
🗄️
DB-Eintrag erstellen
Run-Record mit Status "running" in SQLite anlegen
▼
Run-Record wird sofort angelegt, bevor irgendeine Sandbox existiert. Status running blockiert parallele Runs auf demselben Task/Agent-Paar.
Run(task_id=task.id, agent_id=agent.id, status="running", created_at=now()) # → run.id für alle weiteren DB-Updates
02
📦
Sandbox erstellen
Docker-Container mit dem Task-Repo geklont
▼
Jeder Run bekommt einen isolierten Docker-Container. Das Task-Repo wird auf den spezifizierten Ref geklont. Der Agent sieht exakt den Zustand, den der Task definiert.
Sandbox.create(task) ↳ docker run --rm -it --network=none <image> ↳ git clone <repo_url> --branch <ref> /workspace
03
🔌
Adapter holen
claude_code Adapter mit Model + CLAUDE.md + optional additional_context
▼
Der Adapter abstrahiert das Modell. Jede Konfiguration (Modell, Prompt-Mode, CLAUDE.md, Injections) steckt im Adapter — der Runner ist modell-agnostisch.
get_adapter("claude_code", config) → Adapter( model="claude-sonnet-4-5", prompt_mode="architect", claude_md="/path/CLAUDE.md", additional_context=Optional[str] )
↓
Phase 2 · Execution
04
🤖
Agent laufen lassen
Claude Sonnet bekommt den Task-Prompt und arbeitet in der Sandbox
▼
Der kritischste Schritt. Der Agent kann Dateien lesen, editieren, Tests ausführen. Die Trajectory (alle Tool-Calls + Outputs) wird vollständig aufgezeichnet.
adapter.run(ctx, sandbox) → RunResult( diff_text="...", # was wurde geändert tokens=12450, # input + output cost_usd=0.043, # API-Kosten trajectory=[...] # Tool-Calls + Outputs )
05
📋
Diff einsammeln
Was hat der Agent geändert?
▼
Der Diff ist das einzige, was vom Agent-Container in die Judge-Phase übertragen wird. Kein shared state, kein shared filesystem — nur der Patch.
sandbox.get_diff() # oder adapter_result.diff_text → """diff --git a/solution.py b/solution.py @@ -12,3 +12,8 @@ def solve(): + result = compute(data) + return result """
06
🗑️
Agent-Container zerstören
Isolation: Judge darf nicht im selben Container laufen
▼
Sicherheits- und Integritäts-kritisch: Der Judge muss auf einem sauberen Zustand evaluieren, nicht auf dem möglicherweise beschädigten Workspace des Agents.
sandbox.destroy() # docker rm -f <container_id> ⚠ Critical: judge must evaluate on clean state, not agent's messy workspace
↓
Phase 3 · Evaluation
07
🧪
Judge-Sandbox erstellen
Frischer Clone + Patch anwenden
▼
Neuer Container, frischer Clone. Nur der Diff des Agents wird per git apply eingespielt. Der Judge sieht exakt dasselbe, was ein Human Code Reviewer sehen würde.
Sandbox.create_judge_sandbox(task, diff_text) ↳ docker run --rm -it <image> ↳ git clone <repo_url> --branch <ref> /workspace ↳ git apply <<< "$diff_text"
08
⚖️
Judge bewerten
success_cmd ausführen (typisch: pytest), plus Lint, Regression, Complexity
▼
Der Judge ist multi-dimensional: funktionale Korrektheit ist primär, aber Lint-Delta, Regressions und Complexity gehen in den Composite Score ein.
judge.judge_run(sandbox, success_cmd) → JudgeResult( tests_passed=8, tests_total=10, regressions=[...], # vorher grüne Tests die jetzt rot sind lint_delta=-2, # verbessert (-) oder verschlechtert (+) complexity_delta=+1, # zyklomatische Komplexität )
09
📊
Metrics berechnen
Composite Score aus functionality + trajectory + efficiency + diff
▼
Der Composite Score verdichtet mehrere Dimensionen in einen vergleichbaren Score [0.0–1.0]. Funktionale Korrektheit dominiert; Effizienz und Direktheit differenzieren bei Gleichstand.
compute_composite_score() Weights: functionality 0.45 # tests_passed / tests_total directness 0.15 # kein Overshooting, sauberer Diff efficiency 0.15 # cost_usd normalisiert self_correction 0.10 # Retry-Loops im Trajectory churn 0.10 # revert-and-redo Patterns loops 0.05 # zyklische Tool-Calls
10
💾
DB updaten
Alles speichern: Status, Tokens, Score, Trajectory
▼
Atomarer Write: alle Felder in einem Update. Falls dieser Schritt schreitert, bleibt der Run auf status=error — kein Partial-Write.
run.update( status="completed", duration_s=47.3, tokens=12450, cost_usd=0.043, tests_passed=8, tests_total=10, tool_calls=23, diff_text="...", trajectory=[...], judge_score=0.84, metrics_json={...} )
11
🧹
Aufräumen
Judge-Container zerstören — immer, auch bei Fehler
▼
Der Cleanup läuft im finally-Block — er wird garantiert ausgeführt, egal ob die Pipeline erfolgreich war oder mit Exception abgebrochen hat. Kein Container-Leak auch bei Crashes.
finally: judge_sandbox.destroy() # docker rm -f <judge_container_id> # Always runs — no container leak on error