A Review Found Our Approval Gate Could Be Forged by the Agent It Gated
Four agents flagged a synthetic cath-lab case packet in one Switch room, and a human chair had to click Allow before a fifth agent could write any PDF. A pre-submission review found the agent itself could have faked the Allow record. What we fixed, what we could not, and the numbers.
Summary
We built a hackathon entry on Switch: five Claude Code agents in one Mattermost room. Four specialists read a synthetic cath-lab case packet, flag only what they can quote, and hand work to each other. A human chair rules. The fifth, a Clerk, writes a plan PDF only after the chair presses Allow once on an owner-only approval card. Three days of live runs came out clean. A multi-reviewer pass the night before submission then found that the Allow record, the one thing that gates the PDF, could have been forged by the Clerk itself. All data is synthetic. This is not clinical advice and not a medical device.
What we built
| Piece | What it does |
|---|---|
| Imaging, Labs, Meds, Equipment | Each flags only values it can quote from the packet. None decides, doses, substitutes or reschedules. |
| Relay | The chair names two agents. Imaging hands to Equipment, Labs hands to Meds, one hop, via Switch's teammate handoff. |
| Clerk | Records the chair's rulings, drafts a board, and writes a plan PDF only after an approval card is allowed. |
| Approval card | Owner-only. Allow once writes the file. Decline writes nothing. |
| Scorer | A script that checks every brief for shape, forbidden words, number grounding and the planted conflicts. |
Results
| Set | Runs | Briefs | Misses |
|---|---|---|---|
| Final prompts, five cases | 10 | 200 | 0 |
| Held-out packet set, day 2 | 3 | 60 | 0 |
| Held-out packet set with decoys, day 3 | 3 | 60 | 0 |
| Six packet values swapped | 1 | 4 | each change showed up in the right agent's brief |
| End-to-end dry run with real Allow and Decline clicks | 1 | 35 checks | 35 passed |
These numbers are honest and smaller than they look. Repeats give identical text, so the 200 briefs are 20 distinct ones seen ten times. The scorer and the answer keys have the same author. The thresholds are proposals no clinician has verified.
Gotchas from the review
The approval record could be forged. The recorder built its approval log from the text of posts made by the Clerk's account. The Clerk is a model that reads untrusted packet text. A card-shaped post from it, with an Allow and the chair's name in it, would have been logged as a real approval, and the plan would have been stamped signed. Nothing had gone wrong. The fix was to count only posts that Switch itself tags as permission cards, a property the Clerk's own post tool cannot set. We also read the chair's name from the last matching line, not the first, and recorded rulings from the chair's own message instead of the Clerk's echo. What we cannot close in code: the Clerk runs as the same OS user as the author, so the stamp is a record check, not a signature, and the write-up says so.
A scorer that passed wrong answers. A reviewer built briefs that violated the rules and scored zero misses: a "good to go" after a check mark, prose inside a MISSING: line, look-alike letters in a forbidden word, a wrong vessel on a lesion id. We tightened the scorer, added twelve adversarial cases, and rescored all 1,120 stored briefs. All still passed, so the tightening had no false positives on real output.
"Multivessel" meant "two lesions". Imaging flagged a proximal LAD lesion plus a diagonal as multivessel disease. Those are one coronary territory. We changed the definition to territories, updated the scorer, and reran everything. The old answer key now scores nine of the earlier briefs as misses, which is the error showing up as designed.
The published cutoffs were not eGFR. The contrast-to-kidney-function ratio cutoffs come from studies that used creatinine clearance. The plan divided by eGFR. It now shows a Cockcroft-Gault ratio beside the eGFR one and applies the cutoffs to the higher.
Changing a provider changed everything. One agent's provider hit a weekly usage limit the day before filming. We moved it to Claude Code. Because the provider is part of the prompt hash, every previous result stopped counting for the new build, and we reran the streak, both held-out sets and the dry run.
Switch rough edges. The "Worked for N s" box cannot be hidden. Approval cards lose their buttons above the 4,000-character post limit, so commands have to be one short line. Button clicks need a local callback forwarder that fails silently. The controller socket dropped when the machine slept, and a fresh room's agent sometimes answered without reading the room.
Key takeaways
- An approval gate is only as strong as the source of the approval record. If the gated agent can write to that source, the gate is theatre.
- Ask a reviewer to try to pass a wrong answer through your scorer. Ours did, in minutes.
- Count distinct outputs, not runs. Identical repeats are not independent samples.
- A definition buried in a prompt, like "multivessel", is a clinical claim. Check it against the source.
- Put the provider in the prompt hash. A silent harness swap should reset your streak.
- The demo video and the repository are linked from the hackathon submission.
Everything in these notes I also do for hire: local AI set up on hardware you own, configured on-site, then handed over with enough documentation that you do not need me afterward. If that sounds more useful than another weekend of reading forum threads, the details are at /work. No obligation from an email, and the posts stay free either way.