WebMCP & Deterministic Hybrid App Design

WebMCP works great. I have a cool test app for the WebMCP challenge almost ready, & it’s the first time I’ve been able to deterministically control an app via ai, due to the WebMCP tools enforcing that by design. It really opens up exciting new possibilities for hybrid apps.”

“Even with WebMCP agent/models revert to non-deterministic behavior & the only way around it is to make WebMCP tools enforce deterministic behavior. The only way to do this is to TEST YOUR SHIT THOROUGHLY to surface the agent instabilities. Everything old is new again😉”

Two posts, one hour apart. The first names the opportunity. The second names the constraint. Everything worth saying about WebMCP lives between them.

The promise

WebMCP gives an AI a typed, schema-constrained contract for controlling software in the browser. Before it, you let a model emit code and hoped, or you wired a few loosely typed tools to a backend and fought the model’s improvisation at the edges.

SonicViz has twelve tools. Four for the fretboard, three for the harmonica, one for navigation, four for practice. Every control is an enum, every number is bounded, every flag is explicit, and every tool returns the same state object the UI renders. The model never touches the DOM. It calls tools whose inputs are validated before they reach state and whose outputs are the state.

That is the first time I have been able to control an app deterministically through an AI. The AI did not become deterministic. The boundary is deterministic by design. Pass a tool seed: 1234, key: G, style: blues, bars: 8 and the lesson that returns is identical every time. Generation is a pure function. The model is the voice. The app is the source of truth.

The catch

The schema constrains what the model can pass. It says nothing about what the model decides to do. The model is still a sampler.

Within an hour of the “it works” post this bit me, mid-demo. A model will do all of the following and none of it violates the schema:

  • reorder tool calls into the wrong sequence
  • omit an optional argument that carries real meaning
  • repeat an identical request expecting a different result
  • toggle a state it was asked to set
  • answer from conversation memory instead of reading the app’s actual state

WebMCP gives you a deterministic boundary. The system on the other side is still non-deterministic. The entropy moved up one layer. It did not leave.

Three failure modes we shipped and then found

These only show up when you test with a real model, so I want to be specific.

1. The toggle that should have been a set. “Loop bars 9 through 12” is an instruction to enable looping over that phrase. The first implementation toggled. Off became on the first call, on became off the second. Rehearsing “Loop bars 9 through 12 at 60 BPM and play” twice for camera switched the loop off on the repeat. The fix had to go in the tool. Bounds now enable looping, idempotently. Repeating the request keeps the loop on. The tool was changed to mean what the words mean.

2. The ambiguous bare call. “Mute the backing chords” has a direction. A tool call with no explicit enabled flag cannot know which direction was meant, so the code defaulted to toggle, like a physical mute button. Correct for the button, wrong for the agent. A second “mute the backing” unmutes. The contract we landed on: the agent passes enabled explicitly for mute and unmute. A bare call stays a toggle because that is what the visible button does. The docs and the tests now say this out loud.

3. The “already set” ghost. Ask the agent to switch to the guitar view. Click back to the harmonica by hand. Ask the agent to switch to the guitar again and it answers “already set to guitar” without calling anything. The app was never wrong. I verified every state transition is faithful. The model had cached an earlier tool result and stopped checking. This is the purest version of the thesis. The bug lives in the orchestration, above the schema and the state. The fix is a contract change plus a test that names the failure.

None of these surfaced in unit tests. All three surfaced in testing with a real model. That is the “test your shit thoroughly” part. The interesting part is what “your shit” turns out to mean.

The architecture

Accept that the model is non-deterministic and the design problem cleans up. Put all the determinism in the layer you control. Four rules follow.

Keep a deterministic core. The parts that must reproduce exactly are pure functions. Lesson generation, theory math, deriving guitar positions and harmonica tab from a melody. Same inputs, same outputs, always. The model can request variation with a new seed. Variation is a parameter, never an accident.

Separate canonical from derived. A melody event stores {beat, durationBeats, id, midi} and nothing else. The guitar string and fret, the harmonica hole and technique, the staff note, the TAB line are derived projections computed from the canonical event and never stored on it. That is how one lesson stays a guitar lesson, a harp lesson, and a score at the same time without the three ever disagreeing.

Make tools idempotent and intent-shaped. A tool should do what the sentence means. “Loop bars 9 through 12” enables the loop, every time. “Switch to guitar” switches, every time. Repeating a request is safe by construction. Where a toggle is genuinely wanted, the toggle lives on a physical button and the tool keeps an explicit flag.

One source of truth, with authority boundaries. Tools return the same state the UI reads. There is no agent state drifting separately from app state. Navigation is the only tool allowed to change tabs. Domain tools mutate only their own workspace and never navigate. Read-only tools say so. Audio playback sits behind a direct human gesture. The agent can prepare. Only the user performs.

Why determinism matters

“Deterministic” has become a slide in every AI deck and mostly it means nothing. Here is what it means.

For a learner, determinism is the difference between a practice loop you can trust and a demo that works once. A musician who hears the same two bars slightly differently each time will not blame the model. They will stop trusting the tool. Reproducibility is the product in teaching, never an extra.

The same property, in anything larger than a music app, is auditability and liability. If a system talks to a person and the person acts on what the system says, then what the system says had better be repeatable, traceable, and checkable. A non-deterministic layer can be a voice. It cannot be a record. The tool boundary is where you draw that line, and the line has to be structural, not polite.

Test the agent, not just the code

If the failure mode is orchestration, the test suite has to test orchestration. Two layers cover it.

A dependency-free browser regression suite, currently 26/26, asserts what a machine can assert. Generation is identical for the same inputs. Canonical events stay free of derived notation. Loop bounds enable idempotently. Reset matches the visible Clear action. Consent cannot be bypassed.

A manual QA matrix, written as natural-language prompts, covers what a machine cannot. The same sentences a presenter will speak, each with its expected tool calls and resulting state, ordered from a single command to one-shot compound requests, organized by tab. Agent behavior is only observable through agent interaction, so the checks that matter read like a script. When a regression shows up it becomes a lettered sub-test beside its parent rather than triggering a renumber.

The maintenance rule is the actual discipline. When a tool changes, its descriptor, its coverage contract, its numbered checks, and its presenter-facing cross-references change together. Determinism is not a property you have once. It is a property you re-earn every time you touch the surface.

What a hybrid app is now

A deterministic hybrid app has three layers. A deterministic core that computes. An agent layer that turns natural language into tool calls. A human who stays in the loop, taking over the UI, granting consent, doing the performing. The model proposes. The app computes. The human performs.

SonicViz is music practice, but the shape generalizes. Anywhere you want conversational control plus reproducibility plus safety: CAD, data tools, teaching, accessibility, agentic browsers. The agent lowers the cost of operating a complex interface. The deterministic core keeps the result auditable and exact. The human keeps the agency and the consent gates.

The safety property falls out of the structure. Things that must be a direct human action stay a direct human action, enforced by the core rather than by politeness.

Everything old is new again

None of this is new. Idempotency, single source of truth, exhaustive tests, explicit state machines, separating canonical from derived data. This is deterministic software engineering from forty years ago. What changed is where the boundary sits.

The user of your API is no longer a careful programmer reading your docs. It is a non-deterministic program that will call your tools in the wrong order, drop an argument, and answer from memory instead of reading state. So the discipline that used to live inside your code now has to live at the tool boundary, because that is the only part of the system you can make hold still.

WebMCP does not make AI deterministic. It gives you a place to fight the non-determinism: the seam. Make the tools mean what the words mean. Keep the core pure. Test with real models until the instabilities surface on your machine instead of in front of an audience.

The guardrails have to be in the interface. That was true before AI and it is true again now.

Key Links

Written By

Pin It on Pinterest