Documentation
¶
Overview ¶
Package bench measures what the MCP surface costs and whether it works: it drives a complete lifecycle over a generated fixture workspace through the REAL tool surface and meters every result byte, then verifies the workspace actually reached the states the workflow promises.
The agent-judge reference curves and the operating discipline (n=3 batches, the all-valid gate, nonce anchors, DISQUALIFIED vs INVALID) live in docs/bench-curves.md — read it before comparing any judge batch against history.
It exists because token cost and outcome quality pull against each other, and both sides of that trade have bitten this repository in the same week — texts trimmed for the output diet hid real ambiguity, and texts grown for clarity taxed every call of every session. Variants must therefore be MEASURED against each other, never argued: the A/B mode runs one script over one fixture against two server binaries and reports the deltas.
Validity outranks tokens by construction. A run whose workspace ends in the wrong state, or whose check does not end ok, is invalid, and an invalid run never counts as a win regardless of how few tokens it spent.
Index ¶
- func AB(baseline, candidate string) (string, error)
- func AgentPrep(bin, dir string, withManifest bool, scenario string) (briefPath, shimPath, nonce string, err error)
- func AgentReport(sc AgentScore) string
- func AggregateReport(labels []string, scores []AgentScore) (string, bool)
- func Fixture(dir string) error
- func GitInstructionViolations(transcript string) []string
- func OutcomeFixture(dir string) error
- func Report(r Result) string
- func Seed(bin, dir string) error
- type AgentScore
- type Result
- type Step
- type StepResult
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AB ¶
AB runs the script against two binaries and reports the deltas. Validity gates the comparison: a candidate that saves tokens and loses validity has lost, and the report says so in one line.
func AgentPrep ¶
func AgentPrep(bin, dir string, withManifest bool, scenario string) (briefPath, shimPath, nonce string, err error)
AgentPrep builds a judge workspace in dir: the seeded v2 fixture, the fixed brief, and the metering shim wrapping bin. It prints nothing — callers report the returned paths.
withManifest prepends the composed connect-time manifest to the brief — the judge then simulates an MCP session, which reads exactly that text at initialize — and records its byte count in a manifest.size sidecar so the scorer can report the session-shaped cost next to the tool diet (T-01KYE3X: whether connect-time guidance BUYS anything is measured, not assumed). Without the flag the brief is byte-identical to before. The returned nonce is the orchestrator's out-of-band anchor (B-01KYEA): every in-workspace artifact can be rewritten consistently by a re-prep, so the only evidence a reset cannot forge is a value held OUTSIDE the workspace since prep time — record it, and pass it back to the scorer.
func AgentReport ¶
func AgentReport(sc AgentScore) string
AgentReport renders one judged run as dense agent lines.
func AggregateReport ¶
func AggregateReport(labels []string, scores []AgentScore) (string, bool)
AggregateReport renders n judged runs: per-run lines prefixed with their label, then one summary. One run cannot carry a verdict — at one build, valid runs have spanned 17-46 calls and 2279-5896 bytes — so the spread IS the signal. Median over mean: a single wanderer must not drag the central figure. AllValid gates the caller's exit code: a text change that makes one judge in three fail is a regression however good the median looks.
func Fixture ¶
Fixture generates a workspace that exercises every state and the tricky shapes: offline git mode (the whole gitflow runs with no network), max_rounds=2 (so one reopen succeeds and the second escalates to blocked), and a small code tree so the graph and gitflow have something real.
func GitInstructionViolations ¶
gitInstructionViolations scans a transcript (concatenated tool results) for text instructing the caller to run git, one violation per match line. GitInstructionViolations is the exported form for other packages' compliance tests (NODE-EDGE-001: rendered hints and protocols must never instruct mechanical git work) — same detector, same calibration.
func OutcomeFixture ¶
OutcomeFixture generates the outcome-scenario workspace: the standard fixture plus the limiter feature brief (TASK.md mirrors the agent brief's feature paragraph) and the offscope trap file. The limiter package deliberately does NOT exist — creating it is the task.
Types ¶
type AgentScore ¶
type AgentScore struct {
Calls int
Bytes int
Tokens int
TaskState string // state of the judge task, "" if absent
BugState string // state of the judge bug, "" if absent
CheckOK bool
Valid bool
// ManifestBytes is the connect-time manifest's size when the run was
// prepped -with-manifest (read from the manifest.size sidecar), zero
// otherwise. Kept OUT of Bytes: the aggregate spreads compare tool
// diets, and folding a per-session constant into them would blur
// exactly the wandering signal they exist to show.
ManifestBytes int
// Scenario is "basic" or "tricky" (from the scenario sidecar); RuleOK
// is the tricky rule goal, DecideOK the tricky decide-answer proof.
Scenario string
RuleOK bool
DecideOK bool
// Disqualified marks a run whose METER failed integrity (B-01KYE6G):
// sequence gap, nonce mismatch, or more journal write events than
// metered calls. Distinct from invalid because the remedies differ —
// invalid means the texts failed the agent, disqualified means the
// measurement failed the harness. DisqualifyReason names the check.
Disqualified bool
DisqualifyReason string
// Violations are role-boundary hits (ROLE-BOUNDARY-001): tool-result
// text instructing the agent to run git, scanned off transcript.log.
// Non-empty kills Valid — a surface that delegates mechanical git back
// to the agent fails the run regardless of cost.
Violations []string
// GoalsOK is the scenario's goal conjunction ALONE — set beside every
// Valid assignment so the report can tell "goals held but violations
// voided the run" from "goals not reached" (B-01KYJ67RF9) without
// re-deriving scoring in the render.
GoalsOK bool
// Outcome scenario (T-01KYFSQQ): hidden-acceptance results as "n/m"
// fractions — FirstPass at the first done edge (or "unavailable" when
// no done edge exists to check out), FinalPass on the tree at scoring
// time — plus the reopen rounds the run paid. Completeness per token
// is computed from FirstPass; Rounds prices the correction loops.
FirstPass string
FinalPass string
Rounds int
// AskCount: exit-0 decide op=ask calls in the meter — the elicitation
// usage the outcome-ask variant measures (T-01KYH6H9).
AskCount int
// contains filtered or unexported fields
}
AgentScore judges a completed run in dir mechanically. Verdict rules: both goal items in their goal states, and the final check E-free (the B-01KYDM taxonomy: W findings are environmental degradations, E findings are failures). Bytes come from meter.log — the agent's whole tool-output diet, including every refusal and retry it needed.
func ScoreAgentRun ¶
func ScoreAgentRun(bin, dir string) (AgentScore, error)
func ScoreAgentRunAnchored ¶
func ScoreAgentRunAnchored(bin, dir, expectedNonce string) (AgentScore, error)
ScoreAgentRunAnchored additionally verifies the out-of-band nonce anchor (B-01KYEA): expectedNonce is the value prep printed, held by the orchestrator OUTSIDE the workspace — the one piece of evidence a full consistent re-prep cannot forge. A mismatch disqualifies before any goal is even considered.
type Result ¶
type Result struct {
Steps []StepResult
Bytes int
Tokens int // Bytes/4 — a stated estimate, not a tokenizer
Valid bool
Violations []string // why Valid is false, or informational notes
Coverage map[string]bool
// ManifestBytes is the initialize-handshake manifest's size, metered
// SEPARATELY from the per-call total: its cost multiplier is sessions,
// not calls, and folding it into Bytes would corrupt both figures
// (T-01KYE3 — 7238 bytes stayed invisible through eleven benchmarked
// landings for lack of this line).
ManifestBytes int
// SchemaBytes is the tools/list payload's size: every tool's description
// and input schema, as the wire delivers it at connect time.
//
// It is metered because it was the largest thing nobody was counting. The
// manifest line presented itself as THE once-per-session cost at 4.3KB,
// while a real handshake writes 24623B — tools/list is 81% of it, measured,
// and the initialize result is ~95% manifest text, so schema+manifest tracks
// the real wire to within 1.2% rather than double-counting. So every
// byte-economy conclusion drawn from this benchmark was reasoning about a
// denominator missing three quarters of what a session actually pays, and
// BENCH-001 ("revert a surface change whose judged metric did not improve")
// is only as trustworthy as the metric it defers to (B-01KYS711ZFFG0).
//
// Measured over the wire rather than by marshalling the tool structs, so it
// cannot drift from what a client receives.
SchemaBytes int
}
Result is the metered outcome of one run.
type Step ¶
type Step struct {
Name string // tool name
Args string // JSON arguments; {ID}, {ID2}, {BID}, {ADR} expand to captured IDs
// Capture stores the first item ID of the result under the given key.
Capture string
// ExpectRefusal marks a step that must exit non-zero with a "! ... E"
// record. Optional marks a step whose outcome is informational only
// (e.g. prefix-ambiguity probes, which depend on mint timing).
ExpectRefusal bool
Optional bool
// Label names the step in the per-step report.
Label string
}
Step is one scripted tool call. Refusals are part of the surface under measurement — their grammar is read by the LLM exactly like success records — so steps that must refuse say so, and a refusal where success was expected (or the reverse) fails validity.
type StepResult ¶
StepResult is one call's metering.