The tool list is the wrong abstraction
Past a dozen tools, listing them in context stops working. Glove's scratchpad and sandboxed REPLs replace the list with a database and a program, and we measured what happened.

The standard way to give a model capabilities is to describe each one as a tool, put every description in the context window, and let the model pick. It works beautifully for five tools. It works for fifteen. Somewhere past that, it quietly stops working, and because the failure is quiet, most teams do not notice until they are paying for it.
We noticed because we measured it. This post is about what we found, and about the two primitives in Glove we built in response: a scratchpad that turns tools into a database, and sandboxed interpreters that turn them into a program.
Two costs that grow with the world
A tool has two costs, and both scale with the size of the world the agent lives in.
The standing cost is the schema. Every tool definition sits in context whether or not it is used this turn. At ten tools that is a paragraph. At 367 tools, which is what you get when you connect forty MCP servers, the schemas alone are around 39,000 tokens, on every single call, before the model has read the user's question.
The marginal cost is the result. When a tool returns, its output streams back into context verbatim. Ask GitHub for the open pull requests and you get every pull request, every field, into the window. The data outgrows the window before it outgrows the question.
Put those together and you get a system that gets slower, more expensive, and less accurate as it becomes more capable. That is backwards.
The scratchpad: tools as tables
The scratchpad's premise is that models already know one interface for large, structured, discoverable data extremely well. It is called SQL.
So glove-scratchpad exposes an agent's capabilities as tables behind a single execute_sql tool. GitHub pull requests are a table. Linear issues are a table. Your inbox is a table. Discovery is information_schema, which is where a model would look anyway. Arguments push down as WHERE clauses, so SELECT * FROM github.pull_requests WHERE state = 'open' becomes one resource call with one argument. Composition is JOIN. Side effects are INSERT.
The two details that make it a primitive rather than a trick:
Resources declare volatility. An immutable resource is cached for the life of the database. A stable one is cached within one statement. A volatile one is invoked exactly once per statement, never cached. That is how the engine knows when it is allowed to not call your API.
Writes can be staged. Inside a BEGIN … COMMIT block, effects are recorded but not fired. The agent, or a human, can inspect exactly what is about to happen before it happens. A dry run is not a feature you add. It is a transaction.
What the numbers said
We built a benchmark: ten mocked-but-real MCP servers mirroring a product team's stack, 32 tools, a seeded deterministic world, seven cross-service tasks. Writes were graded on the real side-effect outbox, which cannot be faked. Two arms: every tool folded directly into context, or one execute_sql over the same capabilities.
On frontier models the scratchpad passed 21 of 21 tasks where the baseline passed 16. Across an open-source roster it was 39 of 42 against 30 of 42, at two to three times less peak context. A model billed at nine cents per million tokens scored seven for seven, indistinguishable from the frontier.
The context-pressure result is the one we keep coming back to. Cap the window at 16k tokens, load about 320 pull requests, and ask how many are open. Baseline models hit the cap and miscount. The scratchpad answers SELECT COUNT(*) at about 1,500 tokens of peak context, correct every time. At 50,000 rows per service the naive approach would need roughly ten million tokens. The scratchpad used under six thousand.
Then we went to production scale. Forty servers, 367 tools, 72 tables, about 95 percent of them noise for any given task. The tool-folding baseline inverted: it became the least accurate arm, at twelve times the context and roughly six times the cost.
The total bill for every experiment in the paper was $5.97 across 1,238 model runs. You do not need a lab to check our work.
The lesson was not about SQL
The most important finding is the one that generalizes. The scratchpad only works if it behaves like the database the model already knows. Every place it silently deviated from Postgres muscle memory was a place a weak model failed.
We spent five rounds of hardening chasing those deviations. WHERE active = 'false' was matching the true rows, because the string 'false' is truthy in JavaScript. SELECT nope was returning a column of nulls instead of an error. Each one was an engine bug or a missing piece of metadata, not a model limitation. When we fixed them, the pass rate on the weakest models went from 74 percent to 100, spirals went from six to zero, and the median number of tool calls per task went from six to two.
We wrote the principles down. A silent wrong answer is the worst possible output. Make truth cheap, then trust follows. Discovery must be in-band. Errors are UX. And the one that reorganized how we build everything: optimize for the weakest driver. The 8-billion-parameter models are the instrumentation that shows you where the potholes are. The frontier just drives over them.
The REPL: tools as a program
SQL is a great surface for questions. It is a clumsy surface for decisions. Real tasks branch: list the PRs, and if any are stale, ping the author, else close the ticket. In SQL that is three round trips. In a program it is one.
So the same ToolFn catalog that backs the scratchpad also mounts into three sandboxed interpreters: glove-js, glove-python, and glove-lisp, each behind a single tool. The model writes a program. Results stay in the sandbox. Only what the program returns crosses back into context.
This is what the code-execution docs call off-context data flow. const prs = github.list_pull_requests() keeps the rows in the REPL. The model works with prs.length and prs.slice(0, 5) without ever paying for the full list. Arrays over 25 items and strings over 300 characters are elided with markers showing the true size, so the context cost is bounded by construction.
The sandbox is not a policy. Programs parse, validate against a whitelist, and run with a fuel budget per node and per loop back-edge, so infinite loops are impossible rather than discouraged. Every member access goes through a boundary that blocks constructor, __proto__, and prototype, so a program cannot climb back to the host. And a tool call fires when its expression evaluates. There is no planner that might re-run it. Exactly-once effects fall out of the evaluation order.
The JavaScript arm went from 78 percent to 97 through two rounds of hardening driven entirely by reading transcripts. Python, built last with the lessons baked in, landed near parity on day one. Given a free choice, models pick Python 95 percent of the time. Fluency is a knob, not a language you are stuck with.
The name is part of the tool
One more result, because it is the most counterintuitive thing we found.
We held everything fixed, runtime, catalog, tasks, models, and changed only the name of the tool: execute_js, then execute_js_program, then execute_js_workflow. The single-call completion rate went from 4 percent to 33 percent, an eightfold change. Median eval calls halved. Pass rates were tied.
And when given a free choice between them with byte-identical descriptions, models picked plain execute_js 83 percent of the time. The model will not self-select the better surface. The framing is a lever the developer has to pull. If you take one practical thing from this post, take that.
The boundary is the guarantee
Once results live in a sandbox instead of the context, the sandbox-to-context boundary becomes something else: an exfiltration boundary. Whatever crosses it, the model sees, and whatever the model sees can end up in a reply.
We measured leak rates for a canary secret under four disciplines. Raw tool calls leaked it 75 percent of the time. A REPL, 50 percent. A REPL with explicit instructions not to, 33 percent. An enforced return gate, zero out of twelve. The guarantee cannot be voluntary. A boundary the model may cross when convenient is not a boundary. That work ships as glove-egress.
What this means for how you build
Stop thinking of tools as a list the model reads. Think of them as a world the model queries, or a library the model programs against. The list was always a compromise with how early models worked, and it stopped paying for itself the moment agents got real jobs.
The scratchpad and the REPLs are both in the Glove repo, with the benchmark harness and every transcript. Run pnpm bench. Change the seed. Tell us where we are wrong.

