Files
waggle-os/docs/plans/HARNESS-BENCHMARK-PLAN-2026-05-22.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

17 KiB
Raw Blame History

Implementation Plan: Agent-Harness Comparison Benchmark Matrix

Companion to HARNESS-BENCHMARK-GOAL-2026-05-22.md (goal LOCKED to Reading B). Produced by the planner agent (read-only codebase analysis) 2026-05-22.

DIRECTION UPDATE (Marko, 2026-05-22 — "positioning must prove strength on the agent harness")

The benchmark must prove Waggle's OWN harness is strong — not a third-party reference agent. Verified finding: the GAIA 2 83.8% used gaia2-hermes (a generic worker bridging an upstream model API to the ARE adapter — zero Waggle linkage). So nothing yet proves Waggle's harness. This puts the Waggle→ARE adapter back as the CORE deliverable (the Reading-A piece), reconciled with Reading B as: "Waggle = sovereign orchestrator AND ships a first-party harness proven against the references."

Arena chosen (Marko): GAIA 2 — reuse the existing rig. Memory benchmark (LongMemEval/BEAM) drops to a secondary moat lane. TheAgentCompany deferred to "next" (per BENCHMARK-LANDSCAPE-RESEARCH-2026-05-22.md).

CONFIRMED adapter architecture (waggle_worker)

GAIA 2 has no MCP; it exposes apps as CLI tools via a gaia2-exec setuid wrapper, and the agent is a terminal-using agent (--exec-tool terminal + a scenario-rendered ~/AGENTS.md). Hermes drives the env with essentially one terminal tool + AGENTS.md. Waggle's runAgentLoop (AgentLoopConfig) accepts exactly this shape:

ARE/Hermes contract Waggle AgentLoopConfig field
upstream model API (Sonnet 4.6) model + litellmUrl + litellmApiKey
scenario ~/AGENTS.md systemPrompt
single terminal tool → gaia2-exec tools: [terminalTool] (executor shells to gaia2-exec)
task over Unix socket messages
tool/event capture onToolUse / onToolResult (also feeds events.jsonl)

waggle_worker (Node) responsibilities:

  1. Connect to the adapter Unix socket; send {"type":"ready"}; receive {"type":"message","text":<task>,"run_id"}.
  2. Read ~/AGENTS.mdsystemPrompt; define ONE terminal tool whose executor runs the command via gaia2-exec (so calls land in events.jsonl for the judge).
  3. Call runAgentLoop({model: "claude-sonnet-4-6", systemPrompt, tools:[terminal], messages:[task], maxTurns, maxTokenBudget}).
  4. Send {"type":"response","run_id","state":"final"|"error","message": <final answer>}.

gaia2-waggle container: model on gaia2-hermes Dockerfile; same gaia2-init-entrypoint.sh (adapter + eventd + gaia2-exec + AGENTS.md render); swap hermes_worker.py → a Node waggle_worker bundling @waggle/agent (+ @waggle/core, @waggle/shared).

Fairness invariant (the whole point): same model (Sonnet 4.6), same single-terminal-tool, same AGENTS.md, same judge, same scenarios. The ONLY variable is Waggle's loop logic (planning/reflection/verification-gate). That is exactly "harness strength."

Scope: bounded ~1-2 days. Hard parts: (a) containerizing Waggle's Node runtime + TS build inside the image; (b) wiring the terminal tool executor to gaia2-exec; (c) matching maxTurns/token budget to Hermes for fairness. Then low-N probe: Waggle vs Hermes vs OpenClaw on search, same protocol.

De-risk spike finding (2026-05-22) — the native-dep gate

runAgentLoop (agent-loop.ts) transitively imports @waggle/core via injection-scanner.ts (scanForInjection) and turn-context.ts (createCoreLogger). @waggle/core's barrel re-exports the substrate from @waggle/hive-mind-core, which depends on better-sqlite3 (+ sqlite-vec; CLAUDE.md notes a sqlite-vec-windows-x64 variant). The GAIA 2 containers are Linux, so the native module must load in Linux — even though the loop never opens a DB (scanForInjection is pure regex, createCoreLogger is trivial logging). This is the afternoon-eater flagged earlier; it is solvable, not blocking:

  • Path A (cleanest): break the core-barrel dependency for the benchmark worker. Import scanForInjection
    • createCoreLogger from deep paths, or vendor minimal copies, so the worker never pulls the DB barrel → no native dep at all. Smallest container, no sqlite in the agent image.
  • Path B: Linux-build the stack. better-sqlite3 ^12.6.2 has Linux prebuilds (fine via npm install in Linux); swap sqlite-vec-windows-x64 → the Linux/cross-platform sqlite-vec. Heavier image, but uses the real stack unmodified.

Recommend Path A — the loop genuinely doesn't need the DB; a slim worker is faster to build, smaller to ship, and avoids per-arch native-dep maintenance.

PATH A PROVEN (2026-05-22). Spike step 1 done. The agent loop's entire runtime closure from @waggle/core is exactly 2 symbolscreateCoreLogger + scanForInjection — both in DB-free modules (logger.ts, injection-scanner.ts, zero sqlite imports). A 2-symbol stub re-exporting them from hive-mind-core's deep dist/ paths bypasses the db.js barrel (which eagerly loads better-sqlite3 at line 14 of the hive-mind-core index). Verified in an isolated dir outside the monorepo with better-sqlite3 not resolvable: { import_ok: true, runAgentLoop: "function", better_sqlite3: "not-resolvable (clean)" }. Artifacts: waggle-os-gaia2-wt/benchmarks/gaia2/spike-waggle-worker/.

gaia2-waggle container collapses to: node:20-slim + agent dist/ + the 2-symbol stub + hive-mind-core/dist/{logger.js,injection-scanner.js}. No native rebuild, no sqlite. The remaining build is mechanical: (1) Node waggle_worker (socket protocol: ready/message/response); (2) single terminal tool whose executor shells to gaia2-exec; (3) Dockerfile modeled on gaia2-hermes; (4) low-N probe Waggle vs Hermes vs OpenClaw, same model+judge+scenarios.

TWO-PILLAR plan (Marko 2026-05-22: both proofs co-equal)

Pillar Track Near-term move Status
1 — Harness SOTA GAIA 2 rig (this plan) build waggle_worker (Path A) → low-N Waggle-vs-Hermes-vs-OpenClaw probe spike in progress
2 — Memory SOTA memory benchmarks LoCoMo done (C-1 67.8% trio-strict) → LongMemEvalBEAM flagship; ideally on a LOCAL model (sovereign demo) LoCoMo done; LongMemEval/BEAM = new track

Both run under the sovereignty triple (local-first / zero-egress / auditable) and feed Tier-1 + Tier-2 deliverables.


DECISIONS LOCKED (Marko, 2026-05-22)

  1. Comparison set = {Hermes, OpenClaw, Oracle} only — the 3 ARE-native profiles. Do NOT build ARE adapters for Claude Code / Codex / Claude Cowork. Those remain governance-only entries (Reading B: "runs safely inside Waggle, audited"), never capability-scored. → Phase 0.3 (adapter scoping) is dropped; Phase 0.2 external research is dropped; Phase 0 collapses to confirming the 3-profile matrix + Oracle's role as the upper-bound ceiling.
  2. Phase 1 (judge integrity / offline re-judge) green-lit — start immediately, <$50 judge tokens, no new agent spend.
  3. Lower-N first — probe the matrix at low N before committing to any N=160 spine. No ~$1k spend authorized; lower-N probes only until results justify scale-up.

Resulting matrix: {Hermes, OpenClaw, Oracle} × {5 GAIA 2 splits} × {4 metric families}. Oracle = upper-bound reference (gold context) showing headroom; Hermes + OpenClaw = the two real harnesses under test.

Overview

Fill a controlled {entities} × {GAIA 2 splits} × {4 metric families} matrix where Waggle OS is the held-constant local-first arena and harnesses are the variable. One cell exists (Hermes × search × N=160 = 83.8% strict / 86.5% judged-only). The plan is gated by one hard unknown (do Claude Code / Codex / Cowork even run in ARE?) and one Tier-1 blocker (self-judge contamination).

Critical findings from the codebase (these reshape the matrix)

  1. The comparison set as stated is not directly runnable. The runner ships exactly three agent profiles — _HERMES, _OPENCLAW, _ORACLE (container_env.py:67-93, detect_profile() :154-163). Only three container dirs exist: containers/{hermes,openclaw,oracle}. Claude Code, Codex, and Claude Cowork are NOT ARE-native agents — they are product harnesses with their own loops, not GAIA2 adapters. They cannot be dropped into ARE as-is.

  2. OpenClaw is a universal model adapter. Per containers/openclaw/README.md it speaks Anthropic / OpenAI / OpenAI-compat / OpenRouter via its gateway. So the underlying models of those products can run through ARE, but the product harness loop itself does not. Forces a framing decision (Phase 0).

  3. GAIA 2 has 5 splits, not 6. CANONICAL_SPLITS = (execution, search, ambiguity, adaptability, time) (config.py:24-30). The goal doc's "noise" is not a GAIA2 split — drop/remap. Matrix denominator = 5 splits.

  4. Judge wiring: host validation in cli.py:_resolve_judge_config() (:434-480); [judge] TOML → JudgeConfig; injected into in-container gaia2-eventd as GAIA2_JUDGE_* (runner.py:391-438). Changing judge = changing [judge]. Independent/ensemble judge + self-vs-independent delta require offline re-judging of persisted events.jsonl — that harness does not exist yet.

  5. Metrics gap is real. result.json carries only success, reward, num_agent_events, failure_reasons, daemon_status (runner.py:211-284). No tokens, no $, no wall-clock. Must be added.

  6. The 5-error floor is in-container. daemon_status.json status=error written by gaia2-eventd; runner.py:237-241 only reads it. Fix is an in-container daemon change OR a host-side offline-re-judge workaround.


Phase 0 — Framing + Research Gate (BLOCKING, mostly external research)

  1. Resolve "harness vs model" framing (Risk: H · verify: PM sign-off in a §0 addendum)

    • Entities = agent harnesses (Hermes loop vs OpenClaw loop vs Claude Code loop…) or models-under-one-harness (Sonnet vs GPT-5 vs Gemini via OpenClaw)? Controlled-variable principle implies the former; codebase only supports the latter for non-ARE products.
    • Three viable matrices: (A) ARE-native only — Hermes × OpenClaw, model held at Sonnet 4.6 (only clean apples-to-apples); (B) add Claude Code/Codex/Cowork via custom ARE adapters (large build); (C) reframe non-ARE entries as "model rows" via OpenClaw. Recommend (A) as the Tier-1 spine, (B) as stretch.
  2. Research — ARE-compatibility of Claude Code / Codex / Cowork (Risk: H · verify: written per-product verdict {ARE-native:no / adapter-feasible / not-benchmarkable})

    • Verifiable now: no container/profile exists. Needs external research: does each product expose a scriptable single-task-in / final-message-out interface wrappable behind the ARE gaia2_adapter HTTP contract (POST /notify, GET /status, events.jsonl)? Claude Code has headless CLI/SDK; Codex has a CLI; Cowork is a product UI (hardest / possibly impossible). External research — flagged.
  3. Scope custom adapter (only if 0.2 = adapter-feasible) (Risk: H · verify: 1-page adapter design mapping product I/O → gaia2_adapter HTTP contract + faketime + event logging). Real per-product engineering arc.

Phase 0 gates everything else. If 0.2 returns "not-benchmarkable," the defensible matrix is Hermes × OpenClaw, and Tier-2 claims pivot to "run Codex/Claude Code/Hermes locally, audited" as a governance claim — which is exactly Reading B's thesis.


Phase 1 — Judge Integrity (Tier-1 blocker; RUNNABLE NOW, no Phase 0 dep)

  1. Offline re-judge harness (new runner/gaia2_runner/rejudge.py or script) (Risk: M · verify: re-judging existing N=160 reproduces ≈134 PASS within noise). Decouples judging from execution → a $91 run judged N times for judge-token cost only.
  2. Independent + ensemble judge ([judge] + rejudge harness) (Risk: M · verify: self-vs-independent delta reported). Use M6 roster (Opus 4.7 / GPT-5.4 / Gemini 2.5 Pro / Haiku 4.5). Mirror C-1 LOCOMO trio-strict discipline. Pre-register protocol before any new run.
  3. Judge-leniency delta on existing cell (Risk: L · verify: JUDGE-DELTA-search-N160.md with self 86.5% vs independent X% vs trio-strict Y%). Cheapest highest-credibility deliverable available now — strengthens/corrects the one published cell with zero new agent spend.

Phase 2 — Sovereignty Triple Proof (protagonist metric; RUNNABLE NOW)

  1. Egress=0 proof (new runs/sovereignty/egress-proof.md + capture script) (Risk: M · verify: pcap shows only allowlisted provider egress, zero else — or full air-gap with local model). Defensible claim: "zero egress except the user's chosen model endpoint" unless a local model (Ollama/vLLM) is used with --network=none.
  2. Audit-trail completeness (runs/sovereignty/audit-completeness.md) (Risk: L · verify: every tool call in events.jsonl maps to a trace entry). This is the KVARK governance hook.
  3. Reproducibility assertion (Risk: L · verify: hermetic re-run reproduces aggregate within CI). Largely proven for Hermes; formalize per harness.

Phase 3 — Metric Instrumentation (4 families; RUNNABLE NOW)

  1. Capture cost/tokens/wall-clock into result.json (runner.py:211-284) (Risk: M · verify: re-run cell carries tokens_in/out, cost_usd, wall_clock_s, tool_calls). Tokens/$ from OpenClaw gateway traffic (its README: "logs raw model traffic") else estimate from events.jsonl × pricing. Reuse packages/agent/src/cost-tracker.ts pricing table — do not hand-roll. UTF-8-safe + platform-guarded; bundle with the Windows patch set.
  2. 4-family metric schema (new benchmarks/gaia2/METRIC-SCHEMA.md) (Risk: L). Capability: strict + judged-only. Efficiency: tokens/$/tool-calls/wall-clock. Reliability: error-rate/recovery/determinism. Sovereignty: 3 binaries from Phase 2.
  3. Determinism harness (Risk: M · verify: pass@k reruns of a 20-scenario subset report variance). Runner already supports pass_at>1 with avg±stddev + pass@N.

Phase 4 — 5-Error Floor: Fix or Document (RUNNABLE NOW)

  1. Decide fix vs document (Risk: M · verify: PM decision recorded). Fix is in-container (gaia2-eventd soft-close turn after idle-with-N-events) → image rebuild + parity/upstream path; compounding value across 5 splits. Cheaper: host-side workaround — when daemon_status==error but last_response non-empty + ≥N events, re-judge offline (Phase 1.1) instead of counting undecidable.
  2. File upstream issue regardless (Risk: L).

Phase 5 — Matrix Execution (BLOCKED on Phase 0 verdict; gated by 1-4)

  1. Lock cell list from Phase 0 verdict (Risk: M · verify: pre-registration before spend). Spine: Hermes × {5 splits} + OpenClaw × {5 splits}, Sonnet 4.6, N≥160. = 10 cells × ~$91 ≈ $900-1000 — real PM budget question vs the prior $100 single-cell cap.
  2. Per-split N=10 probe before each full cell (Risk: M). Splits differ (time scenarios have durations → longer wall-clock + timeout-FAIL path runner.py:253-268).
  3. Fill cells at concurrency=2 (Risk: M). Use subset_manifest for deterministic finish-passes (NOT --retry — over-selects, per P4.5).
  4. Aggregate + Wilson CI per cell (Risk: L · ≈±6pp at N=160).

Phase 6 — Deliverables (Tier-1 + Tier-2)

  1. Tier-1 pre-registration + writeup (TIER1-PROTOCOL.mdTIER1-RESULTS.md) (Risk: M). Kill the apples-to-oranges Mem0 baseline — the P4.5 doc cites "~40-55% Mem0"; violates the guardrail (different judge/denominator). Drop from any Tier-1 artifact; every number from OUR matrix.
  2. Tier-2 hero claims tracing to Tier-1 cells (TIER2-HERO-CLAIMS.md) (Risk: L). Per Reading B the protagonist claim is the sovereignty triple across all harnesses, not "Waggle #1."

Blocked-on-research vs runnable-now

Phase Status
0 framing + ARE-compat BLOCKED — external research
1 judge integrity Runnable now (existing artifacts)
2 sovereignty proof Runnable now
3 metric instrumentation Runnable now
4 5-error floor Runnable now
5 matrix execution Blocked on Phase 0 + Phases 1-4
6 deliverables Follows 5; judge-delta sub-deliverable after Phase 1

Effort

  • Phases 1-4 (runnable now, no new agent spend): M, days of eng, <$50 judge tokens.
  • Phase 0 research: H uncertainty, low investigation effort, high effort if adapters needed.
  • Phase 5 full matrix: H budget (spine ~$900-1000; full 6×6 multiples more) — PM decision.
  1. Phase 1.1 + 1.3 first — offline re-judge + judge-delta on existing N=160. Highest credibility-per-dollar, closes the Tier-1 self-judge blocker, zero new agent spend.
  2. Phase 0.2 research in parallel — ARE-compat of the three products determines matrix shape; long pole, start immediately.
  3. Then Phases 2-4 while Phase 0 resolves.
  4. Then Phase 5 once PM signs off on cell list + budget.

Three biggest risks

  1. Comparison set may not exist as posed (H). Runner supports only Hermes/OpenClaw/Oracle. The 3 products likely need bespoke adapters or aren't ARE-benchmarkable — may collapse the matrix to Hermes × OpenClaw (fine under Reading B governance framing).
  2. Judge contamination invalidates Tier-1 (H→mitigable now). Only cell self-judges (Sonnet judging Sonnet). Fix cheaply via offline re-judge + trio-strict delta — do first.
  3. Budget (H, non-engineering). 10-cell spine ~$900-1000; 6×6 stretch multiples more. Must be PM-ratified vs the prior $100 cap.