Promote the experiment probe into the package behind agentseam probe - #145
Conversation
`tools/experiment.py` and friends measured what an agent's hooks actually enforce, but never shipped: `packages.find` reads only `src`, so a `pip install agentseam` user got the matrix without the means to check whether their own machine agrees with it (armed, wave 1, T1/T2). Moves the harness verbatim into `src/agentseam/probe/` (experiment, experiment_probe, experiment_driver, experiment_escalate, experiment_report, recorded_driver, reference_agent) with only import paths and a `_PACKAGE_ROOT` derivation changed -- the latter because the rendered subprocess probe can no longer assume a "../src" checkout layout once it runs from a wheel. Verified against a built wheel in a clean venv with no source checkout on sys.path. `tools/*.py` at the old paths become thin shims (`from agentseam.probe.X import name as name`, including the underscore-prefixed names the test suite reaches into directly) so nothing imports a second copy of the harness. `main()`'s own argparse CLI split out to experiment_cli.py to keep experiment.py under the 300-line review budget. Adds the `probe` verb (`agentseam probe list` / `run`), wired in src/agentseam/probe/cli.py per cli.py's own `add_parser` + `set_defaults(fn=)` idiom, sharing the identical engine tools/experiment.py's own CLI calls -- `probe` was chosen over "armed" itself since the CLI already speaks in verbs (agents, matrix, doctor, install), and the direction doc's project name reads oddly as a verb. Promoting into src/ puts the harness under the strict lint baseline for the first time; fixes review-budget line splits, explicit `check=False` on every subprocess.run, and a G1/G2 helper split in reference_agent's `_interpret` (same logic, lower complexity), plus per-file-ignores for the print-based CLI output and pre-existing, widely-tested exception names (NoRecording, Undocumented) that would otherwise churn every caller for no behavioural gain. `permissionDecisionReason` crossing into src/ for the first time also crossed the literal-duplication threshold against the two adapters that already name it; allowlisted with a reason (three independent, deliberately un-shared readers of the same wire-protocol key, not an accidental repeat). Also adds `agentseam.recordings.agents()` (every agent with a committed recording), needed by the CI gate in a following commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com>
The recorded witness table covers claude_code, pre_tool only, minus escalate (armed, wave 1, T3). A cloud worker holds no vendor credentials and cannot produce a witnessed row (contract, "The constraint that shapes every brief") -- this is the command lines and report shape for whoever can. docs/witness-skeleton.json names the three gaps (escalate at pre_tool, every trial at prompt_submit, every trial at stop), each with the exact `agentseam probe run ... --record --report` command and an evidence-report skeleton shaped like that command's own --report output, with the owner-dependent fields (basis, date, version) left blank on purpose. tests/test_coverage_skeleton.py asserts every skeleton block, exactly as committed, is REJECTED by evidence_report.validate() -- so a block nobody has filled in can never be mistaken for a witnessed one -- and that the same block validates once genuinely filled in, proving the blanks are the only thing standing between it and real evidence. docs/coverage-gaps.md, README.md and CONTRIBUTING.md spell out witnessed/tested/recorded as a closed, three-way vocabulary tied to a report's own `driver` and `basis` fields, so a reader can tell them apart without opening evidence_report.py (T5). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com>
The matrix's claims were never defended in CI: a change could make a `recorded` row untrue and nothing would say so (armed, wave 1, T4). tools/probe_ci.py replays every committed data/recordings/<agent>@<version> recording, at every event and trial it covers, through the recorded driver and diffs the result against src/agentseam/data/matrix.json. Exits 1 on any DISAGREES row. Modeled on tools/watch_versions.py's own shape, but the opposite intent: staleness.yml explicitly never fails the job (drift is a fact about the world, not a defect in the PR); this one exists specifically to fail the job, because a DISAGREES row here means the change under review made a claim untrue, not that the vendor moved. Reports the trial count checked alongside the disagreement count either way -- a run with nothing committed to check prints "0 trial(s) checked ... this is a measurement of nothing, not a pass" rather than a bare "0 disagreements" that would read as a clean bill of health (contract invariant 4). tests/test_probe_ci.py asserts the gate actually agrees with what is committed today, and separately breaks it on purpose (a monkeypatched lying diff) to prove a real disagreement is reported loudly and returns 1, not swallowed. .github/workflows/probe.yml runs it on push, on PRs touching the probe or its data, and on demand; `set -o pipefail` so a disagreement's exit code survives the `tee` into the step summary. Verified by hand: flipping claude_code's pre_tool `block` claim to False in a scratch copy of matrix.json makes both `agentseam probe run` and tools/probe_ci.py report a DISAGREES row, the latter exiting 1; reverting the file returns both to a clean, agreeing run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com>
render() counted disagreements, printed "the matrix and this agent do not match", and returned 0. Harmless while this was a dev script under tools/; not harmless now that the same code is a shipped `agentseam probe run`. A verb that reports a mismatch and tells its caller it succeeded is the exact shape this probe exists to catch in other people's tooling. Until now only tools/probe_ci.py gated on the result. Both callers -- the new verb and tools/experiment.py's own CLI -- get the honest exit code. Two tests pin the pair: DISAGREES exits 1, agreement stays 0, so the gate cannot start crying wolf either. Reproduced before and after against the committed recording with claude_code.events.pre_tool.block flipped: exit 0 -> exit 1, agreement 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com>
|
Promotion into src/ put four pre-existing shell=True calls under agentseam-no-shell-true for the first time: security.yml scans src/ and tests/, never tools/, so the rule had not seen this code. On main, src/ has no shell=True and tools/ has exactly these four. The rule is working; the code is not new. Its prescribed fix -- pass an argv list -- would be wrong here. reference_agent is Claude Code's protocol made executable, and Claude Code runs a hook by handing a command string to a shell; an argv list would emulate a vendor that does not exist, and every tested-basis row is measured against this driver. The transform trial runs the command as the hook rewrote it, so measuring the rewrite means running the rewrite. So the exception is real, and now lives in one place instead of four: _shell.py's run_shell() carries the single nosemgrep and the trust-boundary rationale beside it. The rule stays global and unmodified. Verified narrow: a canary shell=True added elsewhere in the same package still fails semgrep, and passes again once removed. semgrep exit 0 on CI's own invocation; 1615 passed, 4 skipped; ruff and ruff format clean. Probe re-checked end to end: agreement exits 0, a forced disagreement prints DISAGREES and exits 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Claude <noreply@anthropic.com>
Fixed at
|
What this changes
The eight-trial probe moves out of
tools/intosrc/agentseam/probe/and gets a CLI verb, so apip install agentseamcan measure what its own agent's hooks actually enforce — previously the harness existed but shipped to nobody. Adds a CI gate that replays committed recordings and fails on disagreement, and a documented basis vocabulary (witnessed / tested / recorded).Wave 1 of
armed(idea 01 of the portfolio direction); brief and contract in org-plan atplan/armed/, pinned at83af785.Claim check
MATRIXrow carries averifiedrecord — no matrix row is added or changed by this PRNo row is labelled
witnessedhere, and no measurement is synthesized. The coverage the evidence actually supports is unchanged: seven trials, atpre_tool, for one agent. The three gaps (escalateatpre_tool, all trials atprompt_submit, all trials atstop) ship asdocs/witness-skeleton.json— copy-paste commands plus a blank report skeleton — withtests/test_coverage_skeleton.pyasserting the skeleton is rejected byevidence_report.validate()while still blank, and validates once genuinely filled. Only the owner's machine can fill them; this branch was built with no vendor credentials.Checks
pytest -qpasses —1615 passed, 4 skipped(3.11 and 3.13)ruff check .andruff format --check .pass —All checks passed!/147 files already formatteddependencies = []untouched, no new imports outside the standard libraryNotes for the reviewer
One defect found and fixed, and it is the one worth looking at.
experiment_report.render()counted disagreements, printed "the matrix and this agent do not match", and thenreturn 0. Harmless while it was a dev script; not harmless the moment the same code becomes a shippedagentseam probe run. A verb that reports a mismatch and tells its caller it succeeded is the exact shape this probe exists to catch in other people's tooling. Reproduced against the committed recording withclaude_code.events.pre_tool.blockflipped — exit 0 before, exit 1 after, agreement still 0 — with both directions pinned by tests so the gate cannot start crying wolf.Two other things were deliberately made loud rather than convenient.
tools/probe_ci.pyreports the checked count beside the disagreement count and printsno recordings are committed -- this is a measurement of nothing, not a passwhen there is nothing to check, so an empty run cannot read as green.agentseam probe run --driver recordedwith no--trialfilter raisesNoRecording: claude_code@2.1.263/pre_tool never recorded the 'escalate' trialrather than quietly reporting seven of eight — correct, but it means the default recorded invocation fails untilescalateis witnessed. Worth a decision: fail loudly as now, or report per-trial coverage with the unrecorded ones marked absent.Naming is open.
probewas chosen over "armed" because the CLI already speaks in verbs (agents,matrix,doctor,install) — the initiative's name reads oddly as one. Owner's call.Promotion into
src/put the harness under the strict lint baseline for the first time. That was handled by fixing rather than ignoring: modules split by activity to stay inside the line budget, explicitcheck=Falseon everysubprocess.run, a complexity split inreference_agent._interpretreusing existing tests unchanged.per-file-ignoreswere added only for pre-existing load-bearing shapes, each with a stated reason. One real correctness bug surfaced on the way: the generated probe script assumed a../srccheckout layout to findagentseam, which is false in an installed wheel — now derived fromagentseam.__file__and verified by running a trial end to end from a built wheel in a clean venv with no source checkout onsys.path.🤖 Generated with Claude Code
Generated by Claude Code