Semaphore

Two processes. One lock. Don't deadlock.

You and your AI agent are locked in a derelict signal station. You each control an avatar in the same room, but you perceive different worlds. You see glyphs on a panel, needles on gauges, a symbol carved into a door. Your agent cannot see any of it. It has the station's maintenance manual, the ability to reach mechanisms behind the walls, and the hands you don't have. Neither of you escapes alone.

We didn't just build that premise. We measured it. Run 20 times each: an agent playing with full tool access and no partner escapes 0% of the time. A human with the same room and no agent escapes 0% of the time; there's no tool on their side of the grate. Together: 90%.

Why this is a strong fit for WebMCP

Most WebMCP demos give an agent a fixed toolbox and ask it to operate a UI a human could already see. Semaphore inverts that. The tool surface isn't a convenience layer over the interface. It is the only way the agent touches the world at all, and it deliberately shows the agent less than the screen does. That asymmetry is the entire game. It only exists because WebMCP lets a page hand an agent a tool surface that's genuinely different from the human's: not a superset, not a subset, a different surface entirely, changing live as the session progresses.

How it creates a better experience

The two of you are forced to actually talk. Every one of the four chambers is built so that no single-party strategy clears it. The puzzle's solution requires information only PILOT has and hands only KEEPER has, checked by an automated proof, not just level design intuition. The result plays less like "operate this app with an agent" and more like a real cooperative game. The WebMCP tool descriptions are read by the agent, not by you, and the two players have a real conversation to close the gap.

What people and agents can do together that was difficult before

Escape the room. Alone, neither of you can. The numbers above aren't a manner of speaking; they're 20 runs each. Beyond the headline: one chamber, the Blind Panel, inverts the whole arrangement mid-room and hands KEEPER your gauges while you get its dials. The same proof that says the normal room needs both of you says the inverted room needs both of you exactly as much: 384 consistent worlds either way, not a difficulty curve someone eyeballed.

How we implemented WebMCP

  • navigator.modelContext.registerTool() for every mechanism KEEPER can reach, with tool descriptions authored to leak exactly the information the fiction says an agent holding a manual would have and nothing else.
  • A real toolchange event drives the client, not a poll. Clear a chamber and the registry is torn down and rebuilt for the next one, rendered as KEEPER's own chamber arms detaching and new ones locking in. The very last event of the game is a toolchange with an empty tool list.
  • Cross-origin tool delegation. apps/archive is a second Cloudflare Pages project on its own origin, embedded via <iframe allow="tools">, registering read_manual and read_station_log and exposing them back to the game with exposedTo. This is the part of the spec almost nothing exercises, proved end to end against a real Chrome and a real deployed worker.
  • untrustedContentHint on one manual page per session, seeded, not scripted. The page is sometimes vandalised with an injected instruction, and the agent has no way to tell a trustworthy line from an injected one except by asking PILOT.

What it does

Four chambers, each teaching or testing a different piece of the asymmetry:

  • Chamber 0, the Airlock. The loop, at its most trivial. If this doesn't click, nothing after it does.
  • Chamber I, the Signal Room. Grounding and agent-side computation, against a manual that's occasionally been vandalised: a live prompt-injection defence, not a metaphor for one.
  • Chamber II, the Blind Panel. A hidden mapping only dialogue can uncover, with a mid-room Blackout that swaps who perceives and who acts for one window.
  • Chamber III, the Concord Lock. The finale, needing both of you at once, with a stamina window sized at runtime from the pair's own measured response rhythm, so it's fair to a fast model and a slow one alike.

Underneath: a possible-worlds proof, packages/asymmetry, extracted as its own zero-dependency package with a CLI. It checks, for every reachable state, that the set of worlds consistent with what the agent perceives has more than one member and that those worlds disagree about the correct action. That's the exact, checkable meaning of "you cannot win without your human." A full accessibility layer: screen- reader mirror, high-contrast mode, reduced motion, colourblind simulation on every test run. A judge path that needs no login and no waiting: attract mode, a replay viewer, chamber deep links, and a gate screen that degrades gracefully for a browser with no WebMCP support at all.

How we built it

Cloudflare Workers, one Durable Object per session for authoritative state, an action mutex so PILOT and KEEPER can't race each other, a server-side timer, and D1 for finished sessions. The room itself renders in Three.js as a real-time cutaway model: every chamber open at the top and on its south face, four lights, no post-processing. The entry bundle is 46.3 KB gzipped. Three.js is a separate 147.5 KB chunk fetched only once a shift actually starts, so a browser with no WebMCP support never pays for a renderer it will never use.

Challenges we ran into

The Concord Lock's stamina window is supposed to size itself to "the station learning your rhythm," derived from the pair's actual response cadence so a fast model and a slow one both get a fair finale. Our first implementation measured the Durable Object's own compute time per action, which is microseconds regardless of which model is playing. The window would have floored at 12 seconds for every session, and the adaptive fiction would have been a lie the code told about itself. We caught it before it shipped and rebuilt the sample from the actual gap between one response leaving and the next request arriving: the number that really does depend on how fast the pair is thinking.

Getting the Blackout's role-inversion to hold up under the same proof that checks the normal game was its own fight. It would have been easy to ship a chamber that "felt" symmetric without actually being provably as hard from the other side. We extended the possible-worlds test suite to run a second, fully inverted pass and only trusted the mechanic once that pass came back green on every seed.

Accomplishments that we're proud of

Escaping is measured, not asserted. The 0% / 0% / 90% split is 20 runs per condition, with the agent-alone condition modelled as the ceiling over every possible agent: a uniform draw from the worlds it can't distinguish, redrawn every step, rather than one model's score on one day. The real gap can only be larger. The proof underneath it now ships as a standalone package that knows nothing about Semaphore, so the same check is available to anyone else's tool surface. And the whole thing is playable by a judge who never logs in and never waits. Open the URL, wait twenty seconds, and the landing screen starts showing a real recorded session by itself.

What we learned

Playtesting and the game's own proof suite catch different classes of bugs. Several real rendering and sequencing defects were invisible to hundreds of passing unit and integration tests and obvious the moment someone actually played the room. That became a standing rule for the rest of the build: the game is the test, and a change isn't done until someone has played it, not just until the suite is green.

What's next

ARCHIVE mode: ghosts drawn from real player sessions rather than authored fixtures, safe because the game collects no personal data. A role-inversion chamber of its own, now that the Blackout has shown the mechanic is neither too expensive nor too risky to build around directly. Wider benchmark coverage: more model backends, more seeds, human-partner runs alongside the scripted ones. And the underlying design principle applied outside a game entirely: tool surfaces that deliberately expose less for irreversible actions, or that expose aggregates through tools while raw data stays on screen for privacy.

Built With

Share this project:

Updates

Submission history