← Multi-Agent Orchestrator
19 min

Shared State, Memory & Durable Execution

Typed state and reducers, the blackboard pattern, checkpointing, concurrency hazards and cross-run memory.

Lesson 5 of 9
0 done
In plain English

Agents working together share a common notebook of the job's current status. When two write at the same time, you need rules for combining their updates so nothing is lost. Save progress often so a crash does not mean starting over.

Shared state
The common record of where the job stands.
Reducer
A rule for combining several updates into one.
Checkpoint
A saved point you can restart from.
Picture it
Agents share one notebook
Shared state
The job's current status
Agent 1
Reads and writes updates
Agent 2
Reads and writes updates
Merge rules
What happens if two write at once
Saved progress
So a crash is not a restart

The last lesson was about what flows between agents. This one is about what they all read from and write to: shared state. As soon as more than one agent can change the same thing, you inherit problems that people who build distributed systems have dealt with for decades. These are clashing writes, stale reads and lost progress after a crash. The good news is that the same solutions work.

State is not the same as messages

Beginners picture a multi-agent system as agents chatting. Real systems model it as one typed state object. The steps, which are agents, tools and checkers, read it and update it. Messages are just one field of that state. The state holds the goal, the plan, the status of each task, the results so far, the budget used and some flags. This is the core idea of graph-based frameworks such as LangGraph. The workflow is a graph of steps over one shared, typed state, and every step returns a partial update.

state.py
from typing import TypedDict, Annotated
from operator import add

def merge_findings(a: dict, b: dict) -> dict:
    """Reducer: how concurrent updates to 'findings' combine."""
    return {**a, **b}                      # keyed by task_id, so parallel workers never collide

class RunState(TypedDict):
    goal: str                              # pinned, never overwritten
    plan: list[dict]
    findings: Annotated[dict, merge_findings]     # parallel-safe
    errors: Annotated[list[str], add]             # append-only
    tokens_used: Annotated[int, add]
    status: str                            # "planning" | "working" | "synthesizing" | "done"

# Each node returns only the keys it changes:
def worker_node(state: RunState) -> dict:
    result = do_work(state)
    return {"findings": {result.task_id: result.data}, "tokens_used": result.tokens}

Reducers: what a concurrent update means

Say three workers finish at once, and each returns an update to the same field. Something must say how those updates merge. Without a clear rule you get last-writer-wins, and results are lost without a sound. Choose a reducer for each field. Use append for logs and errors. Use merge-by-key for findings. Use sum for counters. Use max or min for scores. Use single-writer-only for fields like the goal, which nobody except the lead should overwrite.

The blackboard pattern

A blackboard is a shared workspace. Specialist agents add partial solutions to it. A controller decides who acts next, based on what is on the board right now. It suits problems where the path is not known in advance and different specialists can build on each other's progress, such as diagnosis, planning and investigation. The trade-off: the board becomes a shared context that everyone reads. So it needs the same trimming and hygiene as any context window.

Checkpoints and durable execution

A multi-agent run can last minutes or hours and cost real money. If the process dies at minute 40 and you start over, you have doubled the bill and annoyed the user. Save the state after every step. That is a checkpoint. Then a run can resume where it stopped, be inspected, be rewound to an earlier step, or pause for a person. Frameworks offer checkpointers. Workflow engines like Temporal offer durable execution: your orchestration code is replayed from a recorded history after a crash. Either way, the rule is the same. Long-running agent work must be resumable.

  • Make every step safe to repeat, or guard side effects with an idempotency key. Resuming must never send the email twice.
  • Checkpoint before actions that cannot be undone, and not only after.
  • Store the version of the state schema, so old checkpoints can be migrated or rejected cleanly.
  • Give each run a steady ID you can pass to support: show me run 8f3a.

Concurrency traps to design out

  • Clashing writes: two workers edit the same file or record. Fix it with a single owner for each resource, or with version numbers (optimistic locking).
  • Read-then-act races: an agent checks is this ticket unassigned, and acts after another agent has assigned it. Fix it with one atomic claim operation instead of check and then set.
  • Repeated work: two workers pick up the same sub-task. Fix it with task claims, or a queue that delivers each task exactly once.
  • Stale context: a worker reasons over a snapshot that is already out of date. Fix it by versioning snapshots and checking again before it commits a write.

Memory across runs

Keep run state, which is this job, apart from long-term memory, which is what the system has learned across jobs. Long-term memory tends to come in three kinds. Episodic memory is what happened in past runs. Semantic memory is facts about users, products and preferences. Procedural memory is how to do things, such as refined prompts and playbooks. Decide for each agent whether memory is private or shared, and who may write it. An agent that can write shared memory can poison every other agent that reads it. So treat memory writes like database writes: checked, credited to someone and reversible.

Key takeaways
  • ✓Model the system as typed shared state, plus steps that return partial updates. Messages are one field.
  • ✓Define a reducer for every field that steps can touch at the same time.
  • ✓Save checkpoints so long runs can resume, pause for people and be rewound. Make steps safe to repeat.
  • ✓Design out races with single owners, atomic claims and version checks. Control who can write shared memory.
Hands-on exercise

Find the lost update

  1. Write a tiny orchestrator where three parallel workers each add their result to a shared list or dictionary WITHOUT a reducer, using a simulated delay.
  2. Make two of them write the same key, and see which result survives.
  3. Add a merge-by-key reducer and an append-only error list. Run again and confirm nothing is lost.
  4. Kill the process half way. Save the state to a JSON file after each step, and add a way to resume.
Check yourself
Q1Three parallel workers return updates to the same findings field. What goes wrong without a reducer?show answer

Last-writer-wins. The results of the other workers are overwritten without a sound. A reducer, such as merge by task ID, says how simultaneous updates combine.

Q2Why must steps be safe to repeat in a system with checkpoints?show answer

Resuming replays steps from the last checkpoint. A side effect that is not safe to repeat, such as sending an email or charging a card, could happen twice.

Q3Why is shared long-term memory a security concern?show answer

Any agent that can write to it can poison what every other agent reads later. Memory writes need checks, credit to the writer and a way to roll back.

← Context & Hand-offs Between AgentsAgent Protocols: MCP vs A2A →
0 / 9 finished0%