All projects

Separation

0 comments

What it does

Two agents write disjoint state, safe alone, together breaking an invariant neither touched. Databases named this write skew in 1995; serializable snapshot isolation catches it. Agent runtimes don't: validating at commit can't see a decision that isn't written yet. SEPARATION is that hazard in a Mozaik TRACON sector: a descent moves only the vertical, a turn only the lateral; together, 2.3511 NM, 397 ft, 34.54 s of lost separation. Serializing misses the window both ways: 9446 and 8770 ms. An airlock holds both half-formed clearances mid-turn and rewrites one argument: 4000→7000 ft. 600 runs: 100 losses in both control arms, 0 with the interlock; 100/100 hazardous seeds altered, 0/100 safe.

How the agents run concurrently

The airlock is a Mozaik InterceptionHandler on commit_clearance calls. Mozaik awaits interceptionHandler.handle() inside the agent loop, so awaiting suspends that agent alone, mid-turn — the only interval in which a peer's half-formed intent can change its mind. Concurrently: three LLM controllers with incommensurable objectives (APPROACH, DEPARTURE, FLOW), ten LLM pilots with private constraint sheets, non-LLM TCAS reflexes at 20 Hz, and a world that gates no one, absent from the model registry. Both controller turns stay open 57.78 s (fixtures/trace.json); when FLOW's objection lands, TurnScheduler.inflight() holds both turnIds — asserted at tests/live/mind-change.test.ts:153, printed as INFLIGHT AT THIS MOMENT: 2. The sequential ablation arm shows content-differs 0 by construction: no peer intent exists while a decision forms. Shared state, on Mozaik's RuntimeState: a VersionedCell per aircraft's standing, and the desk's PendingSet — the airlock, the only genuinely multi-writer cell. Standing is multiply held by design: APPROACH holds AAL221/runway-sequence while FLOW holds AAL221/metering-interval, at once, legally. Every mutation goes through casWrite(cell, expectedToken, mutator, …); a stale write is rejected and announced as cas.rejected. What is not shared is context: each turn owns a fresh ModelContext, because addContextItems mutates in place and one Memory across two live turns hard-400s the provider. FunctionCallItem.rehydrate replaces the held call's args — a narrowing, not a veto — so it executes and lands in APPROACH's own memory: targetAltFt 4000→7000, "that descent crosses my metering block at CARDL".

Comments

No comments yet.

Sign in to like and comment

Newsletter

For developers who want to learn how to build self-organizing agents.

Join our online hackathon