Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.
recordings/B3.5.mp4An unverified claim becomes a shared premise, and a peer message is trusted more than a document it is no safer than.
Schema validation plus an independent verifier before any claim propagates.
Pass a fabricated claim through a schema check and then through a ground-truth verifier.
A tool result re-enters the context as a fact. So does a peer's message. A schema check proves the shape is right and says nothing at all about whether the claim inside it is true.
cybertravels/returns.py, which is the return path: a schema per tool, and an independent verifier that catches a correctly shaped lie — get_booking(2) answering with booking 9. Conformance is a statement about the serialiser; the empty result conforms perfectly.The return path, which every outbound control leaves open: a schema per tool for the shape, and an independent verifier for the content, because a result that conforms perfectly can still answer a question nobody asked. Conformance is a statement about the serialiser.
An unverified claim becomes a shared premise, and a peer's message gets trusted more than a document it is no safer than.
Schema validation plus an independent verifier before any claim propagates.
Results that passed both checks, as a share of results acted on.
Measure this
| what to measure | denominator or target |
|---|---|
| results that passed both checks | share of results acted on |
tool result / peer message
|
v
+------------------+ shape is valid, claim may be false
| schema check | {"status":"ok","rows":0} <- conforms perfectly
+--------+---------+
v
+------------------+ the claim, checked against something independent
| verifier | did the row actually appear in the database?
+------------------+
conformance is about the serialiser. accuracy is the expensive part.Mitigates: T5 Cascading Hallucination · T12 Communication Poisoning · T7 Misaligned Behaviour.
Everything that comes back into the context is an input: tool results, peer messages, retrieved documents, a sub-agent's summary. B1.10 and B1.12 both happened because those inputs were trusted in proportion to how internal they looked rather than to how checked they were.
Two different checks, and conflating them is the mistake:
Schema validation asks is this the right shape. Cheap, mechanical, catches malformed input and injection through a field that was supposed to be an integer. It is necessary and it proves nothing about truth — a perfectly-formed JSON object can assert anything.
Verification asks is this claim true, according to something that is true independently of the agent. A test that passes. A query whose result you can re-run. A file that exists. A signature that checks.
The rule that follows: a claim may not propagate past the hop that produced it without a verification result attached. Not "was it plausible" — was it checked, by what, and what did that return.
That single field is what stops B1.12's cascade, because confidence can no longer rise as evidence disappears: the evidence field travels with the claim, and an empty one is visible at every hop.
It is also the answer to B1.16, which is why "ask the model whether it succeeded" is not a verifier — it is the same component grading its own work.
What this control closes. Stops a claim propagating without evidence attached. Schema validity is not truth: a well-formed object can assert anything.
{"status":"refunded"} is well-formed and may be false. The skill checks four returns twice — schema, then an independent oracle — and confirms that a claim with no oracle stops as unverifiable rather than quietly becoming true.
The skill — skills/runtime/tool-return-validation-check/SKILL.md
name: tool-return-validation-check
description: >-
Check what a tool's return value is validated against before the agent acts on
it — schema, then an independent oracle — and confirm that an unverifiable
claim stops rather than propagating. Use when a tool's output becomes an
agent's belief, or when reviewing multi-hop reasoning.
allowed-tools: Read, Grep, GlobA tool return is untrusted input that arrives wearing the tool's authority. Two checks are needed and they catch different things: a schema catches malformed, and an oracle catches confidently wrong. Systems usually have the first and treat it as if it were the second.
Any agent that acts on what a tool told it, and any pipeline where one step's output is the next step's premise.
1 — Define the schema per tool return. Types and required fields. This is the cheap check and it should be automatic; a return that fails it never reaches the model.
2 — Identify the oracle for each claim type. Something independent that can say true or false: a CVE database, a build, a test run, a second source. Not another model — a model checking a model measures agreement, not truth.
3 — Run the four cases. Schema-perfect and true; schema-perfect and false; schema-perfect with no oracle available; malformed. All four have to be distinguishable in the output.
4 — Make "unverifiable" a terminal state. The third case is the one that matters. A claim with no oracle must stop as unverifiable rather than defaulting to true — silent promotion is how a hedge becomes a fact three hops later.
5 — Confirm only verified claims propagate. Follow each case for several hops and record which survive. The unverified ones surviving is the finding.
Input — the fixture committed at the top of scripts/tool_return_validation_check.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.
Output — the opening lines of a real run:
schema_ok=True -> {'stopped': 'refuted', 'claim': 'libfoo has no known CVEs', 'by': 'checked against the advisory database'}
schema_ok=True -> {'propagated': 'test_login passes', 'verified_by': 'checked against the advisory database', 'hops': 3}
schema_ok=True -> {'stopped': 'unverifiable', 'claim': 'the refund was approved', 'why': 'no oracle for this claim'}
schema_ok=False -> {'stopped': 'malformed'}
The first message is schema-perfect and confident and false. Schema
validation passed it; the oracle refuted it.
The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.
{
"tools": [{"name": "str", "schema": true, "oracle": "str|null"}],
"cases": [{"claim": "str", "schema_ok": true, "oracle_verdict": "true|false|unavailable",
"state": "verified|refuted|unverifiable|malformed", "propagated": false}],
"unverifiable_is_terminal": true
}
Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.
The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.
Install it, once. From the folder you cloned. --all does every agent it finds on your machine:
python3 scripts/install_skills.py --all --lessons B3.5Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.
Then pick b3-5-validating-what-comes-back:
| agent | install just for this agent | start it |
|---|---|---|
| Claude Code | python3 scripts/install_skills.py --tool claude --lessons B3.5 | type /b3-5-validating-what-comes-back |
| GitHub Copilot (VS Code) | python3 scripts/install_skills.py --tool copilot --lessons B3.5 | type /b3-5-validating-what-comes-back in Chat |
| Cursor | python3 scripts/install_skills.py --tool cursor --lessons B3.5 | type / in Agent chat and search for it |
| Codex | python3 scripts/install_skills.py --tool agents --lessons B3.5 | type $b3-5-validating-what-comes-back, or run /skills |
No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.
python3 scripts/lesson.py B3.5Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.
python3 scripts/checkpoint.py --at B3.5 --out work/cybertravels
python3 scripts/checkpoint.py --at B3.5 --diffOn Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.
Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.
Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.
If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.
Four messages are checked twice. A schema-perfect, high-confidence claim is refuted by the oracle; a claim with no oracle stops with unverifiable rather than silently becoming true; a malformed message is caught by the schema; and only the verified claim propagates.
Find one place a sub-agent's output becomes another agent's input and ask what oracle checks it. If the answer is the model's own confidence, that is the component grading its own work.
Tools used
Skill source: skills/runtime/tool-return-validation-check