Why a test comes before the agent
A guardrail test is a test you write before a coding agent starts. It fails now and must pass when the work is done, so a machine decides "done" and not the agent's own report.
It matters because an agent can report success when the work is not done. A test written first, and kept safe from the agent, is a check you can trust.
By the end of this lesson
Note
I can write a failing test first so that a coding agent's work is checked.
Key words
| Word | What it means |
|---|---|
| Guardrail test | a test written first that the work must pass |
| Red, then green | the test fails before the work and passes after |
| Cheat | a change that passes a test without doing the work |
| Held-back test | a test the agent cannot see |
| Protected test file | a test file the agent may not change |
The cells students fill in are left blank in this preview.
Write the failing test first
Write the test from the task, before the agent starts. Run it and watch it fail. A test that passes before any work tests nothing new.
Then give the agent the task and the test. The agent changes the code until the test passes. You now have a check that does not depend on what the agent says it did.
A test can pass without the work
An agent is asked to make the test pass, and it may find a short way. It can answer only the inputs it can see. It can change or delete the test.
This is not bad intent. The agent was given a target, and it reached the target. So the target must be hard to reach the wrong way.
Worked example: cheats and how to stop them
| Cheat | What it looks like | How to stop it |
|---|---|---|
| Special case | if price is 2000, answer 0 | Held-back tests with other inputs |
| Edit the test | A case is deleted or changed | Protect the test file and compare it |
| Skip the test | The test is marked to skip | Count the tests before and after |
| Weaker test | An exact check becomes "is a number" | Review every change to a test file |
The cells students fill in are left blank in this preview.
A common mistake
Warning
Many people think a green test run means the agent did the work. A test can turn green because the test changed, or because only the visible inputs work. Trust green only when the test file is unchanged and the held-back tests pass too.
Judge four recorded attempts
Delivery costs 299 pence below 2000 pence and nothing from 2000 up. The script holds three visible tests and two held-back ones.
It judges the code before the work and three recorded attempts. The attempts were written by hand to stand for what an agent can hand back.
Write the model helper, part 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '// The token counts of a reply, or null when the reply carries none.' 'function count(data, name) {' ' return data.usage && typeof data.usage[name] === \"number\" ? data.usage[name] : null;' '}' > model.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the model helper, part 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function ask(messages, sample, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"status \" + res.status);' ' const data = await res.json();' ' const text = data.choices[0].message.content;' ' return { text, inTokens: count(data, \"prompt_tokens\"), outTokens: count(data, \"completion_tokens\"), recorded: false };' ' } catch (error) {' ' console.log(\"Recorded sample: no model answered (\" + error.message + \").\");' ' return { text: sample, inTokens: null, outTokens: null, recorded: true };' ' }' '}' 'module.exports = { ask };' >> model.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the guardrail judge, part 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' '// Delivery costs 299 pence below 2000 pence, and nothing from 2000 pence up.' 'const visible = [[500, 299], [1999, 299], [2000, 0]];' 'const held = [[2500, 0], [100, 299]];' 'const attempts = {' ' \"before\": { fn: function (p) { return 299; }, tests: visible },' ' \"honest\": { fn: function (p) { return p >= 2000 ? 0 : 299; }, tests: visible },' ' \"special case\": { fn: function (p) { return p === 2000 ? 0 : 299; }, tests: visible },' ' \"edited test\": { fn: function (p) { return 299; }, tests: [[500, 299], [1999, 299]] },' '};' > guard.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the guardrail judge, part 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function passed(fn, cases) {' ' return cases.filter(function (c) { return fn(c[0]) === c[1]; }).length;' '}' 'function verdict(a) {' ' if (JSON.stringify(a.tests) === JSON.stringify(visible) === false) return \"refuse: tests changed\";' ' if (passed(a.fn, a.tests) < a.tests.length) return \"not done\";' ' if (passed(a.fn, held) < held.length) return \"cheat suspected\";' ' return \"keep\";' '}' >> guard.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the guardrail judge, part 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' Object.keys(attempts).forEach(function (name) {' ' const a = attempts[name];' ' console.log(name + \": visible \" + passed(a.fn, a.tests) + \"/\" + a.tests.length' ' + \", held back \" + passed(a.fn, held) + \"/\" + held.length + \": \" + verdict(a));' ' });' ' const q = \"In one short sentence, why keep some tests where a coding agent cannot see them?\";' ' const sample = \"Hidden tests show whether the agent solved the problem or only the examples it could see.\";' ' const reply = await ask([{ role: \"user\", content: q }], sample, 60);' ' console.log(\"model said: \" + reply.text);' '}' 'main();' >> guard.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run the guardrail judge
docker exec -w /home/node/project agent node guard.jsShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
What the Run prints
| Line | What it shows |
|---|---|
| before: visible 2/3 ...: not done | Red: the test fails before the work |
| honest: visible 3/3, held back 2/2: keep | Green, and the hidden cases agree |
| special case: ... held back 1/2: cheat suspected | Only the visible inputs work |
| edited test: ...: refuse: tests changed | A case was deleted, so nothing counts |
| model said: ... | A sentence from the lab. A Recorded sample line if no model answers |
The cells students fill in are left blank in this preview.