JJoeven

Curriculum/Multi-Agent Systems

A Write Barrier After Map

Map workers are read-only (or write only to their own prefix). Money and email happen once, after reduce, in the parent apply step, with an idempotency key.

advanced19 min20 / 24

Keep a write barrier: map workers read (or write only to artifacts/{child_id}/...). The parent’s apply step is the only place money or email happens. If you skip this, you have invented distributed side effects with a cute name.

Retries duplicate children. Two children that both “helpfully” refund the same customer is the two-writers incident with extra parallelism. Idempotency keys on apply save you. The Tools track already taught those keys — here the parent owns them. Children never see refund or email on their allow-list.

The swarm lesson forbade write tools on workers as the main risk. This lesson is the positive design: a barrier in the dispatcher, and a parent apply that is safe to retry.

Two sides of the barrier

Child. Legal: score, extract, maybe read-one-item. Illegal: refund, email, edit on a shared path, apply. Dispatcher returns write_barrier without mutating the world. Same copy-on-write habit as planner-cannot-patch-src.

Child-prefix writes: a scratch file under that child id can be ok if ACL hides it from siblings and it is not money. Do not put customer ledger files in a prefix and call it scratch.

Parent apply. After reduce. Reads the table. If missing/dropped too high, stop. If ok, call refund or email once with key job_id:customer_id (or job_id:item_id). Retry of apply with the same key returns dup: True and does not move money again.

Callerrefundscore
Childwrite_barrierok
Parent, new keyok, first timenot a write
Parent, same key againok, dup
Write barrier after map
Children readBarrierParent apply

Children map. Only the parent writes money or mail. Same key does not pay twice.

Write barrier after map

The sequential billing subgraph still wraps this: HITL then apply. A swarm of scorers might sit before HITL (“rank these 200,” then a human sees the page, then apply). Children still cannot apply while the human is thinking.

Agreement among children is not apply either. Two children that both “think” the customer should be refunded still return labels. The parent reduce may vote refund_candidate. HITL may agree. Apply then runs once with a key. “Two children agreed” in the quiz is a trap: chorus is not a ledger, same as the grounded judge.

Walkthrough: child refund blocked; parent once

Child score on prefix c1: ok.

Child refund: write_barrier. World unchanged.

Parent apply refund with key job9:cust1: ok, dup false. Seen set now holds the key.

Parent apply same key: ok, dup true. No second refund.

Wrong parent call score as apply: not_a_write. Apply is for the write names only. Do not mix.

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

What printed: child score ok. Child refund error write_barrier. First parent apply ok with dup: False. Second parent apply ok with dup: True. Child refund is blocked. Parent apply runs once; the retry is dup.

The seen set is a toy of the Tools idempotency store. Production uses a real key-value with TTL and the same semantics: first writer wins. Two parents (a bug) still collide on the key. Combine with one-writer-per-record in the next part so a loyalty agent cannot credit while billing refunds.

Pricing and enqueue

Do not let a child “queue an apply” either. Queuing a write is a write. Children return labels. Parent decide + HITL + apply.

Retries of the parent job must reuse the same apply key. If a crash restarts the job with a new key, you double-refund with perfect barriers and perfect child isolation. The key is derived from job_id plus record id, not from a UUID minted at apply time. Tools already taught this. Swarm parents forget it because “this is just reduce.” It is not just reduce. It is the only door to money.

Price the swarm in the job record before enqueue, then enforce the cap in the worker launcher. A write barrier plus an idempotency key is how you sleep. If launch refused because N was 50, you never needed the barrier for those 50 refunds — they did not start. Both controls matter: refuse over-budget fan-out, and barrier whatever did start.

Swarms of editors on one file cannot be saved by a barrier unless you redefine the swarm as “propose hunks to prefixes, parent merges.” If you cannot merge, it is not a swarm. Sequential one writer. The two-writers lesson will lock the file anyway; do not use a swarm to dodge the lock.

Friday ticket: two children, one customer

Billing fans out 12 invoice lines to scorers. Two lines belong to the same customer. Both scorers would like to be helpful and call refund. The dispatcher returns write_barrier twice. The world does not move. Reduce sees two refund_candidate labels for cust1. HITL agrees once. Apply runs with key job9:cust1. A worker crash retries apply. The store already has the key. dup: True. One refund. That is the whole point of the page.

If the parent minted a fresh UUID at apply time, the retry would look like a new refund. The barrier would still hold for children, and you would still double-pay. The key is job_id plus the record id, not a random token. Write that in the runbook next to the diagram.

FailureWhat you seeFix
Child refund on allow-listTwo money events on one ticketRemove write names from child tools
Apply key is a UUIDDup after crash still paysDerive key from job + customer
Child enqueues applyQueue fills with writesQueueing a write is a write; return a label
Parent apply before reduceRefund with no score tableApply reads reduce output or stops
Prefix “scratch” is the ledgerChildren edit shared money filesPrefix is not a ledger

What goes wrong

  • Putting refund on the child allow-list “just for this swarm.” There is no just. The dispatcher is the policy.
  • Treating dup: True as an error and retrying with a new key. Dup is success. Log it. Do not invent a second door.
  • Logging only the thought “I would refund” and not the apply result. On-call needs the key and dup.
  • A second parent (loyalty) that also apply-refunds. The barrier is per dispatcher, not a law of physics. One writer per record is the next part.
  • Skipping HITL because “the children agreed.” Agreement is a label. Money is apply.

How agents use this

Stamp barrier=map on child spans and apply_key on parent writes. Alert if a child span ever has a write tool name, even if denied — prompt drift. Alert if apply runs without a reduce row for that id.

Fixture: 3 children, 2 try refund, 1 scores; after reduce, one parent refund with a key; replay apply; assert one money event. Keep that fixture when you change frameworks.

Swarms of editors on one file cannot be saved by a barrier unless you redefine the swarm as “propose hunks to prefixes, parent merges.” If you cannot merge, it is not a swarm. Sequential one writer.

The failure-modes part next names ping-pong, two writers, and the priced 20-versus-50 launch cap. You already have the habits: typed teams, boring orchestration, debate with a grounded judge, map-reduce with a barrier. The rest is how those teams still fail when a control is missing.

Check your understanding

When may a swarm child call refund?