Harness design notes
← All notesPart 2: Does speculation pay? →

Speculative tool calling · Part 1 of 2

The Speculation Ceiling

Speculative programmatic tool calling contains two techniques. One is capped at 2× and needs a streaming parser, a shadow interpreter, and a purity allowlist. The other has no cap and needs none of that. Most teams will build the first one.

Alex Zhang's post proposes a clean idea. In a harness where the model writes code and the code calls tools, you don't have to wait for generation to finish before running anything. Parse the partial program as it streams, pre-launch the calls whose arguments you already know, and let the real execution collect cached results when it arrives. He calls it speculative programmatic tool calling, and he measures it at 1–1.2× on a Recursive Language Model over OOLONG.

He's upfront that the number is modest. It isn't a disappointment. It's a number you could have predicted from two measurements you already have. And once you can predict it, something more useful falls out: the post bundles two distinct techniques under one name, with very different ceilings and very different implementation costs. They are not additive, and the cheap one is worth more.

Where the wall clock actually goesA turn is two phases, and only one of them is being optimized

A turn is decode (the model writing the program, call it D), then execute, the program running its tool calls, E. Baseline wall clock is D + E. The calls inside E are usually sub-LLM or sub-agent invocations, so E is dominated by network-bound work that could have started earlier. There are three ways to arrange the same six calls.

Fig 1Three arrangements of one turn
DECODE TOOL CALL, AFTER DECODE TOOL CALL, SPECULATED DURING DECODE sequential D + ∑Ei 590 parallel dispatch D + max Ei 315 speculated max(D, last call) 295 PARALLEL DISPATCH SAVES 275 SPECULATION ADDS 20 MORE
The same six independent sub-LLM calls, three schedules. Running them concurrently after decode collapses ∑Ei to max Ei and captures 275 of the 295 units available. Launching them during decode, the streaming half of sPTC, recovers the remaining 20. Numbers are illustrative, but the ordering is structural, not a quirk of these values.

That second bracket is the whole argument. Once the calls run concurrently, there is very little left for streaming speculation to win, because the thing it hides is already hidden. The two techniques compete for the same savings, and whichever you implement first collects most of it.

The mechanismA shadow REPL, and the valve that makes it safe

The implementation is worth understanding before deciding against half of it. Zhang forks the real namespace with a deepcopy and swaps every speculatable tool for a stub that launches the call and registers a promise. Each time a chunk of tokens arrives, the partial program is re-run in that shadow namespace.

Fig 2Shadow REPL and the promise store
model streaming tokens parser per chunk PARTIAL CODE sub-LLM workers shared serving engine shadow namespace deepcopy fork of locals tools → speculating stubs RE-RUN promise store key = (args, occurrence) not args alone LAUNCH DISPATCH RESOLVE real namespace exec(complete cell) tools → lookup, else run COMPLETE CELL LOOKUP → CACHE HIT SHADOW STATE IS NEVER PROMOTED
Speculation runs in a fork that can never write back. The shadow namespace exists only to discover which calls to launch; the completed cell is then executed against the untouched real namespace, where tools resolve from the promise store instead of re-running. Keeping the cell as the unit of computation is what makes a half-written or broken program harmless.

Two design decisions deserve to be copied by anyone building in this space, independent of whether you adopt speculation at all.

The one-way valve. Shadow state is never promoted into the real namespace. A partially generated program is very often a broken program, and a speculator that mutated real state would leave the harness somewhere no retry could recover from. Treating the cell as an atomic unit costs a deepcopy and buys a system that fails cleanly.

Promises keyed by occurrence, not just arguments. If a program runs five identical llm_query calls to take a majority vote, keying the cache on arguments alone collapses them into one sample and silently changes the answer. Keying on call site plus occurrence means the speculated trajectory is identical to the unspeculated one. This is the guarantee AsyncFC gives up, and it's the difference between a latency optimization and a semantics change.

What is actually speculatablePurity is a whole-cell property in practice

Speculation is only legal when you can determine a call's arguments without running anything that touches the outside world. Zhang's implementation walks four cases.

Case 1: Literals

Arguments are known from the tokens alone. Launch the moment the call parses; no shadow execution needed.

title = llm_query("Title for: The Odyssey")
blurb = llm_query("Blurb for: The Odyssey")

Case 2: Pure dependencies

Arguments derive from values the shadow REPL can compute safely. Chains work too: a call can wait on an earlier speculated result and still fire before decode ends.

a = llm_query("Triage: " + doc)
c = llm_query("Summarize: " + a)  # waits on a

Case 3: Peekable or not

A value already sitting in the shadow namespace is peekable. One produced inside a function body the speculator hasn't entered is not. Same file, opposite outcome.

side = llm_query(q, chunks[0])   # peekable
parts = [gist(c) for c in chunks]  # not

Case 4: Blocked, and contagious

Anything reaching an unsafe call is refused. Critically, the refusal propagates: every later call whose arguments touch that value is blocked too.

notes = open("/tmp/x").read()  # blocked
b = llm_query("Use: " + notes)  # blocked

The fourth case is the one that bites in production. Purity gets discussed as a property of individual calls, but blocking propagates through the dataflow, so it behaves like a property of the whole cell. One file read near the top of a program zeroes out the hit rate for everything downstream, and real agent programs read files near the top constantly. Expect measured hit rates well below what the case analysis suggests.

What it is worthThe ceiling is 2×, and only at one point

Now the arithmetic. Let h be the fraction of execution time you successfully speculate. You save min(hE, D): you cannot hide more work than there is decode to hide it under.

Speedup from streaming overlap (D + E) ÷ (D + E − min(hE, D))
ceiling = 1 + min(D, E) ÷ max(D, E)  ≤  2

Perfect speculation, infinite engineering, zero overhead: you cannot beat 2×, and you only reach 2× when decode time and execution time are exactly equal. Plot it against E/D and you get a tent.

Fig 3Speedup ceiling vs. the decode-to-execute ratio
MEASURED BAND, ZHANG 2026: 1.0–1.2× 1.0× 1.25× 1.5× 1.75× 2.0× 0.1 0.2 0.5 1 2 5 10 E / D  ·  EXECUTION TIME ÷ DECODE TIME  (LOG) 2×, ONLY WHERE D = E EVERYTHING ELSE IS WORSE RLM ON OOLONG: E/D ≈ 0.2 → 1.2× h = 1.0 h = 0.75 h = 0.5 h = 0.25
A model, not a measurement. Curves are the formula above at four hit rates. The result worth noting: at E/D ≈ 0.2 with a perfect hit rate the ceiling is exactly 1.2×, the top of Zhang's measured band. His implementation appears to be running near the theoretical maximum for its workload. The technique isn't underperforming; the workload has no more to give.

Two things follow that you can act on this week. First, E/D is measurable today with two timers and no new code, and it tells you your ceiling before you write a parser. Below about 0.3, your best case is under 1.3× and the shadow interpreter is not worth maintaining.

Second, and less obvious: being execution-heavy doesn't rescue you either. The tent is symmetric in log space. At E/D = 5 you get the same 1.2× as at E/D = 0.2, because now there isn't enough decode to hide the work under. Streaming speculation only pays near balance, which is a narrow place to build for.

The costs are not cappedPurity analysis protects correctness, not the bill

Zhang's safety analysis is about the inputs to a speculated call. But the calls themselves are sub-LLM invocations: they cost tokens, they occupy queue slots, and they're non-deterministic. A speculation that never gets used is not free the way a mispredicted CPU branch is free.

Fig 4Returns saturate, spend does not
PURE COST: NO FURTHER LATENCY AVAILABLE SPECULATED WORK = DECODE TIME latency saved, flat from here tokens billed, still climbing 2.4× baseline spend none max 0 0.5 1.0 SPECULATION AGGRESSIVENESS: FRACTION OF ELIGIBLE CALLS LAUNCHED EARLY
Illustrative, at E/D = 2.5. Latency saved is min(aE, D) and flattens the moment speculated work fills the decode window. Token spend has no such term. It rises with every additional call you launch. The gap to the right of the dashed line is money spent on results that arrive no sooner.

Zhang names the real failure mode himself: the tool serving engine clogged with speculative requests. It's nastier than it sounds. Speculative and real calls hit the same engine, so under concurrency (his own benchmark runs 4 and 8 instances against one vLLM server) aggressive speculation makes the thing you were speeding up slower, exactly when the system is busiest. Backpressure and a cap on in-flight speculation aren't a refinement here. They're load-bearing.

Speculative execution in a CPU discards a wrong guess for free. Speculative execution over billed, rate-limited sub-agents discards it for money, and takes capacity from the request you actually needed.

The half that isn't cappedBuild the scheduler, not the speculator

Zhang puts the important claim in his final paragraph: the long-run value is in "more clever JIT compilation tricks to overlap tool calls with the actual REPL execution itself." That's the lede.

Parallel dispatch, running independent calls concurrently because the model wrote them sequentially and never reached for asyncio, is bounded by the width of the dependency graph, not by decode time. Six independent calls is a 6× cut in E. Twenty is 20×. There is no tent. And it needs almost none of the machinery: no streaming API, no incremental parser, no shadow interpreter, no purity allowlist for the common case. You parse the completed cell into a dependency graph and schedule it, a problem with forty years of literature behind it.

The lineage is worth naming, because it tells you which problems to expect. This is promise pipelining, as built in the E language and Cap'n Proto: return a promise immediately, let dependent calls reference it, resolve at the boundary. The dependency analysis is dataflow analysis. The shadow REPL is speculative execution minus the rollback. What's new is the setting: the program is written by a model that doesn't know it should have parallelized, and the harness gets to fix that at runtime. It's a compiler problem living inside an agent.

If you are building thisAn order of operations

  1. Measure E/D and the width of your dependency graph. Two timers and an AST walk over logged cells. An afternoon of work that tells you the ceiling of everything below.
  2. Build parallel dispatch first. Highest ceiling, lowest complexity, no streaming required. If your graphs are wide, stop here. You may have collected the entire win.
  3. Add streaming speculation only if E/D is still near 1 after step 2, and your remaining calls are serially dependent so parallelism can't touch them. That's the narrow band where it pays.
  4. Key promises by call site and occurrence, always. Cheap, and it's the difference between a latency win and a silent change to your sampling distribution.
  5. Cap in-flight speculation, shed under load, and track hit rate as a cost metric. An uncapped speculator degrades the shared engine precisely when you can least afford it.

None of this argues against the post. It's a good one, the implementation is small and readable, and the two design decisions in Fig 2 are worth stealing whatever else you conclude. The claim I'd add is that the interesting result isn't the speedup. It's that a harness executing model-written code has quietly become a runtime, and it inherits every scheduling, caching, and backpressure question runtimes have always had. Speculation is the first of those to arrive. It won't be the last.

That is the argument. Part 2 turns it into a calculator: give it your token rate and your backend's tool latency and it tells you which half, if either, is worth building for your workload.