Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

How to plan and stop an AI agent

After this lesson: I can make an agent plan its steps and stop on a rule.

60 min lesson6 commandsAI coding agent lab: use opencode to explain and fix code, and check its work

The agent that never finished

An AI agent is a loop in which a model chooses its own next step. Because nothing tells it when to quit, an agent needs a plan and a rule for stopping.

A support agent retries a failing lookup all night. Every retry costs money and none can succeed. The loop was correct. The stop rule was missing.

By the end of this lesson

Note

I can make an agent plan its steps and stop on a rule.

Key words

WordWhat it meansExample
Agenta loop where a model picks the next step
Planthe steps an agent intends to take, as data
Step budgetthe most steps one run may take3
Stop rulea test that ends the loop
Outcomethe reason a run ended

The cells students fill in are left blank in this preview.

A plan is data, not a feeling

Ask the model for a plan as a JSON list, then treat it as data. Your program can count it, check each step and show it to a person before anything runs.

A plan is a proposal. The model may return a list with the wrong shape or a step no tool can do. Check the plan before you follow it.

Worked example: a plan and its run

StepPlan saysTool resultRun state
1look up appleapple costs 40pok
2look up pearpear costs 45pok
3look up kiwino tool fitsfail
4report the pricesnot reachedbudget or stuck decides

The cells students fill in are left blank in this preview.

Write the model helper, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' '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 || 200 }),' '      signal: AbortSignal.timeout(30000),' '    });' '    if (res.ok === false) throw new Error(\"status \" + res.status);' '    return (await res.json()).choices[0].message.content;' > 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

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' '  } catch (error) {' '    console.log(\"Recorded sample: no model answered (\" + error.message + \").\");' '    const list = [].concat(sample);' '    const turn = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' '    return list[Math.min(turn, list.length - 1)];' '  }' '}' '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 planning agent, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const PRICES = { apple: 40, pear: 45, bread: 120 };' 'const messages = [' '  { role: \"system\", content: \"You reply with JSON only.\" },' '  { role: \"user\", content: \"Plan how to find the price of an apple, a pear and a kiwi. Reply with a JSON array of 3 short strings, one item to look up each. Reply with only the JSON.\" },' '];' '// Recorded sample, used only when no model answers.' 'const sample = JSON.stringify([\"apple\", \"pear\", \"kiwi\"]);' > plan1.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the planning agent, part 2

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function step(text) {' '  const key = Object.keys(PRICES).find(function (k) { return text.toLowerCase().indexOf(k) >= 0; });' '  if (key === undefined) return { ok: false, note: \"no tool fits: \" + text };' '  return { ok: true, note: key + \" costs \" + PRICES[key] + \"p\" };' '}' >> plan1.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the planning agent, part 3

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  const reply = await ask(messages, sample, 100);' '  let plan = null;' '  try { plan = JSON.parse(reply.slice(reply.indexOf(\"[\"), reply.lastIndexOf(\"]\") + 1)); } catch (error) { }' '  if (Array.isArray(plan) === false) { console.log(\"Not a list, using a fixed plan.\"); plan = [\"apple\", \"pear\"]; }' '  const budget = 3, maxFails = 2;' '  let used = 0, fails = 0, outcome = \"done\";' '  for (const text of plan) {' '    if (used === budget) { outcome = \"budget\"; break; }' '    used += 1;' '    const r = step(String(text));' '    fails = r.ok ? 0 : fails + 1;' '    console.log(\"Step \" + used + \": \" + r.note);' '    if (fails === maxFails) { outcome = \"stuck\"; break; }' '  }' '  console.log(\"Outcome: \" + outcome + \" after \" + used + \" steps\");' '}' 'main();' >> plan1.js"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Run the agent and read the outcome

Command
docker exec -w /home/node/project agent node plan1.js

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

One possible output

LineWhat it may show
Step 1 and 2The prices of the apple and the pear
Step 3no tool fits: kiwi
Outcomedone after 3 steps, because one failure is not two

The cells students fill in are left blank in this preview.

Three ways a run ends

A run can end because the work is done, because the agent is stuck, or because the budget ran out. Each outcome is a different message to the user and a different line in the log.

Check them in that order. A run that finishes on its last allowed step is done, not over budget. Stuck means repeated failure with no progress.

A common mistake

Warning

Many people think a budget alone is a stop rule. A budget only caps the cost. It cannot tell a finished run from a stuck one. Keep a budget as the last resort and add rules for done and stuck.

About this lab and its source: AI coding agent lab: use opencode to explain and fix code, and check its work

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.