Back to the case study

The Harness: Engineering Notes, Quantified

Project document · Engineering notes

Written by the agent (Claude Code) on 2026-07-06 and re-measured 2026-07-14, with every figure taken from the repo, the logs and the archive.

Reproduced from the project folder as it was left in July 2026. Formatting was converted for the site and long dashes were replaced; the words and numbers are unchanged. File names and figures refer to the project repo, which is not published.

The research story lives in analysis/*.md. This document is about the machine that produced it, the data-collection, labeling, and transport infrastructure , with every claim measured from the repo, the logs, and the archive. First measured 2026-07-06; all figures re-measured 2026-07-14 (addenda dated below).

What it is

One detector, many collectors. The detector is a linear probe that consumes (features, label) rows and reports AUROC; it is deliberately harness-agnostic. Everything else, four generations of collectors, a three-model LLM jury, an unattended model×corpus grid scheduler, and a self-draining mirror with off-site backup, exists to feed it labeled rows at scale without a human in the loop.

The pipeline spans five systems (the fifth added 2026-07-10, see the addendum):

System Role
MacBook (this repo) orchestration, jury grading, all probe analysis, the agent here is Claude Code, the sole executor
Mac Studio M2 Ultra, 192 GB (remote, via Tailscale) activation collection, the only GPU-bound stage
Groq + Cerebras APIs the label jury (3 cross-vendor models, majority vote; seats pinned per-provider, 2 of 3 on Cerebras since 2026-07-12, one paid)
Google Drive (rclone) off-site archive of every activation file ever collected, and, since 2026-07-10, the sole coordination channel between the two agents
Claude Science (claude.ai, Drive connector only) independent re-derivation of probe numbers + offline experiment design; no box or repo access (see addendum)

By the numbers

Code

Metric Value
Python scripts, detector repo 82 (70 analysis + 12 jury)
Collector scripts, local_harness repo 19 run_*.py + the 835-line aec_harness.py
Total lines of code across both repos ~19,500 (12,687 detector + 6,763 collectors)
Grid scheduler 64 lines of shell (grid_queue.sh) driving ~65 sequential model-loads
Written analysis 53 markdown docs; the analysis/ write-ups alone run 4,060 lines
Git history 93 commits over 26 days (2026-06-18 → 2026-07-14)

The harness is many small, single-purpose tools sharing file-format contracts (CSV + npz), not a framework.

Data

Metric Value
Activation archive on Drive 205 npz files; the archive folder totals ~200 GiB
Currently live locally 22 npz, 26 GB data/ (the rest Drive-backed and deleted under the <30 GB disk rule)
Tracked tabular data ~380 files in data/, indexed in data/README.md
Labeled corpora 64 label CSVs across: bios (Arm B), non-person domains (Arm C), FEVER, SciFact, HaluEval dialogue + summarization, FOLIO, PRISM ×2, crescendo (fictional + real ×4 architectures), fresh-entity replication, holdouts, PopQA ×3 models, self-generation
Sentence-level claims graded 2,067 (Arm B) + 1,604 (Arm C) + 1,926 (crescendo real) + per-arch crescendo re-grades + 2,068 self-gen + …
Individual juror verdicts issued ~75,000 across every grading run

Coverage

Metric Value
Model panel 25 rows, ~13 architectures, 5+ vendors, 0.5B → 32B params
Architecture classes covered dense transformer, MoE ×2, state-space (Mamba, no attention), code model, reasoning-distilled, base and instruct, open-data (OLMo/Pythia)
Corpus × model grid in flight 12 model-class reps × ~6 corpora ≈ 65 unattended runs, ~3 days of continuous compute
Label reuse factor the 2,067 Arm B claims were graded once and reused across all 25+ model rows, every new model costs one script registration and zero new labeling

That last row is the core economic decision: labels are a property of the claim text vs ground truth, not of the model. Teacher-forcing the same claims through each new model means the jury bill (the slow, rate-limited, quota-capped stage) was paid one time, and the marginal cost of a 26th model is GPU-hours only.

The transport layer: auto_mirror.sh

The M2 Ultra produces ~2 GB npz files faster than its disk should accumulate them, and the remote box is a loaner with a deadline. auto_mirror.sh is a 59-line daemon that drains it continuously:

pull (rsync) → hash-verify → back up to Drive (rclone, hash-verified) → delete remote
    

The ordering is the invariant: every file exists in ≥2 places at every step of its life. Measured from its own log (lifetime, re-measured 2026-07-14):

The label oracle: the Groq jury

Human labels are the source of truth (qa_grading.xlsx); the jury audits and extends them, and never silently overwrites a human judgment. Design points, each earned by a failure:

The collection layer: four backends, one schema

Each milestone forced a backend change, and each change kept the downstream contract stable so no analysis code had to be rewritten:

  1. Ollama top-10 logprobs (M1), cheap, already built; 1 feature.
  2. llama.cpp full-vocab (M2), 152k-logit rows, byte-reproducible at temp 0. Negative result, but it proved the output side was exhausted, and revealed llama.cpp cannot expose intermediate layers, forcing the pivot.
  3. HF transformers residual stream (M3), per-layer activations at selected token positions, fp16 on MPS. One collector (run_armb_teacherforce.py, 208 lines) handles all 25 panel models including base-mode (--raw), Mamba, and MoE variants.
  4. Multi-turn crescendo (run_crescendo.py, 181 lines), history-carrying N-turn collection, with fallbacks for architectures (Gemma-4) that break the standard config path. Extended to a live adaptive attacker (run_crescendo_adaptive.py): a second model reads the defender's answers and escalates in a closed loop, 12 defender architectures collected unattended.
  5. K-sample resampling (run_semantic_entropy.py), the semantic-entropy baseline collector: 10 sampled generations + sequence logprobs per prompt, same prompt build as the activation collectors so the existing greedy-answer labels apply with zero new grading.

The unattended grid run is the payoff for the schema discipline: 65 model×corpus cells scheduled by 64 lines of shell, each producing an npz that the analysis side scores with a one-line registry entry, while the mirror drains the disk behind it and the operator sleeps.

Scientific hygiene, enforced by tooling

The one-sentence version

In 26 days and ~19,500 lines of small, resumable scripts, this harness moved ~200 GiB of activations across 25+ models and a dozen labeled corpora, issued ~75,000 jury verdicts against Wikipedia ground truth with humans as final authority, lost zero bytes across 183 GB of automated pull-verify-archive cycles, and turned "test a new model" into a one-line registration, which is what made a 25-row cross-architecture panel affordable at all.


Addendum (2026-07-10): the two-agent layer

Everything above described one agent (Claude Code, in this repo) driving the pipeline. The project now runs as two AI agents with disjoint capabilities, coordinating exclusively through Google Drive, no shared filesystem, no live channel, no human relaying state between them.

What Claude Science is

Claude Science is a second, independent Claude instance running in the claude.ai app. Its only I/O is a Google Drive connector with a 10 MB per-file download cap; it has no shell, no repo checkout, and no access to the GPU box. It serves two roles the box-side agent shouldn't hold alone: independent analysis (it re-derives probe numbers from raw activation slices with its own code, the 0.932-vs-0.934 config discrepancy it caught is exactly the class of drift a second implementation exists to catch) and experiment design (it writes collection scripts and specs offline; Phase 2 of the self-generation study runs entirely on its side). Claude Code (this repo's agent) remains the sole executor: it holds the box, the repo, rclone, and the collection queue, and is the explicit gatekeeper, nothing Claude Science drops can divert the box without a deliberate decision here.

The Drive protocol: three contracts, no relay

Casey used to paste status prose between the two agents by hand. Three small file contracts (all committed as specs) eliminated that in both directions:

File Direction Contract
probe_pack/ slices Code → Science activations as L{LL}_{pos}_part{K}.npy float16 slices, each < 10 MB, + row_index.csv; byte-exact reassembly, row order preserved. This is the only way multi-GB archives fit through the connector cap.
STATUS.json Code → Science one machine-readable file, overwritten in place at a fixed Drive id (pinned once, never searched again). phases{} live progress, pull_manifest[] with a ready:true-is-a-promise flag (set only after the last byte of a product is uploaded, a partial pull is structurally impossible), analysis_numbers{} with labels-version/probe/pooling pinned per value (so no-PCA vs PCA-64 figures can never be conflated again), auto-generated changelog[], blockers[]. Written by write_status.py every 5 min (status_sync.sh), uploads on change + 30-min heartbeat; remote-unreachable degrades to last-known-with-stale-flag, dead network degrades to write-locally-retry-later.
from_claude_science/OUTBOX.json Science → Code the reverse channel: a request queue, not a remote control. Items carry action (none/use/review/run-when-precondition); Claude Code's status loop polls it, acts deliberately, and acknowledges via STATUS.json:claude_science_ack. Collection priority and Casey's intent always outrank a flag in this file.

The remaining human bandwidth between the agents is decisions only, the first live exchange over this protocol (Phase 0, below) needed exactly two relayed messages: the initial file handoff and the one-time STATUS.json id.

First production run of the protocol: Phase 0 self-generation

The probe's flagship numbers are teacher-forced; the obvious critique is that self-generated text might read differently. Claude Science designed the experiment and wrote the collector blind (never having seen the box); Claude Code reviewed, fixed, and ran it. The division of labor caught real bugs in both directions:

Reliability postscript (2026-07-08 → 10)

The box became shared (a co-user who power-cycles it) and the operator's network became intermittent, which forced the last robustness tier: heavy runs chained overnight behind .done_markers skip-guards (npz existence stopped being a valid guard once the mirror started deleting them); ssh heartbeat monitors that distinguish reboot / queue-death / console-change; every transport loop (mirror, status sync, phase0 handoff, grade chain) built as retry-until-window with resumable partials, a 5.5 GB pull crossed a dozen link drops without restarting; and a disk rule with teeth after data/ filled 228 GB to literal zero: score a pack → verify it on Drive by name+size → delete the local npz (82.6 GB reclaimed in one audited sweep, nothing lost).


Addendum (2026-07-14): the audit turnaround, and grading goes sovereign

Outside audit → answered experiments, same week

An external audit (2026-07-13) critiqued the project from this document alone. The harness's response time is itself a measurement of the harness:

Every audit item that could be answered with existing labels or free computation was answered within ~36 hours of reading the audit. The items that cost money or human time were triaged to a human decision, not silently dropped.

Batch C: the jury moves onto the box

The label oracle has been the pipeline's only rented dependency, rate-limited, quota-capped, and (since the audit's jury-prior finding) known to leak juror world-knowledge into "supported" verdicts one way. The Batch C build moves grading onto the box's own GPU with two integrity mechanisms the API jury could not enforce:

If the bridge holds, the marginal cost of grading drops to GPU-hours, the same economics that made the 25-model panel affordable, now applied to the labeling stage.

Engineering notes Division of labor The Drive protocol Working paper draft