Research

Engineering

The agent is blind: grounding a spoken agent to a live canvas

The most recurring failure in our voice workstations was never a wording problem — an agent that co-drives a screen it cannot see will drift from it unless the pixels and the words are published by the same machine.

Volkan Turk · Founder & principal architect, NextSense AI

Nine words

A user was building something in one of our workstations. They picked a direction by voice. The agent said, warmly and confidently, that the choice was saved. The canvas did not move.

So they typed:

"but I can't see the rabbit now"

Nine words. It is the best bug report we have ever received, and I have thought about it more than any other sentence in our issue history.

What makes it devastating is not that something broke. Software breaks. What makes it devastating is the shape of the complaint. The user is not saying this is broken. They are saying you told me something that isn't true, and now I don't know which of us is confused. One sentence like that costs more trust than ten honest errors, because an honest error is a machine failing and this is a machine gaslighting.

We have hit this failure class more times than any other in our entire product history. This is what it actually is, and what we now build to prevent it.

The agent cannot see the screen

Start with the thing that is obvious once you say it and invisible until you do.

A language model driving a live visual surface has no eyes. It never sees the canvas. Everything it "knows" about what is on screen is a text projection that we, the runtime authors, assemble and hand to it — a description of the world, written by us, one turn stale by construction.

This is not a limitation you can prompt around. It is the physics of the situation. The agent is blind, and every visual claim it makes is a claim about a document it read, not a thing it saw.

Now add the second property that makes voice specifically brutal: a spoken agent has no scrollback. In a chat UI, a wrong claim sits on screen next to the evidence, and the user can compare. In voice, the claim evaporates the instant it is said. The only durable artifact is the user's memory of being told something that turned out to be false.

Two state machines will always diverge

Here is the actual bug, stated in the terms that let you fix it.

When you build this naively, you end up with two state machines: one that decides what is rendered, and one that decides what is said. Each is individually reasonable. Each passes its own tests. They read overlapping but non-identical inputs, they update on different triggers, and they run on different clocks — one on a render frame, one on a model turn that may take a second or more to complete.

Two parallel state machines — one for pixels, one for words — will always diverge under real timing. Not "might," under load, in a rare interleaving. Always. Divergence is the default behavior of two independent machines describing the same world; agreement is the thing you have to engineer.

The divergence has a family of symptoms, and once you know the class you start recognising all of them as one bug:

  • The agent says a value was saved; the canvas never advanced to that step. ("I can't see the rabbit.")
  • The user reopens an earlier step by voice; the agent happily starts editing it while the canvas stays exactly where it was. The agent is now working on X while the human is staring at Y — and both of them think they are looking at the same thing.
  • The agent describes a state that was true one tick ago, which is indistinguishable, to a human, from a state that was never true at all.
  • A change lands on screen and the agent doesn't mention it, because nothing told the words-machine that the pixels-machine had moved.

The seductive misdiagnosis is that this is a prompting problem. It reads like one. The obvious fix is to tell the model to check the current step before it speaks, or to be more careful about the word "saved."

That fix cannot work, and it is worth being precise about why: there is no "current" that both machines agree on. You are asking the model to consult a truth that does not exist as a single value anywhere in your system. It will consult whichever version reached it, describe that faithfully, and be wrong. You have not fixed the race; you have added a paragraph to the losing side of it.

What good looks like

The reframe that unlocked this for us: a voice agent driving a live UI is not a wording problem, it is a distributed-state-consistency problem. Once you name it that, the disciplines are the ones the distributed-systems world already knows, and they are unglamorous.

One resolver feeds both the pixels and the words. Not two derivations from a shared store — one derivation, consumed twice. If the renderer and the grounding text can ever be computed by different code paths, they eventually will be, and they will eventually disagree. This is the single highest-leverage structural decision in the whole class.

One writer, and grounding republishes in the same tick as the visual move. If a UI change and its description are separate events, there is a window between them, and every window is a bug someone will hit at 2am. Collapse the window to zero by construction rather than shrinking it by optimisation.

Navigation and state mutation are atomic. "Go to that step" and "change the thing on that step" are one transition, or you will ship a state where one happened and the other didn't — which is precisely the editing X while staring at Y failure, in its purest form.

Reopening a settled decision is a first-class transition, not a special case. This is the one people skip, because reopening feels like an edge case. It isn't; it is what humans do constantly. If going backward moves the visual surface along a different path than the one grounding reads, you have quietly rebuilt the second state machine you just spent a quarter deleting.

The agent is assumed blind — permanently, structurally. It may describe only what the machine has published as genuinely on screen, or it may say nothing visual at all. Those are the two options. This is not a style guideline about hedging language; it is a permission model. The agent does not have the capability to make an ungrounded visual claim, in the same way it does not have the capability to charge a credit card. Take the capability away and the class of lie disappears with it.

That last one has a cost we accepted deliberately: sometimes the honest answer is silence, and a spoken agent that occasionally declines to describe the screen feels slightly less magical than one that always has something to say. We will take slightly-less-magical over occasionally-lying every single time. One gaslighting moment is the line between a workstation people trust and one they close.

The companion law: one door

There is a second principle that turned out to be the same principle wearing different clothes, and it is worth stating on its own because it generalises past voice.

Model the whole interaction surface as one typed event algebra with a single door. A spoken utterance, a tap, a typed message, a session event, an async result that arrived from a generation you kicked off ninety seconds ago — all of these become one envelope, entering through one entry point, producing one derived model, mutated by a single writer.

The reason is the one we just spent an essay on: parallel state machines are the root cause of the entire agent-versus-canvas divergence class, and a second input channel is just a second state machine wearing a different hat. The moment "the voice path" and "the click path" have their own handling, they have their own beliefs, and you are back where you started with a nicer architecture diagram.

The corollary is a small rule with a lot of leverage: classify by what the user wants, not by which channel carried it. "Pick the second one" is the same intent whether it arrived as speech, as a tap on a card, or as typed text. If your system branches on transport before it branches on meaning, every capability has to be built twice and will drift once.

The payoff is concrete. It is what makes it possible to start a session talking and finish it typing, and for a typed message to land cleanly in the middle of a live voice turn — not as a special integration, but as the ordinary behavior of a system where the channel was never the commitment in the first place.

The grounding channel is also a bill

One more thing, because it constrains the obvious escape hatch.

The instinct, once you accept that the agent is blind, is to describe more. Bigger snapshot, richer projection, tell it everything. That instinct runs directly into economics: on a token-billed realtime model, you re-send the accumulated context every turn, so the size of your grounding snapshot is literally money, and a mishandled context lifecycle can multiply a ten-minute session's cost several-fold.

Grounding fidelity and token cost are not two problems that trade off. They are one budget, and you have to design them together from day one. Which is another way of saying you cannot solve blindness by describing harder — you solve it by making sure the small thing you do publish is the same small thing the screen is showing.

The test that actually matters

The review question we ask now is not "did the agent say the right thing?" You cannot test your way to that; it is an infinite space of sentences.

The question is: can this agent say something the machine never published?

If the answer is yes — if there is any path where a spoken claim about the screen is generated from something other than the published projection — then you have two state machines, and the rabbit will disappear. It is only a matter of which user finds it first, and whether they bother to tell you.

Ours told us. Nine words. We rebuilt the machine.


These are engineering-competence claims from our own shipping history, described at the level of problem classes and disciplines. The products are human-led and agent-assisted: the person decides, the agent does the heavy lifting and narrates what it did. The guarantees we make about generated output are structural — valid, renderable, playable — not that it is flawless.