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 multi-agent systems fail

After this lesson: I can find the failure in a transcript and name its kind.

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

A team fails in ways one agent cannot

A multi-agent failure mode is a way a team of agents goes wrong that no single agent would. It matters because the fault is in the conversation between them, so each agent looks fine alone.

You find it by reading the transcript, the list of messages they sent.

By the end of this lesson

Note

I can find the failure in a transcript and name its kind.

Key words

WordWhat it means
Transcriptthe messages agents sent, in order
Loopthe same message sent again and again
Deadlocktwo agents each waiting for the other
Runaway costtoken use that grows past the budget
Passed ona guess that is later repeated as a fact
Guarda rule in code that stops a failure early

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

Four ways a team goes wrong

A loop repeats one message. A deadlock has two agents each waiting for the other to go first. A runaway cost grows, because each round reads the whole history so far.

The fourth is quieter. A worker guesses, the next agent repeats the guess, and by the third it is written as a fact.

Worked example: the sign and the guard

FailureSign in a transcriptGuard
LoopThe same sender, receiver and text three timesStop at the third repeat
DeadlockTwo messages that both say waiting for, swappedA time limit and a tie-break
Passed onA guess, then the same words as a factCarry the status with the claim
RunawayTokens per round that keep risingA budget for the whole run

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

Check the sharpest sign first

A real transcript may show more than one sign. Decide an order and keep it. A deadlock and a loop both stop work, so name them before cost.

The module on building agents added a loop guard. This lesson is about recognising all four, so you know which guard to add.

A common mistake

Warning

Many people add a retry when a team stalls. A retry makes a loop longer and a deadlock no shorter. First name the failure, then pick the guard that fits it.

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\";' '// 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

Command
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 failure finder, part 1

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'function m(from, to, text, tokens, status) {' '  return { from: from, to: to, text: text, tokens: tokens, status: status };' '}' 'const T = {};' 'T.loop = [m(\"writer\", \"checker\", \"check the draft\", 300, \"none\"), m(\"checker\", \"writer\", \"add a source\", 200, \"none\"),' '    m(\"writer\", \"checker\", \"check the draft\", 300, \"none\"), m(\"checker\", \"writer\", \"add a source\", 200, \"none\"),' '    m(\"writer\", \"checker\", \"check the draft\", 300, \"none\")];' 'T.deadlock = [m(\"planner\", \"reviewer\", \"waiting for the review before I plan\", 150, \"none\"),' '  m(\"reviewer\", \"planner\", \"waiting for the plan before I review\", 150, \"none\")];' > fail.js"

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

Write the failure finder, part 2

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'T.passed = [m(\"reader\", \"planner\", \"Saturday visits rose 40 percent\", 200, \"guess\"),' '  m(\"planner\", \"writer\", \"Saturday visits rose 40 percent\", 200, \"fact\")];' 'T.grows = [m(\"a\", \"b\", \"round one\", 1000, \"none\"), m(\"b\", \"a\", \"round two\", 2000, \"none\"), m(\"a\", \"b\", \"round three\", 4000, \"none\"),' '  m(\"b\", \"a\", \"round four\", 8000, \"none\"), m(\"a\", \"b\", \"round five\", 16000, \"none\")];' 'T.fine = [m(\"planner\", \"reader\", \"find the Sunday demand\", 200, \"none\"), m(\"reader\", \"planner\", \"212 of 400\", 150, \"fact\")];' >> fail.js"

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

Write the failure finder, part 3

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function some2(ms, test) {' '  return ms.some(function (x, i) { return ms.some(function (y, j) { return i < j && test(x, y); }); });' '}' 'function deadlock(ms) {' '  return some2(ms, function (x, y) {' '    return x.text.startsWith(\"waiting for\") && y.text.startsWith(\"waiting for\") && x.from === y.to && x.to === y.from;' '  });' '}' 'function loop(ms) {' '  return ms.some(function (x) {' '    return ms.filter(function (y) { return y.from === x.from && y.to === x.to && y.text === x.text; }).length >= 3;' '  });' '}' >> fail.js"

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

Write the failure finder, part 4

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function passedOn(ms) {' '  return some2(ms, function (x, y) { return x.status === \"guess\" && y.status === \"fact\" && x.text === y.text; });' '}' 'function total(ms) { return ms.reduce(function (a, x) { return a + x.tokens; }, 0); }' 'function find(ms, budget) {' '  if (deadlock(ms)) return \"deadlock\";' '  if (loop(ms)) return \"loop\";' '  if (passedOn(ms)) return \"passed-on\";' '  return total(ms) > budget ? \"runaway\" : \"none\";' '}' >> fail.js"

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

Write the failure finder, part 5

Command
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' '  for (const name of Object.keys(T)) {' '    console.log(name + \": \" + T[name].length + \" messages, \" + total(T[name]) + \" tokens, found \" + find(T[name], 20000));' '  }' '  const q = [{ role: \"user\", content: \"In one sentence: what simple rule stops two agents sending one message for ever?\" }];' '  const r = await ask(q, \"Count each message and stop at the third repeat.\", 40);' '  console.log(\"Model: \" + r.text.trim());' '}' 'main();' >> fail.js"

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

Run the failure finder

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

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

These are recorded transcripts

A real run would spend the lab's allowance on one broken team. So the five transcripts are recorded data, short and invented. The finder is real code.

The one live call asks for a rule. Its words differ each run.

One possible output

LineWhat it shows
loop: 5 messages, 1300 tokens, found loopOne message three times
deadlock: 2 messages, 300 tokens, found deadlockBoth wait for the other
passed: 2 messages, 400 tokens, found passed-onA guess became a fact
grows: 5 messages, 31000 tokens, found runawayOver the 20000 budget
fine: 2 messages, 350 tokens, found noneNo sign of failure

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

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.