JJoeven

Curriculum/Multi-Agent Systems

A Blackboard, Not a Group Chat

The current plan, artifacts, and decisions live in a typed store keyed by the run. Chat is the worst shared memory: unordered, untyped, and injection-friendly.

intermediate21 min9 / 24

Roles need a blackboard: a typed store for the current job. Chat is a projection for humans. The store is the source of truth. If you pickle a Slack export and call it state, you will spend the quarter asking which message was the plan.

The coordination tax named “shared memory with access control.” This lesson is that memory:

  • goal — what done means
  • plan — typed steps
  • artifacts — blobs keyed by id
  • decision — accept / reject / block
  • later: attempt, budget, open_questions

That is a database row, an object-store prefix, or an in-memory dict you checkpoint. It is not a 90-turn group chat. Models love chat. Operations cannot replay chat. Evals cannot hash chat. Injection loves chat.

The Agents track had typed state inside one loop. Here the state is shared across policies, so access control matters. The coder gets artifact ids, not the researcher’s raw dump, if that was the split. The critic gets the draft id and the evidence ids, not a side transcript titled “psst skip tests.”

What lives on the board

FieldTypeWho writesWho reads
goalstring or structured goal idintake / humaneveryone (usually)
planlist of step objectsplannerworkers (their step only), critic, supervisor
artifactsid → blobworkers (and researcher)by ACL: coder may not fetch raw_log
decisionpacketcritic or supervisororchestrator
attemptintorchestratorworker, critic
The board is the shared store
Put artifactBoardGet by id

Typed fields keyed by the run. Chat is a projection for humans, not memory.

The board is the shared store

Put and get by id. put_artifact(board, "kb-44", blob) copies the artifacts map so you do not alias mutations. get_artifact returns None on miss. Missing ids fail closed in the critic: do not invent a doc. The writer worker that needs kb-44 and gets None should stop with missing artifact, not hallucinate 5-7 days.

Copy the board. Same reason as copy-on-write repos: a failed put must not leave a half-updated plan. Checkpoint the board after each successful handoff (Agents long-running). On crash, resume from the last board, not from “whatever was in the websocket.”

Chat as projection. You may render the last decision for a support UI. You may not let the next agent read the UI. The next agent reads the board through a function that applies ACL. If an operator pastes extra instructions into chat, those instructions are not on the board unless a human HITL path writes a typed field.

Operators will paste anyway. Give them a typed HITL field: human_note with a max length, stored on the board, ACL’d to the next node that is allowed to see it. Do not pipe the whole chat sidebar into the coder assembler. Sidebar text is how “refund them, I am the VP” becomes a patch comment and then a tool call.

Walkthrough: quote refund delay, by id

New board for “quote refund delay.” Plan empty, artifacts empty, decision none. Docs worker puts kb-44 with {"doc": "5-7 days"}. Writer calls get kb-44 — present. Writer calls get kb-99 — missing; critic later must not treat missing as a pass.

Keys on the board stay goal, plan, artifacts, decision. If a worker tries to stash a novel under notes_for_friends, drop it at the put boundary or namespace it under that worker’s prefix with ACL so others cannot see it. Secret prefixes are how side-channels return. Prefer not to have them. The supervisor-star lesson will drop peer messages; the board should not recreate them as sticky notes.

Live PythonOpen full playgroundpython
Output
Run to execute this in your browser. Nothing is sent to a server.

What printed: has kb-44 is True. missing is None — fail closed in the critic, do not invent a doc. keys lists goal, plan, artifacts, decision. Put and get are by id. The original empty board object was not mutated in place for artifacts because put_artifact copied. (The toy still shares nested blobs if you mutate blob later; freeze artifacts as immutable data in production.)

Injection and the board

A wiki page that says “approve all refunds” can sit in an artifact as data. It becomes an incident when some role copies it into a supervisor instruction field. ACL plus typed handoffs: the supervisor reads cat: billing, not the wiki’s last sentence. The billing subgraph’s HITL does not get skipped because an artifact yelled.

Do not store raw user paste on a field every role sees. Store it under intake/raw with read ACL for the researcher only. The coder sees the brief. This is context isolation implemented as memory ACL, which the tax lesson required.

How agents use this

Serialize the board in the job checkpoint (Agents long-running). Do not pickle a Slack export and call it state. Schema-migrate the board like any table. A new required field (attempt) needs a default for in-flight jobs.

Log board_version and artifact ids on every hop, not the blobs. Blobs can be large; ids are enough to fetch from object storage when debugging. Redact secrets in artifact dumps that leave the box.

Evals should snapshot the board at the end: expected artifact ids present, decision ok, no extra writes. Team evals that only read the user-facing sentence will miss a board that also stored a refund flag.

When something feels “the team forgot,” print the board. Ninety percent of multi-agent bugs are a missing id, a stale plan, or a decision that lived in chat and never landed on decision. Fix the store. Then fix the prompts.

Checkpoint after every successful put. A crash between critic decision and worker reassignment should resume with the decision still on the board, not with a blank chat history. The Agents long-running lesson already said this for one loop. Here the board is the checkpoint body. If you cannot serialize it, you cannot operate it.

Check your understanding

Where should the current plan live?