We deleted the session. The agents got more reliable.
Two agents were working at the same time. One was scaffolding a report-card app for a school in one tenant. The other was fixing a paragraph on our own marketing site in a different tenant. Both were talking to the same MCP server. Halfway through, the school's agent created a record, and the record landed on the marketing site.
We caught it before it mattered. But the bug was not a typo, and it was not a race in the database. It was built into the shape of the protocol. The server was keeping track of which app you were "currently" working on, and two conversations were quietly sharing that one memory. That afternoon we decided to delete the session. Here is what we learned.
The convenience that was actually a bug
For most of the platform's life, the server held a little state on your behalf: an active app and an active domain. You pointed a session at an app once, and every call after that operated on it without being told. Create a page. Patch a script. Kick off a build. None of those calls had to name their target, because the server already knew where you were.
For one person in one conversation, this was lovely. It read like a terminal. You "changed directory" into an app, started working, and the server kept your place.
An agent is not a terminal, and production is not one person.
Three ways implicit context goes wrong
The first is concurrency. The moment two conversations share one server, they share that single "current app," and either one can move it out from under the other. That is the failure that put a school's record on our marketing site.
The second is drift. A real task takes an agent fifteen or twenty steps. Step three switches to another app to look something up. Step twelve writes a record and assumes it is still in the app it started in. The model's picture of where it is and the server's actual state have silently come apart, and the write is perfectly formed and completely misplaced.
The third is amnesia. Sessions reset and pods restart. When the server forgets which app was active, the next call inherits nothing, and the failure is quiet instead of loud.
The thread running through all three is simple. When the thing driving the tools is a language model, implicit server-side context is a correctness hazard. The model holds one version of the world, the server holds another, and nothing forces the two to agree.
The fix was to keep nothing
So we kept nothing. In the rewrite there is no active app on the server, and no current domain. Every call carries its own scope, every single time.
// before: the server remembered where you were
setActiveApp("ob_www");
upsertRecord({ name, script });
// after: every call says exactly what it means
upsertRecord({ appName: "ob_www", domain: "www", name, script });
An agent reads its home domain once, from a single identity call, and then repeats it on everything after that: reads, writes, searches, builds. The scope rides along inside each request. There is no current app to drift away from, because there is no current anything.
Two conversations can now hammer the same server all day and never see each other, because there is no shared, mutable state left to corrupt. Isolation stopped being something we had to be careful about and became something the protocol simply guarantees.
The part we did not expect
We thought we were trading ergonomics for safety. We were. The surprise was what it did for smaller models.
A stateful protocol quietly asks the model to hold an invariant across turns: we are still in app X, eleven calls later, even though a tool result in between mentioned three other apps. Frontier models are passable at this. Cheaper ones are not. That kind of silent, long-range bookkeeping is exactly where a small model loses the thread.
A stateless protocol takes the invariant out of the model's head and writes it into the arguments of every call, where it is visible and trivial to check. The model no longer has to remember the scope. It only has to carry it forward, and copying a value forward is something even a modest model does reliably.
So the platform got cheaper to run well, and it got there because of the weaker models, not in spite of them. The cost is real and we pay it on every request: more verbose calls, appName and domain written out again and again. We will take repetition a machine cannot forget over convenience it can.
Why this matters more when the agent can write
If agents only ever read, statelessness would be a footnote. Ours write. An agent on OBTO has direct write access to a live production system. It creates pages, patches server code, and ships builds, with no branch to merge and no human clicking approve in the middle. Our own site is one of those systems: the page you are reading is a record an agent wrote and deployed, which is a story of its own.
That is only safe because two gates hold at once. The first is structural integrity. A patch is refused if its anchor text does not match the line that is actually live, and a new artifact is refused if its uploaded bytes do not hash to what was promised. Those checks stand in for the human code review a normal pipeline would run.
The second gate is the one this story is about: scope. When a write states its own app and its own tenant, inside its own arguments, there is no ambient session sitting elsewhere for it to hit by accident. A confused agent cannot quietly write into the wrong tenant, because there is no wrong tenant the server happened to be pointing at. Its blast radius is bounded by exactly what it typed into the call.
An old idea that agent tooling forgot
None of this is new if you have built web services. HTTP had the stateless-versus-session argument decades ago, and stateless won. What is worth saying out loud is that agent tooling wandered back the other way. An "active context" you set once and forget feels wonderful in a demo, with one user and one thread. It is precisely the kind of thing that shines on stage and breaks the first time two real users arrive at once.
If you want to see the contract in action, the getting-started guide walks an agent through pointing at an app and watching every call name its own scope, and the free Builder tier is enough to try it. Because nothing runs off in a hidden session you cannot see, nothing runs off the ledger either, which is part of why our pricing is metered at published rates instead of opaque credits.
We build the platform we run our own company on: this website, a stack of school apps, our internal tools, all on the same server and all under the same contract, as we wrote about in the harness and the substrate. Making that contract stateless was one of the least glamorous things we have shipped and one of the few we would still defend, word for word, a year from now. Describe it. Ship it. Own it. And know, on every single call, exactly what "it" is.