Files
ECC/docker/gateguard-effectiveness
..

GateGuard question effectiveness

Development-only harness that runs real Claude sessions on trapped coding tasks with and without GateGuard's questions. Design, arms, measures and results: docs/gateguard/question-effectiveness.md.

Two runners, measuring different things.

run.js — cost baseline. One-shot autonomous sessions over scenarios/, where each trap hides its deciding fact in the repository. The pilot returned no outcome difference between any arm, so treat this as a measure of the gate's turn and token cost, not of its effect.

node docker/gateguard-effectiveness/run.js --out gg-eff --dry-run
node docker/gateguard-effectiveness/run.js --out gg-eff --model <model> --allow-real-provider

run-intent.js — effectiveness. Multi-turn sessions over scenarios-intent/, where the deciding fact exists only in a simulated user's hidden intent, so an agent that does not ask can only guess. Scenario files omit declared gate question targets, but this does not establish blind or independent authorship: the authors knew the taxonomy, and the external-contract question was added after inspecting this corpus. Treat those results as exploratory. That question is now opt-in (GATEGUARD_EXTERNAL_CONTRACT_QUESTION), and the runner clears every GATEGUARD_* variable from the agent's environment, so a gate arm built from a current candidate tree runs without it. The report classifies behavior into descriptive hole categories.

Each run writes meta.json, raw results.jsonl, transcripts, summary.json, holes.md, and evidence.json. The evidence manifest fingerprints the frozen scenario and harness inputs and hashes the output artifacts. Verify an output directory with --verify; this detects later file changes but is not a cryptographic signature or proof of who ran the experiment.

run-intent.js --check-graders, and each intent run before it bills a session, requires each grader to score reference 1 and the start workspace, the naive trap and every decoy-* overlay below 1. A decoy has the right shape and the wrong content, so a grader that checks only the form fails the check. Both runners count only valid trials: authentication-related provider errors stop the run before the attempt is appended to results.jsonl. The transcript is retained, but there is no attempt record. Other provider-error or timeout attempts stay in results.jsonl with their own transcripts, are reported apart, and are retried on resume. A gated trial whose hook never ran also stops the run without a results.jsonl record.

node docker/gateguard-effectiveness/run-intent.js --check-graders
node docker/gateguard-effectiveness/run-intent.js --out gg-intent --arms off,gate --dry-run
node docker/gateguard-effectiveness/run-intent.js --out gg-intent --model <model> --allow-real-provider
node docker/gateguard-effectiveness/run-intent.js --out gg-intent --summarize
node docker/gateguard-effectiveness/run-intent.js --out gg-intent --verify

The one-shot run.js runner serializes an output directory with .run.lock; concurrent workers must use separate --out directories. Normal completion and errors release the lock. After a killed process, confirm that its worker has stopped before removing the stale lock and resuming. Metadata uses private exclusive temporary creation and atomic rename so a destination symlink cannot redirect metadata writes.

Hidden-intent trials retain the selected Claude configuration directory and pass --claude to both the agent and simulated user. Session cleanup removes only new project folders matching workspaces observed inside that trial's temporary root; pre-existing folders and concurrent runs remain. Shared project-name overrides remain untouched because they do not establish exclusive trial ownership. arm-patch.js is included in frozen harness fingerprints, so changes to ablations require a new output directory rather than continuing an existing experiment.