A worker only knows its brief
The orchestrator-worker pattern is a program where one agent splits a task into briefs, hands each to a worker and joins the results. It matters because a worker knows nothing but its brief.
A vague brief gets a confident answer to the wrong question. A brief that stands alone gets an answer you can check.
By the end of this lesson
Note
I can split a task into stand-alone briefs whose results I check before joining.
Key words
| Word | What it means |
|---|---|
| Brief | everything one worker is given: question, material and reply shape |
| Result shape | the fields a worker must send back |
| Material | the text a worker may use for its answer |
| Validation | checking a result before it is used |
| Join | putting the accepted results together |
| Unknown source | a source a worker cites but was never given |
The cells students fill in are left blank in this preview.
What goes in a brief
A worker cannot see the task, the other briefs or the chat. If it is not in the brief, it does not exist.
So a brief holds the question, the material the worker may use, the shape of the reply and a size limit. The material is the text itself, not a promise that the worker can look it up.
Worked example: one brief
| Field | Example | Why it is there |
|---|---|---|
| question | What would a Sunday cost in staff time? | One question, not the whole task |
| material | The staff cost sheet, as text | The worker can use nothing else |
| sources | s2 | So the answer can name where it came from |
| reply | An object with answer and sources | So the orchestrator can check it |
The cells students fill in are left blank in this preview.
Validate a result before you join it
A result is data from a part of the system you do not control. Check the shape first: is there an answer, is there a list of sources. Then check that every source is one the brief gave.
Join only what passes. The module on building agents passed a message between agents. Here you decide whether to believe what comes back.
A common mistake
Warning
Many people join every result and fix the mess afterwards. One bad result then sits in the final answer with the same weight as a good one. Reject at the door and keep the reason.
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 orchestrator, part 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { ask } = require(\"./model\");' 'const SOURCES = {' ' s1: \"Survey: 212 of 400 visitors asked for Sunday opening.\",' ' s2: \"Staff: six hours at 18 pounds an hour is 108 pounds each Sunday.\",' ' s3: \"Visits: Saturday had 310 visits last month, a weekday had 120.\",' '};' '// A brief carries everything its worker may use: the question and the text.' 'function brief(id, question, ids) {' ' const material = ids.map(function (i) { return SOURCES[i]; });' ' return { id: id, question: question, sources: ids, material: material, reply: \"an object with answer and sources\" };' '}' > team.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the orchestrator, part 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Scripted workers. Each sees its own brief and nothing else.' 'const WORKERS = {' ' b1: function (b) { return { answer: \"212 of 400 visitors want it\", sources: [\"s1\"] }; },' ' b2: function (b) { return { answer: \"108 pounds each Sunday\", sources: [\"s2\"] }; },' ' b3: function (b) { return { answer: \"Weekends are busy\", sources: [\"s9\"] }; },' '};' 'function problems(b, r) {' ' const out = [];' ' const full = typeof r.answer === \"string\" && r.answer.trim().length > 0;' ' if (full === false) out.push(\"missing answer\");' ' if (Array.isArray(r.sources) === false || r.sources.length === 0) out.push(\"missing sources\");' ' else r.sources.forEach(function (s) { if (b.sources.includes(s) === false) out.push(\"unknown source \" + s); });' ' return out;' '}' >> team.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Write the orchestrator, part 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const own = \"How busy is the library on a weekend day?\";' ' const q = [{ role: \"user\", content: \"Write one short research question about how busy a library is at weekends.\" }];' ' const text = (await ask(q, own, 30)).text.trim();' ' const briefs = [' ' brief(\"b1\", \"How many visitors want Sunday opening?\", [\"s1\"]),' ' brief(\"b2\", \"What would a Sunday cost in staff time?\", [\"s2\"]),' ' brief(\"b3\", text.endsWith(\"?\") ? text : own, [\"s3\"]),' ' ];' ' for (const b of briefs) {' ' const r = WORKERS[b.id](b);' ' const p = problems(b, r);' ' console.log(b.id + \" [\" + b.material.length + \" source] \" + b.question);' ' console.log(\" \" + (p.length ? \"REJECTED: \" + p.join(\", \") : \"accepted: \" + r.answer));' ' }' '}' 'main();' >> team.js"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Run the orchestrator
docker exec -w /home/node/project agent node team.jsShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
The workers are scripted
The lab has one model, so a real team would be one model playing every part. The three workers here are plain functions with recorded answers. The one live call only words a research question.
A real system sends each brief to a model, and the check stays the same.
One possible output
| Line | What it shows |
|---|---|
| b1 [1 source] How many visitors want Sunday opening? | A brief with its material |
| accepted: 212 of 400 visitors want it | Shape and sources both pass |
| accepted: 108 pounds each Sunday | The second worker also passes |
| REJECTED: unknown source s9 | The third cited a source it was never given |
The cells students fill in are left blank in this preview.