Avatar
gokaygokay
All ArticlesTwitter
Agent 2 Program: What Survives When You Compile an LLM Workflow
Agents2026-11-118 min

Agent 2 Program: What Survives When You Compile an LLM Workflow

An agent solves a task once and a program handles it after that. I tested the idea on real data with local models: where the speed is real, where it fails silently, and what a wrapper harness actually saves.

By Gökay Aydoğan

Share:
Explore the project on GitHub

Every time I hand an agent a job it has done before, it starts from zero.

It reads the input, plans, calls its tools, checks itself and writes the answer. The tenth run costs as much as the first, and most of what happens in the tenth run already happened in the first.

The idea I posted was simple. I don't want to change the model. I want a wrapper harness that watches the LLM do a task like "analyze my profile", learns the process, and turns it into a program. The next profile should be much faster, and the decisions left for the model should be smaller than the first time.

That is using an LLM as a compiler instead of a runtime. I asked Fable 5.2 in Claude Code to test it properly on my own machine: public datasets with human labels, real local models, and training, calibration and test data kept apart. It designed nine experiments, ran them on an M4 and stored 18,529 real model answers along the way. The code, results and full report are open.

Download video
The film: what each tier costs, the unseen-group failure, a replay of 2,790 real requests through the production loop, and the harness result. Every number in it is read from the result files.

A program is a ladder of cheaper answers

Compiling does not remove the model. It moves it. Arithmetic, lookups and formatting become code. A judgment such as "which department does this request belong to" becomes a typed decision, answered by the cheapest tier that is sure: an exact cache, mined rules, a small decision model, then one narrow LLM call, and finally the full agent.

On my M4 a local decision takes between 0.2 and 9 milliseconds. One typed call to a local 4B model takes 267 ms, and only because the task contract sits in a cached prompt prefix. Re-reading the contract on every call takes 6.3 seconds.

Median time for one answer at each tier, on a log scale. The gap between the embedding model and the LLM call is where the speed comes from.
Median time for one answer at each tier, on a log scale. The gap between the embedding model and the LLM call is where the speed comes from.

How much the fast tiers can take over depends on the task. With a 2% budget for silent errors, a small embedding model could answer 94% of one assistant dataset and 76% of a banking dataset. On a third dataset no model could be certified at 2% at all. A compile step has to be allowed to say: this decision stays with the model.

Confidence is not familiarity

The dangerous failure showed up when I held out whole groups of requests. The fast model was trained on some intents, then tested on intents it had never seen, with the same set of possible answers.

Calibrated the usual way, on a random split, it kept its 2% promise on familiar groups. On the never-seen groups it answered 76% of requests and 23% of those answers were wrong. That happened in 20 of 20 random splits. 68% of those mistakes were made with a confidence of 0.9 or higher.

Left: wrong answers per request from groups the model never saw. Right: what the fix costs on familiar traffic. Means over 20 random group splits.
Left: wrong answers per request from groups the model never saw. Right: what the fix costs on familiar traffic. Means over 20 random group splits.

The fix is to calibrate against the shift you expect. Hold whole groups out, test them separately, and add a familiarity score that does not depend on the model's confidence. I used the distance to the nearest training example. That cut wrong answers on unseen groups from 17.6% to 1.6% of requests. It also cut the share of familiar requests the fast tier answers from 95% to 63%, and it still went over budget in 35% of the splits, because rows from one group fail together. It is an estimate, not a guarantee.

Typing noise showed the same problem from another side. With 15% of letters mistyped, the embedding model was 51% accurate and still answered 53% of requests, 24.5% of them wrong. A plain character n-gram model answered 43% and stayed at 2.3% wrong. Its confidence dropped with its accuracy.

The same test requests with more and more mistyped letters. Both thresholds were certified on clean data and never changed.
The same test requests with more and more mistyped letters. Both thresholds were certified on clean data and never changed.

A verifier has to fail differently

A fast answer should not be its own verifier. But a second model trained on the same features is almost the same model.

When the fast model was wrong on a never-seen group, an ensemble of five networks on the same embedding gave the same wrong answer 70% of the time. A character n-gram model did so 38% of the time, and a local LLM 32%. The n-gram check costs 0.2 ms and catches 62% of those errors, so it became the default verifier.

How often each second opinion repeats the fast model's mistake on never-seen groups. The more differently a checker is built, the less it agrees with the error.
How often each second opinion repeats the fast model's mistake on never-seen groups. The more differently a checker is built, the less it agrees with the error.

The local LLM was not a safe fallback either. Qwen3-4B was right on 95% of familiar requests and 78% of unseen-group requests. It took its "none of these" exit for only 45% of out-of-scope queries, and it gave an in-scope answer to 10 of 12 prompt-injection strings.

Putting the guarded fast tier in front of it gave the same quality, 11.4% wrong against 12.4% for always asking the LLM, with 0.46 model calls per request instead of one. With the human label as the fallback instead of the LLM, the same fast tier delivered 1.0% wrong answers and escalated 46% of requests.

Programs rot

I compiled a headline router on one year of HuffPost headlines and let it run on the next five. In 2013 it was wrong on 11% of what it answered. In 2018 it was wrong on 65%, and it was still answering. By then 53% of the headlines belonged to sections that did not exist when it was compiled.

Month by month: the share of wrong answers among the headlines the static program still answers, and the share of traffic from sections created after it was compiled.
Month by month: the share of wrong answers among the headlines the static program still answers, and the share of traffic from sections created after it was compiled.

Then a scripted loop over 2,790 held-out requests with four kinds of drift. Anything the program refused went to the human label. The guarded program handled new groups, a burst of out-of-scope requests and noisy input with 1.3%, 2.7% and 1.0% wrong answers. The naive one was wrong on 16% of the whole stream.

The fourth drift was a policy change: requests about physical cards moved from one department to another. The inputs looked familiar and the program was confident, so 81% of the re-routed requests were answered under the old policy. Re-checking 10% of the fast answers did not catch it in the 640 requests that followed, and re-checking 40% took 200. Announcing the change, and switching off the affected answer class at once, brought it to 0%.

Wrong answers per 100 requests across the five phases. The guards handle unfamiliar inputs. They cannot see a change of rules.
Wrong answers per 100 requests across the five phases. The guards handle unfamiliar inputs. They cannot see a change of rules.

Two rules came out of this. A compiled answer has to carry the version of the contract it was compiled for. And a program must never retrain on its own unverified answers: that was the worst policy in the loop.

The wrapper harness, tested

This is the experiment closest to my original post. The agent analyzed public résumé profiles in three free-form calls: extract the facts, assess them, write a summary. The harness recorded 60 runs. For every output field it searched a small family of candidate programs, and admitted one only if it agreed with the agent on 40 other recorded runs.

On 80 profiles it had never seen, the compiled version took 3.8 seconds instead of 11.2. If the prose is still generated by the model, 6.7 seconds.

Seconds per profile for the three-call agent and the two compiled versions, on the same local model.
Seconds per profile for the three-call agent and the two compiled versions, on the same local model.

The decisions shrank more than they disappeared. Only 1 of 6 short fields became pure code. The rest stayed with the model as one narrow, typed call that reads 528 tokens instead of 1,885 and writes 78 instead of 182.

The recordings also audited the agent. In 44 of 100 runs the profile text showed no education at all, and the agent still reported a degree in 33 of them. A rule that copied this habit agreed with the agent on 70% of runs. The best honest rule agreed on 45%.

It went the other way too. A rule written from the task description matched the human label on 83% of the profiles it answered. The agent matched on 67% of the same profiles. Judged by agreement with the agent, that rule was rejected.

So the trace is evidence, not the specification. Compile from the contract, and check against reviewed outputs wherever you have them.

The same test with a frontier model

Everything above used a 4B model on my laptop as the LLM tier. So I gave the same contract and 1,949 of the same held-out requests to Claude Opus 5.5 at medium effort. I had no API key on this machine, so it answered in blind batches through Claude Code subagents. That means real answers, but no latency and no cost figures.

It was a different tier. On never-seen groups it was right on 95% of requests against 78%, with 1.3% wrong answers against 20%. It took the "none of these" exit for 96% of out-of-scope queries against 47%. It gave an in-scope answer to 1 of 12 prompt-injection strings, where the local model did for 11. And when the fast model was wrong on a never-seen group, it repeated the same wrong answer 4% of the time, against 33%.

That flips one conclusion. With the local LLM, the guarded fast tier cost nothing in quality. With Opus, asking the model every time was 1.0% wrong, and the guarded fast tier in front of it was 1.8% wrong while saving 55% of the calls. The fast tier's own wrong answers were 0.9% of all requests. A naive fast tier in front of the same model was 9.2% wrong. The better the fallback, the more a careless compile costs.

What this does not show

Outside that last section the LLMs are 4-bit local models of about 4B parameters. The frontier model ran on one frozen split, in batches, without latency or cost. The last tier in these tests is the human label standing in for an agent. The harness was tested on one workflow, with candidate programs written by the coding agent after reading the recorded runs. And none of this touches perceptual work like video to Blender, where the verifier is the hard part.

I like where it landed. The idea survives with less magic than I hoped and more structure: a ladder of cheap answers, a gate that knows what it has not seen, a checker that is built differently, and a contract that outranks the trace.

Correction, 11 November. The first version of this post said 65% of the re-routed requests were answered under the old policy, and that re-checking 10% of the fast answers noticed after 400 requests. I had counted applications for a new credit card as re-routed, and the policy text does not cover them. The naive program's total was given as 21%. With only the request types the policy names, the numbers are the ones above, and the production-loop figure and the film were redrawn.

Code, the full report and the revised skill →

AgentsResearchCreative EngineeringOpen Source

Enjoyed this article?

Follow me on Twitter for more AI experiments and insights.

Follow @gokayfem

More Articles

View All
Blockout 2 Real: Giving a Grey Scene Editable Materials and Light
01
3D

Blockout 2 Real: Giving a Grey Scene Editable Materials and Light

An image model proposes the look of a grey room. Blockout 2 Real writes it back as UV textures, PBR materials and fitted lights, then measures a real Blender re-render.

Image 2 Low Poly: One Cat, Four Exact Triangle Budgets
02
3D

Image 2 Low Poly: One Cat, Four Exact Triangle Budgets

One cat reference, four Blender meshes: exactly 100, 200, 500 and 1000 triangles. A look at where the triangles go, how the counts are checked, and what the outline scores miss.

Image 2 Sculpt: Turning a Single Image into a Measured 3D Sculpt
03
3D

Image 2 Sculpt: Turning a Single Image into a Measured 3D Sculpt

One image gives you the face, posture and proportions. Image 2 Sculpt builds a sculpt against that evidence, checks the missing views and records the work as a Blender timelapse.

Avatar
gokaygokay

© 2026 All rights reserved.