Last time I started looking at some of Grotto's implementation details while going through the budget system.
This time I want to take a step back and look at the overall picture. How does Grotto make it safe to embed the runtime in a host application and execute untrusted code inside it? And how does the host get its own APIs into the runtime for the code to call in the first place?
The language is the sandbox
Half the work is already done by the language itself. Neander follows the guiding design principle that the language is the sandbox.
A Neander program has no file I/O, no sockets, no system access. It can only reach the APIs the embedding application registered, and even those only by going through discover and call. On top of that, every valid program is guaranteed to terminate, and the budget system stops it before it can drain the host's resources.
All that is left for Grotto is to make this design a reality.
The grotto is the fortress
The language being safe by omission handles the program's intentions. The runtime architecture handles everything else: runaway resource use, one submission interfering with another, a wedged program that will not stop.
Grotto's architecture is based on the dispatcher-worker pattern:
Only the dispatcher runs on the host's thread and it does not touch the code itself. Every non-empty, in-range program submission runs in its own fresh worker thread, spawned for that one program and terminated once it returns the response envelope. Two programs cannot observe or influence each other, because they never share a thread and never share memory. The only channel that exists at all is a message pipe to the dispatcher, and it carries copies, not references. The isolation sits on an OS thread boundary.
Because the program runs on a thread separate from the dispatcher, the dispatcher can always kill it. As we already saw last time, that is what makes the duration budget enforceable even against a program stuck in a tight synchronous loop with no yield point: the dispatcher keeps the clock on its own thread and, on overrun, terminates the worker outright.
API providers: the one door in
The whole safety story rests on a program being able to reach nothing except the APIs the host registered. Which raises the question the series has quietly deferred until now: how do those APIs get in, and why is that the one door that does not undo everything else?
The host registers its APIs as provider modules. Each module is one namespace, and it exports two things: a manifest that declares what the namespace offers (its functions, their parameter and return types, its named types and documents) and a handlers map that supplies the actual code behind each declared function. The manifest is what an agent sees through discover. The handlers are what a call eventually runs.
The runtime checks these once, at startup, not on every submission. When the embedding application starts the runtime, a provider validator imports every module and verifies the contract: every function the manifest declares has a matching handler and vice versa, no two modules claim the same namespace, nobody grabs the reserved runtime or main names, and every declared type actually parses and resolves. A malformed provider fails loudly, once, at start. By the time the runtime accepts a single submission, the whole API surface is known to be well-formed.
At execution time the handler runs in-thread, inside the worker, as an ordinary async function call. There is no per-call IPC and no serialization across a process boundary. What there is, is a marshalling boundary: values are converted between their Neander representation and plain JavaScript as they cross into and out of the handler, and every conversion is checked against the type the manifest declared.
That boundary is where the runtime hands control to code it did not write, so it is worth being precise about what can happen there. A handler that throws a conforming {code, message} error comes back to the program as an ordinary error, which it can handle like any other call failure: inspect it, substitute a default, or re-throw it. A handler that misbehaves, by throwing something malformed or returning a value that does not match its declared type, is not the program's fault and is not handed to the program. It is a provider contract violation: the execution aborts, the blame lands on the named provider, and the agent's program never sees it. Provider bugs stay provider bugs. They do not leak into the language's failure model and they do not compromise the isolation around them.
So the one capability the language grants is narrow by construction: reachable only through discover and call, backed by a contract verified before anything runs, and marshalled across a typed, checked boundary every time it is used.
Defense in depth, by category
Put it together and the guarantees line up in layers, each catching a different class of problem at a different moment:
- Before the program runs: validation. Anything malformed, ill-typed, or referring to APIs that do not exist is a Flaw, and the program never executes. Forging a discovery handle, mixing up types, calling a function that is not there: caught here, statically.
- While it runs: budgets. Too much computation, memory, or time is an Abort. The program is stopped, the host is not.
- Around the whole thing: isolation. No I/O to misuse, no sibling submission to spy on, a worker the host can terminate at will, and a provider boundary that blames the provider rather than corrupting the run.
None of these is the load-bearing one. That is the point of layering them. A program that passes validation still cannot outspend its budget. One that stays inside its budget still cannot reach the filesystem. One that somehow wedged its own thread still cannot survive the dispatcher's clock. Nothing here requires trusting the agent. It requires trusting the runtime, and the runtime is a small, dependency-free dispatcher plus a throwaway worker, open for anyone to read.
Field Notes from the Grotto
Across the series we have covered a lot of ground: how an agent finds the host's APIs, how it learns the language cold from an empty program, why its programs always stop, what they are allowed to spend, why it is safe to run them at all, and how the host's own APIs get in. The argument stands on its own now. Put the safety in the language and the runtime, by construction, and no sandbox is needed, because the things a sandbox exists to contain were never built.
For the embedder, this was the answer to the question that arrives the moment someone says "let an agent write code and run it in production": why is that not insane? Because the code the agent writes cannot do anything the host did not hand it, cannot outlast or outspend its allowance, cannot touch the request running next to it, and reaches the host's own code only across a boundary the host verified first.
This is the end of the foundational series. Thank you for taking the tour. There will be a couple of encores, standalone entries on parts that reward a closer look on their own terms: the full failure taxonomy, from in-program failable values to the one top-level verdict every submission returns, and the type system, structural and recursive, down to the arbitrary-precision numbers.
There is still time to read the Neander spec, embed Grotto in your own app, and let me know if your APIs got called.
Mind your head on the way out.

Top comments (0)