Put the pieces in one loop
An agent harness is the program around the model: it builds the window, runs the tools, applies the mode and records the changes. The model only proposes a step. The harness decides what happens.
This lesson joins the five earlier ones into one loop you can run and rewind.
By the end of this lesson
Note
I can put context, tools, modes, isolation and undo into one harness that I can run and rewind.
Key words
| Word | What it means |
|---|---|
| Harness | the program that runs an agent around a model |
| Loop | ask for a step, decide, act, record, repeat |
| Scripted model | a fixed list of steps that stands in for a model |
| Transcript | the record of each step and its outcome |
| Window | the recent results sent to the model next |
| Rewind | undoing every change after a chosen step |
The cells students fill in are left blank in this preview.
The loop, step by step
Each turn the harness asks the model for one step. It decides by mode: allow, ask or deny. If it must ask, the user answers. If the step is allowed, the harness logs what it will replace, then does it.
The result goes into a window, which keeps only the latest few. Then the loop repeats until the model says it is done.
What each earlier lesson adds
| Lesson | Where it sits in the loop |
|---|---|
| Context | The window keeps only the latest results |
| Tools | Each step names a narrow tool |
| Modes | One function decides allow, ask or deny |
| Isolation | A side task returns a short result into the window |
| Undo | A log entry is written before a change |
The cells students fill in are left blank in this preview.
A common mistake
Warning
Many people let a refused step vanish. The model then never learns it was refused and asks again. Record the refusal as a result, so the next window shows it.
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 harness, part 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const RISK = { read_file: \"read\", write_file: \"write\", delete_file: \"destructive\", run: \"run\" };' 'const script = [' ' { tool: \"read_file\", path: \"app.py\" },' ' { tool: \"write_file\", path: \"app.py\", text: \"x = 2\" },' ' { tool: \"run\", path: \"pytest\" },' ' { tool: \"delete_file\", path: \"notes.txt\" },' ' { tool: \"write_file\", path: \".env\", text: \"KEY=1\" },' ' { tool: \"done\" },' '];' 'const files = { \"app.py\": \"x = 1\", \"notes.txt\": \"todo\" };' 'const log = [];' > harness6.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the harness, part 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function decide(mode, risk, path) {' ' if (mode === \"plan\") return risk === \"read\" ? \"allow\" : \"deny\";' ' if (path.startsWith(\".env\") || risk === \"destructive\") return \"ask\";' ' if (mode === \"auto\" && (risk === \"read\" || risk === \"write\")) return \"allow\";' ' if (mode === \"ask\" && risk === \"read\") return \"allow\";' ' return \"ask\";' '}' >> harness6.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the harness, part 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function act(s, n) {' ' if (s.tool === \"read_file\") return s.path in files ? files[s.path] : \"missing\";' ' if (s.tool === \"run\") return \"ran \" + s.path;' ' log.push({ step: n, path: s.path, before: s.path in files ? files[s.path] : null });' ' if (s.tool === \"write_file\") { files[s.path] = s.text; return \"wrote \" + s.path; }' ' delete files[s.path];' ' return \"deleted \" + s.path;' '}' >> harness6.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the harness, part 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function runAll(mode, approvals) {' ' const out = [];' ' for (let i = 0; i < script.length; i++) {' ' const s = script[i];' ' if (s.tool === \"done\") break;' ' const v = decide(mode, RISK[s.tool], s.path);' ' let outcome = v === \"allow\" ? \"ok\" : \"denied\";' ' if (v === \"ask\") outcome = approvals.shift() === true ? \"approved\" : \"refused\";' ' const good = outcome === \"ok\" || outcome === \"approved\";' ' out.push({ step: i + 1, tool: s.tool, path: s.path, outcome: outcome, result: good ? act(s, i + 1) : outcome });' ' }' ' return out;' '}' >> harness6.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the harness, part 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function undoTo(step) {' ' for (const e of log.slice().reverse()) {' ' if (e.step <= step) continue;' ' if (e.before === null) delete files[e.path];' ' else files[e.path] = e.before;' ' }' '}' >> harness6.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the harness, part 6
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const out = runAll(\"auto\", [true, false]);' ' for (const o of out) console.log(o.step + \" \" + o.tool + \" \" + o.path + \" \" + o.outcome + \" -> \" + o.result);' ' console.log(\"window \" + JSON.stringify(out.slice(-3).map(function (o) { return o.result; })));' ' console.log(\"files \" + JSON.stringify(files));' ' undoTo(1);' ' console.log(\"after undo to 1 \" + JSON.stringify(files));' ' const text = out.map(function (o) { return o.tool + \" \" + o.outcome; }).join(\", \");' ' const reply = await ask([{ role: \"user\", content: \"Write a one line commit message for a run that did this: \" + text }], \"Fix the basket total in app.py\", 30);' ' console.log(\"message: \" + reply.text);' '}' 'main();' >> harness6.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run the harness and rewind
docker exec -w /home/node/project agent node harness6.jsShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
The model is a script
The steps come from a fixed list, so every student prints the same run. A real harness would ask the model for each step. The one short live call only writes a commit message from what the run did.
With no model it prints a Recorded sample for that line.
One possible output
| Line | What it shows |
|---|---|
| 1 read_file app.py ok -> x = 1 | A read passes in auto mode |
| 3 run pytest approved -> ran pytest | The first approval was yes |
| 4 delete_file notes.txt refused -> refused | The second approval was no |
| 5 write_file .env refused -> refused | No approval was left, so no |
| files {"app.py":"x = 2","notes.txt":"todo"} | Only the write changed anything |
| after undo to 1 {"app.py":"x = 1",...} | The rewind put the write back |
The cells students fill in are left blank in this preview.