Rust workspace · finished architecture

Panoptes: types & data flow

The complete evaluation harness the course builds — all four stages. How the structs, enums, traits, and functions across four crates connect, from a scenario spec through generated vignettes and logged model responses to blinded coding and typed results.

panoptes-gen panoptes-harness panoptes-coding all → panoptes-core — every stage depends only on core's types, never on each other.

The story, end to end

It starts with a FamilySpec — a scenario family loaded from a TOML file: its title, the doctrine it cites, the menu of actions an operator may take, and a prompt template with holes to fill. The holes are filled by Params: the four axes that make one scenario differ from another — how confident you are in attribution (who caused the event), whether you have hours or days, whether the action is reversible, and whether more information was requested. all_params enumerates every combination — a 3×2×2×2 grid of 24 — and ScenarioFamily::is_valid discards the incoherent ones. For each surviving Params, generate renders the template into a concrete prompt and hashes it. The result is a Vignette: one fully-specified scenario instance — an id, its params, the exact prompt text, and a SHA-256 of that text so the same scenario always carries the same fingerprint. A vignette is the atom of the whole harness — a single decision to put to a model.

A vignette is only a question until something answers it. ModelClient is the trait every provider implements: give it a prompt, get back a ModelResponse (the text plus token Usage); AnthropicClient is the first concrete implementation, its base URL injectable so tests point it at a mock rather than the live API. dispatch is the loop that turns questions into data — every vignette, across every model, across every epoch (a repeat, to measure consistency) — producing one model call and one ResponseRecord: which vignette, which model, the params carried along for analysis, the prompt, the raw response, the usage, a timestamp. append_record writes each one to responses.jsonl, an append-only log that is never rewritten — the record of what the models actually said is sacred.

Raw responses have to be coded — graded against a codebook — and the grading has to be blind to be trustworthy. make_sheet turns the response records into BlankSheetRows that show a coder only the response text, stripped of any hint of which model produced it or under what parameters; the mapping back to that metadata lives in a separate KeyRow file the coder never sees. When the coded sheets return, load_coded parses each into a CodedRow — and this is the payoff of the type-first design: CodedRow's fields are enums (ActionType, StrategicLogic), so a value outside the codebook is not flagged, it fails to parse. The validation is not a step you run afterward; it is the parse. check_anchors enforces the last rule — a latent code must quote the text it is grounded in — and what remains is a clean, typed judgment that Python reads downstream for the statistics.

01 The pipeline

The full four-stage path, orchestrated by the unified panoptes CLI. A TOML spec and a 24-cell parameter grid become vignettes; the dispatch loop runs them across models and epochs into an append-only log; blinded sheets go out for coding and come back as typed CodedRows that Python reads downstream.

is_valid() filters the grid

panoptes CLI · generate · run · code

ca_geo.toml

FamilySpec::from_toml()

FamilySpec

all_params() · 24 Params (3×2×2×2)

generate(CaGeo, spec)

CaGeo : ScenarioFamily

Vignette · id · params · prompt · sha256

prompts/*.txt · manifest.csv

dispatch() · vignette × model × epoch

ModelClient::generate() · AnthropicClient

ModelResponse · text · Usage

ResponseRecord · response_id · params · usage

append_record()

responses.jsonl · append-only

make_sheet() · shuffled_indices(seed)

sheets/*.csv · blind_key.csv

human / LLM coding

coded/*.csv

load_coded() · check_anchors()

CodedRow · ActionType · StrategicLogic · escalation

Python: stats and report

colour = owning crate · cylinders = files on disk · dashed = outside the Rust workspace · the CLI (dotted) orchestrates the three stages

02 Type relationships

Composition (◆ owns-a), trait implementation (▷ dashed), and the enums that make invalid states unrepresentable. Params is the hub — it is the grid element, it rides inside every Vignette, and it is stamped onto every ResponseRecord. Two traits define the extension points: ScenarioFamily (new scenario) and ModelClient (new provider).

panoptes_core

panoptes_harness

panoptes_gen

«trait»

ScenarioFamily

+name() : String

+is_valid(Params) : bool

CaGeo

+name() : String

+is_valid(Params) : bool

FamilySpec

+String name

+u32 version

+String title

+Vec<String> doctrine_refs

+Vec<String> action_menu

+String template

+from_toml(str) : FamilySpec

panoptes_coding

BlankSheetRow

+String response_id

+String response

+String c1_action

+String c2_logic

+String c2_anchor_quote

+String c3_escalation

KeyRow

+String response_id

+String vignette_id

+String model

+u32 epoch

«error»

CodingError

+usize line

+serde_json::Error source

«enum»

TimePressure

Hours

Days

«enum»

Reversibility

Reversible

Nonreversible

Params

+u8 attribution_confidence

+TimePressure time_pressure

+Reversibility reversibility

+bool info_request

Vignette

+String id

+String family

+Params params

+String prompt

+String prompt_sha256

Usage

+u32 input_tokens

+u32 output_tokens

ResponseRecord

+String response_id

+String vignette_id

+String model

+u32 epoch

+Params params

+String prompt

+String response

+Usage usage

+DateTime run_at

«enum»

ActionType

SensorRetask

Maneuver

Monitor

EscalateToCommand

RequestData

NoAction

«enum»

StrategicLogic

Control

Maritime

Political

Procedural

None

Mixed

CodedRow

+String response_id

+ActionType c1_action

+StrategicLogic c2_logic

+String c2_anchor_quote

+u8 c3_escalation

+String codebook_version

+Option<String> code_notes

ModelResponse

+String text

+Usage usage

«trait»

ModelClient

+model_name() : str

+generate(prompt) : ModelResponse

AnthropicClient

+String api_key

+String model

+String base_url

+new(key, model, url) : AnthropicClient

◆── composition · ┈▷ implements · «enum» / «trait» / «error» stereotypes · ~T~ = generic parameter

03 Per-crate inventory

What each crate defines, and the one stage it owns. The three stage binaries (panoptes-gen, panoptes-run, panoptes-code) are unified behind the single panoptes command.

panoptes-core

The data model — no I/O, no network. Every type that crosses a crate boundary lives here, defined once.

  • enumTimePressure · Hours / Days
  • enumReversibility · Reversible / Nonreversible
  • structParams · the four scenario axes
  • structVignette + vignette_id
  • structUsage · ResponseRecord
  • enumActionType · StrategicLogic
  • structCodedRow · a graded response

panoptes-gen

Stage 1 — generation. Spec + parameter grid → vignettes + manifest.

  • traitScenarioFamily · name + is_valid
  • structCaGeo · the one live family
  • structFamilySpec · from a TOML
  • fnall_params · 24-cell grid
  • fngenerate · filter → render → hash
  • binpanoptes-gen

panoptes-harness

Stage 2 — execution. Dispatches prompts across models × epochs; logs every response.

  • traitModelClient · Send + Sync; async
  • structModelResponse · text + Usage
  • structAnthropicClient · one provider impl
  • fndispatch · the run loop
  • fnappend_record · → JSONL
  • binpanoptes-run

panoptes-coding

Stage 3 — coding. Blinded sheets out; typed rows back (parse = validation).

  • structBlankSheetRow · blinded, no leak
  • structKeyRow · the blind key
  • fnmake_sheet · shuffled_indices
  • errorCodingError · line + source
  • fnload_coded · check_anchors
  • binpanoptes-code

04 Reading the diagrams

  • ◆──Composition — the source struct owns a value of the target type (every Vignette owns a Params).
  • ┈▷Trait implementation — CaGeo ▷ ScenarioFamily, AnthropicClient ▷ ModelClient.
  • «enum»A closed set of variants — the type system rejects any value outside the codebook.
  • ~T~A generic parameter; Option~String~ renders Option<String>.
  • Cylinders are files/stores on disk; dashed nodes are stages outside the Rust workspace.
  • colourcore · gen · harness · coding.