Files
hack-house/hh/scripts/bench/olympics/SPEC.md
T
leetcrypt df8f1881d8 feat(olympics): agent Olympics benchmark — multi-language arena, results ledger, model-aware budgets
Fourth benchmark axis: teams of LLM agents deliberate in a room, implement
code in an isolated VM, and are scored deterministically on correctness/speed.

- Multi-language adapter (python/js/go/rust/bash) via MultiPL-E continuation mode
- Append-only JSONL ledger with status tracking (ok/dnf/killed/error) so
  budget-exhausted or crashed runs still record a row (fixes selection bias)
- Model-aware wall-clock scaling (U-shaped by param count; 3x for reasoning)
- Self-owned SIGALRM/SIGTERM watchdog (RunTimeout: BaseException so broad
  except Exception handlers in the infer/completion path can't swallow it)
- Seed forwarded to Ollama sampler + markdown-fence stripping in completion.py
2026-06-27 10:17:23 -07:00

27 KiB
Raw Blame History

Agent Olympics — a hackathon-competition benchmark inside hack-house

Status: DRAFT spec (design only — no implementation yet). Author: bench team. Scope: a fourth benchmark axis layered on hh/scripts/bench/. Arena-mode first, MBPP/MultiPL-E bootstrap, modular team composition, Claude/Opus LLM-as-judge. This document is the build contract. It is grounded in the local Obsidian vault (~/coding/obsidian/research/) and 2026 web research — see References.


1. One-paragraph concept

Stand up two (or more) teams of LLM agents inside hack-house. Each team lives in its own end-to-end-encrypted room and believes it has a private, secure channel to its teammate(s). Each team is given the same coding challenge and an isolated VM to work in. Teams deliberate in chat, then implement and test code in their VM, racing on a blend of correctness, speed, code quality, and collaboration. A referee posts challenges and records everything; a judge (deterministic tests + Claude/Opus LLM-as-judge) scores the submissions and the conversation/tool-call trajectory. Aggregate across a slate of events into a medal table. The framework is the apparatus; the output is a reproducible answer to "which model(s), in which team configuration, collaborate best to ship good code fast?"

This doubles as the most demanding live test of hack-house itself (concurrent agents, encrypted rooms, shared sandbox, ACL, the destructive guard).


2. Goals & non-goals

Goals

  • G1 — Real comms substrate. Team deliberation flows through real hack-house E2E-encrypted rooms, not a simulated bus. The product is the medium.
  • G2 — Modular team composition. Same-model teams (collaboration-protocol study) AND mixed-model teams (capability ladder) are first-class, config-driven.
  • G3 — Bootstrap fast. Reuse the existing bench/ MBPP + MultiPL-E loaders, graders, and PodmanRuntime so M1 runs end-to-end with zero new datasets.
  • G4 — Extensible challenges. A Challenge interface so custom multi-file "file-type" challenges plug in later without touching the arena core.
  • G5 — Trustworthy scoring. Verifiable (unit-test) scoring first; LLM-as-judge only for open-ended quality, with documented bias mitigation and judge calibration.
  • G6 — Reproducible & auditable. Every frame, tool call, and VM state diff is recorded to a replayable transcript; runs are seed/version-pinned.
  • G7 — Measure the placebo. Treat competitive/persona "performance elicitation" framing as an explicit, A/B-testable variable, not folklore (see §14).
  • G8 — Safe by construction. Isolated VMs, deny-by-default egress, reuse the bench/safety guard + injection classifier as a fair-play/safety referee.

Non-goals (for now)

  • Not editing cmd_chat/ (tool code). Arena mode drives turns externally and posts into real rooms. Product mode (real AgentBridge agents talking to each other) is a later milestone that needs an explicit greenlight.
  • Not a training loop (no RL/fine-tuning). This selects and compares; it does not train.
  • Not a public leaderboard. Held-out, private challenge sets are a feature, not a gap (contamination resistance — see §10).

3. Prior art this design borrows from (grounding)

  • Read-vs-write rule for multi-agent work. Coding is write-heavy, shared-state work, the regime where naive parallel multi-agent systems fail (Cognition's "Flappy Bird" conflicting-implicit-decisions failure). The fix: separate a read/deliberate phase from a single-driver write phase, and share full traces, not just messages. We bake this into the loop (§7) and measure the coordination tax. vault: multi-agent-orchestration-patterns
  • Coordination topologies & milestone KPIs. MultiAgentBench/MARBLE (ACL 2025) scores collaboration with milestone-based KPIs across star/chain/tree/graph topologies; AgentCoder/AgileCoder show role specialization (planner/coder/tester) beats undifferentiated peers. We make role + topology config knobs. [web]
  • Eval-harness discipline. Standardize the harness, own a private held-out set; pick challenges for discrimination, not difficulty; report intervals, not point estimates; prefer verifiable scoring, reserve judges for open-ended quality. vault: eval-harnesses-benchmark-design
  • Outcome vs trajectory. Final-output-only scoring overstates quality 2040%; score the path (tool correctness, step efficiency, plan adherence, contribution balance). Use agent-as-a-judge to walk the trajectory. Report pass^k (worst-case) beside pass@k for reliability. vault: agent-evaluation-and-observability
  • Sandbox isolation hierarchy. Plain containers are the floor (shared kernel = one-CVE-from-escape); microVMs (Firecracker/Kata) or gVisor are the bar for untrusted multi-tenant code; deny-by-default egress + hard caps + disposable one-shot. Current hh uses podman --network=none (the container floor) — we note the upgrade path. vault: agent-sandboxing-isolation
  • LLM-as-judge biases. Position, verbosity, self-preference, format, calibration drift are all documented and individually mitigable; calibrate the judge against a human-labeled gold slice before trusting it. [web + vault: llm-evals]

4. Design pillars (constraints every component obeys)

  1. The room is the bus. All inter-agent messages are real encrypted-room frames. The referee is a privileged room member (holds the key) for recording.
  2. Arena now, product later. Orchestrator schedules turns + drives inference, but posts every utterance into the real room. Graduate to real AgentBridge agents in M5.
  3. Phase-separated collaboration. Deliberate (read/plan, parallel-friendly) → Implement (single driver writes to the VM) → Test/iterate → Submit. This is the research-backed shape for write-heavy work and also the thing we measure.
  4. Everything modular & data-driven. Teams, roles, models, topologies, challenges, scoring weights, and elicitation framing are config, not code.
  5. Verifiable-first scoring. Hidden unit tests gate the score; judges only grade what can't be checked mechanically.
  6. Cost is a first-class metric. Multi-agent ≈ 15× chat tokens; track tokens, wall-clock, turns. Report cost-normalized scores so a win bought with 10× spend is visible.
  7. Disposable, isolated, deny-by-default. One VM per team per event, no host secrets mounted, egress blocked by default, hard resource caps.
  8. Reproducible. Pin model versions, seeds, prompts, challenge set hash, and judge version into every result record.

5. Architecture

5.1 Package layout

bench/olympics/
  SPEC.md            # this document
  arena.py           # orchestrator: rooms, turn scheduler, phase machine, termination
  team.py            # Team, Member: persona/role/model binding, topology
  roster.py          # build teams from config (same-model / mixed-model, modular)
  challenge.py       # Challenge ABC + grader interface; MBPP/MultiPL-E adapters
  challenges/        # event specs (bootstrap: pointers into existing bench suites)
  vm.py              # per-team isolated workspace (wraps bench/runtime.py; egress policy)
  loop.py            # the collaboration phase machine (deliberate/implement/test/submit)
  comms.py           # hack-house room client for the arena (connect, post, record)
  transcript.py      # OTel-aligned event log -> replayable JSON per team/event
  scoring.py         # composite score, normalization, intervals, medal table
  judge/
    __init__.py
    deterministic.py # unit-test/lint/complexity scoring (no model)
    llm_judge.py     # Claude/Opus judge client + bias-mitigation harness
    rubric.py        # criterion-separated rubrics per axis
    prompts/         # judge system prompts (versioned)
  referee.py         # fair-play + safety: reuse bench/safety (guard + injection)
  elicitation.py     # persona/framing variants for the placebo experiment (§14)
bench-olympics.py    # launcher: run / replay / score / medal / judge subcommands

5.2 Reuse map (what already exists in bench/)

Need Existing component
Problems + hidden tests bench/suites.py (HumanEval/MBPP), bench/datasets.py, bench/langs.py
Pass@k / grading bench/harness.py (_pass_at_k, assemble+run)
Isolated execution bench/runtime.py (PodmanRuntime, --network=none, caps)
Model inference bench/completion.py (raw) + new chat client in comms.py
Workflow weighting bench/score.py + workflows.json pattern (reused for scoring profiles)
Safety referee bench/safety/ (DESTRUCTIVE, classify.py, inject_bench.py)

5.3 Data flow (one event, one team)

config ─► roster.build_teams ─► arena.run_event
                                   │
   referee posts Challenge brief ──┼──► (room frame, recorded)
                                   ▼
        ┌──────────── loop (phase machine) ────────────┐
        │  DELIBERATE: members post plan/critique turns │  ◄─ inference via comms/judge model
        │  IMPLEMENT : driver !task → commands → vm.run │  ◄─ PodmanRuntime, egress policy
        │  TEST      : run PUBLIC tests in vm, feedback │
        │  (iterate until green or budget exhausted)    │
        │  SUBMIT    : freeze vm artifact               │
        └───────────────────────────────────────────────┘
                                   ▼
   transcript.json + frozen VM artifact ─► judge (deterministic + LLM) ─► scoring ─► medal table

5.4 hack-house integration points

  • Rooms: one room per team (isolation). Arena connects as N agent clients (one per member) + 1 referee client, mirroring how bench-sandbox.py already drives Client/WebSocket sessions.
  • Sandbox: the team's !task path types commands into the shared PTY; vm.py wraps PodmanRuntime for the actual isolated execution + output capture.
  • ACL: the referee acts as room owner, issuing _perm:acl to grant the driver drivers rights for the Implement phase only; revoked between phases.
  • Guard/HITL: the existing DESTRUCTIVE gate stays live; the referee can require host sign-off (the host-sign-off gate, separately specced) for flagged plans.

6. The collaboration loop (phase machine)

State machine per (team, event), bounded by a shared budget (max_rounds, max_tokens, wall_clock_s — whichever trips first):

  1. BRIEF. Referee posts the challenge to the room: task prompt, public example I/O, allowed languages, budget, and the submission protocol.
  2. DELIBERATE (read/plan; parallel-friendly). Round-robin over members; each sees the full shared room trace (Cognition: share full traces, not just messages) and posts one message — proposal, critique, interface decision. Ends on a consensus token (e.g. PLAN-LOCKED) or round cap.
  3. IMPLEMENT (write; single driver). The role-designated driver translates the locked plan into shell/file commands via !task; teammate(s) may post review comments but only the driver writes to the VM. (This is the research-backed way to avoid conflicting implicit decisions in write-heavy work.)
  4. TEST & ITERATE. Run public tests in the VM; failures return to the room as feedback; loop DELIBERATE↔IMPLEMENT until green or budget exhausted.
  5. SUBMIT. Team emits SUBMIT; VM artifact frozen and graded on hidden tests.

Termination: SUBMIT, budget exhaustion, or no-progress detection (no new code + repeated/semantically-duplicate messages over a window). Topology knob: DELIBERATE supports star (lead routes), chain, or free mesh — the collaboration pattern itself becomes an experimental variable.


7. Teams, roles, personas (modular)

# example team config
teams:
  - id: falcon
    topology: star          # star | chain | mesh
    members:
      - name: archie
        model: qwen2.5-coder:7b
        role: architect      # decomposes, sets interfaces, reviews, drives plan
        persona: senior-systems-engineer
      - name: bob
        model: qwen2.5-coder:7b
        role: builder         # implements; the Implement-phase driver
        persona: fast-prototyper
  - id: kestrel
    topology: mesh
    members:                  # mixed-model team
      - { name: kira,  model: qwen2.5:3b,        role: peer, persona: pragmatist }
      - { name: kojo,  model: llama3.2:3b,       role: peer, persona: skeptic }
  • Roles map to persona system prompts + loop privileges (who drives Implement, who must approve PLAN-LOCKED). Built-ins: architect, builder, tester, peer. Role specialization is supported because prior art shows it helps; pure-peer teams are the control.
  • Modularity requirements: same model on all members (protocol study), distinct models per member (capability study), distinct models per team (model-vs-model), and N-member teams (default 2; ≥3 allowed). All from config, no code change.
  • Personas live in elicitation.py as named, versioned prompt fragments so the placebo experiment (§14) can swap them while holding model/challenge fixed.

8. Challenge system

Challenge is an ABC the arena consumes; graders are pluggable.

class Challenge(Protocol):
    id: str
    languages: list[str]
    def brief(self) -> str: ...                  # prompt + PUBLIC examples (room-posted)
    def scaffold(self, vm) -> None: ...          # seed files into the VM (optional)
    def public_tests(self) -> Test: ...          # visible to the team during TEST
    def hidden_tests(self) -> Test: ...          # held out; used only at SUBMIT grading
    def rubric(self) -> Rubric: ...              # open-ended quality criteria for the judge
  • Bootstrap (M1M3): MBPP / MultiPL-E adapter. Wrap existing bench/suites.py problems: the MultiPL-E/MBPP prompt + visible examples become brief()/ public_tests(), and a held-out slice of the asserts becomes hidden_tests() (public/hidden split prevents teaching-to-the-test). Languages from bench/langs.py.
  • Events = challenge archetypes (the "Olympics"):
    Event Shape Primary metric
    Sprint one easy problem speed (turns + wall-clock)
    Marathon hard / multi-file build correctness (hidden pass@k)
    Relay disjoint modules per member interface-handshake success
    Debugging fix a broken repo (injected bugs) time-to-green
    Code review catch a planted bug collaboration / detection
    Security resist a socially-engineered unsafe ask resistance (reuse §13)
  • Custom "file-type" challenges (post-bootstrap, G4): multi-file projects with a scaffold + a containerized test command. Author once as a challenges/<id>/ dir (brief.md, scaffold/, public_tests/, hidden_tests/, rubric.json) — the arena needs no changes. Pick for discrimination: retire any event all teams ace or all fail.

9. Scoring model

Composite per (team, event); weights are a profile (same mechanism as workflows.json).

event_score = wc·correctness + ws·speed + wq·quality + wb·collaboration + (penalties)
Axis Source How
Correctness deterministic hidden-test pass@k; gate: 0 here caps the rest. Also report pass^k (worst-case reliability across repeated runs).
Speed deterministic normalized turns-to-green + wall-clock; tie-break tokens.
Quality deterministic + judge lint + cyclomatic complexity (deterministic) and LLM-judged readability/design against the rubric.
Collaboration trajectory + judge contribution balance (message/edit distribution), plan adherence, did review catch a bug, redundant-step rate. Agent-as-judge walks the transcript.
Penalties referee destructive/injection events, budget overrun, no-progress stalls.
  • Normalization & intervals. Per-event z-score or min-max across teams; report confidence intervals (multiple seeds / problem samples) and do not rank teams whose intervals overlap (vault eval discipline).
  • Cost-normalized variant. Also publish score / tokens and score / wall-clock so a 15×-spend win is not mistaken for a free one.
  • Medal table. Aggregate event_scores into per-team standings across the slate (gold/silver/bronze per event + overall), with weights per olympics-profile.

10. Judging (deterministic + Claude/Opus LLM-as-judge)

Two layers; LLM-judge only where deterministic checks can't reach.

10.1 Deterministic (always-on, free of model bias)

Unit-test pass@k/pass^k, lint, complexity, build success — in judge/deterministic.py.

10.2 LLM-as-judge — three operating modes (per user requirement)

The judge can orchestrate (drive a run live) or analyze (post-hoc), at two scales:

  1. In-session judge. Claude in the current session reads one event's transcript
    • VM diff + tool-call log and scores the open-ended axes. Fast, interactive, good for a single event or while iterating on the framework.
  2. Skill judge — individual. A /olympics-judge skill (one Skill invocation = one isolated judging session) pinned to Opus 4.x (claude-opus-4-8) grades one submission package. Clean context per submission → no cross-contamination; good for careful single-event grading at higher capability than the competitors.
  3. Skill judge — batch / large-scale. The launcher fans out many /olympics-judge sessions (one per submission) for a full tournament, then aggregates. Parallel, reproducible, scales to many teams × events.

Judge input package (what every judge mode receives):

  • the frozen VM submission (final file tree + build/test output),
  • the full hack-house conversation log for the team (recorded transcript),
  • the tool-call / command log (every !task → commands → result),
  • the final VM state (diff vs scaffold), and
  • the rubric (criterion-separated) for the event.

Bias mitigation (mandatory, from the research):

  • Position bias: when comparing two teams pairwise, randomize/swap order and aggregate; prefer independent rubric scoring then derive A-vs-B from scores.
  • Verbosity bias: rubric scores quality per-criterion, not "which is longer."
  • Self-preference: never let a competitor model judge its own family; the judge (Opus 4.x) is stronger than and distinct from the local competitors.
  • Calibration: validate the judge against a small human-labeled gold slice and report judge↔human agreement before trusting judge scores for ranking; pin judge model version + prompt version in results.

11. Observability & transcript schema

Record everything as spans aligned to the OpenTelemetry GenAI semantic conventions (invoke_agent, execute_tool, gen_ai.client.token.usage) so traces are portable and the judge gets clean structured input.

// transcript event (one per room frame / model call / tool call / phase change)
{
  "ts": 0.0, "event_id": "...", "team": "falcon", "challenge": "mbpp-py-42",
  "phase": "IMPLEMENT", "kind": "execute_tool",     // message|invoke_agent|execute_tool|phase|acl|guard
  "actor": "bob", "role": "builder",
  "model": "qwen2.5-coder:7b", "model_version": "...",
  "payload": { "task": "...", "commands": ["..."], "rc": 0, "out_trunc": "..." },
  "tokens": { "in": 0, "out": 0 }, "redactions": []
}
  • One transcript JSON per (team, event); a run manifest pins config hash, seeds, model versions, challenge-set hash, judge version → full reproducibility.
  • bench-olympics.py replay <transcript> re-renders the room conversation for audit; re-judging under a new rubric is a pure function of the recorded package.

12. Isolation, security & fair play

  • VM per team per event, disposable/one-shot, no host secrets mounted, egress deny-by-default. M1 uses the existing PodmanRuntime --network=none (the "container floor"). Upgrade path for stronger/multi-tenant isolation: gVisor, then Kata/Firecracker microVMs, or a hosted sandbox (E2B/Modal) — sized to the threat (vault sandboxing hierarchy).
  • Team isolation via separate encrypted rooms + separate VMs; no cross-room keys. No internet means teams can't fetch a published solution.
  • Fair-play referee (referee.py) reuses bench/safety: the DESTRUCTIVE guard flags dangerous commands; the injection classifier (bench/safety/classify.py) watches for sabotage / social-engineering. Violations → penalties + logged.
  • Equal budget enforced identically per team (rounds/tokens/wall-clock).

13. Performance elicitation — the "placebo" experiment (G7)

Hypothesis (user's): the framing — a high-stakes secure competition with elite personas and a private teammate channel — pushes models toward better, more novel solutions. The literature is mixed (role prompting and stakes/emotional framing show real but inconsistent effects), so we make framing a measured variable, not an assumption.

  • Treatment knobs (in elicitation.py, swappable while model+challenge fixed):
    • Competition framing: "you are competing against another team; the faster, cleaner solution wins" vs. neutral "solve this task."
    • Persona priming: "elite staff engineer known for elegant, novel solutions" vs. bare assistant.
    • Stakes/audience: visible scoreboard, time pressure, "your teammate is counting on you" vs. none.
    • Secure-comms theater: explicitly tell agents the channel is private/encrypted (true) vs. silent.
  • Design: A/B (factorial) — run identical models/challenges with framing ON vs OFF; compare correctness, quality, novelty, and solution diversity (distinct passing approaches). Report effect size with intervals; a knob only "works" if it beats the neutral control outside the confidence band.
  • Novelty metric: cluster passing solutions (AST / embedding distance); reward approaches that pass hidden tests and differ from the canonical/most-common solution — this is where "novel solutions" become measurable rather than vibes.

14. Configuration & CLI

bench-olympics.py run     --config events/round1.yaml [--seed N] [--judge none|insession|skill]
bench-olympics.py replay  <transcript.json>
bench-olympics.py score   --run <run_dir> --profile balanced   # re-rank, no re-run
bench-olympics.py medal   --run <run_dir>
bench-olympics.py judge   --run <run_dir> --mode skill --model claude-opus-4-8 [--batch]

Config carries: teams (§7), event slate (§8), budget, scoring profile (§9), elicitation arms (§13), runtime/isolation tier (§12), judge mode (§10). Mirror the run/score separation already proven in bench/score.py: results persist, score/medal re-rank without re-running a single model.


15. Build milestones (with acceptance criteria)

  • M1 — Arena spine. One team (2 members, same model) solves one MBPP problem end-to-end: real room deliberation → driver !taskPodmanRuntime → public tests → SUBMIT → hidden-test grade → transcript.json. Done when: a full transcript replays and a deterministic score is produced.
  • M2 — Two teams, isolation, scoring. Parallel rooms + VMs, composite score with intervals, cost tracking, scoreboard. Done when: two teams race the same event and a ranked result with CIs is emitted.
  • M3 — Events, roles, personas, topologies. Event catalog (Sprint/Marathon/Relay/ Debugging/Review), role-based loop privileges, medal table. Done when: a 3-event slate produces a medal table from config alone.
  • M4 — Judging + referee. Deterministic quality + LLM-judge (in-session, then skill) with bias mitigation and a gold-slice calibration report; safety/fair-play referee live. Done when: judge↔human agreement is reported and penalties fire.
  • M5 — Product mode. Real AgentBridge agents converse agent-to-agent (needs the bridge greenlight); arena nudges turns. Done when: an event completes using real product agents end-to-end.
  • M6 — Placebo experiment. Factorial elicitation arms + novelty/diversity metrics. Done when: an A/B run reports framing effect sizes with intervals.

16. Open questions / decisions needed

  1. Turn-taking in Arena mode: strict round-robin vs. a lightweight "who speaks next" router (star topology). Start round-robin (deterministic), add router in M3?
  2. Public/hidden split for MBPP: how many asserts to reveal vs. hold out so public_tests guide without leaking the full spec? (Proposal: reveal 1 example, hold the rest.)
  3. Budget defaults: rounds/tokens/wall-clock caps that keep an event under a few minutes locally while leaving room to actually collaborate.
  4. Judge model pinning: confirm the exact Opus id for the skill judge (claude-opus-4-8) and whether batch judging runs via the Skill tool or a separate headless session.
  5. Isolation tier: stay on podman --network=none for local runs, or invest in gVisor/microVM now for stronger guarantees and future multi-tenant use?
  6. Novelty metric: AST-distance vs embedding-distance for solution diversity — which is cheap and discriminating enough locally?

References

Local vault (~/coding/obsidian/research/):

  • 2026-06-07-multi-agent-orchestration-patterns.md (read-vs-write, share full traces, topologies, 15× cost)
  • 2026-06-09-eval-harnesses-benchmark-design.md (discrimination, intervals, verifiable-first, judge de-biasing, held-out sets)
  • 2026-06-07-agent-evaluation-and-observability.md (outcome vs trajectory, pass^k, agent-as-judge, OTel GenAI semconv)
  • 2026-06-09-agent-sandboxing-isolation.md (container floor → gVisor → microVM; deny-by-default egress; disposable VMs)
  • 2026-06-09-securing-multi-agent-systems.md, 2026-06-16-shared-memory-in-multi-agent-systems.md, 2026-06-02-llm-evals.md, 2026-06-07-agent-reliability-guardrails-and-hitl.md (siblings)

Web (2026):