The last time I did one of these, StateFrame had just gone from 0.2.2 to 0.3.40 in four days and I was a little embarrassed about it. Fifty releases sounded like a project that couldn't sit still.
Well. It didn't sit still.
Since then StateFrame has gone from 0.3.40 to 0.7.3. That's 111 more releases on GitHub in eleven days, and the changelog grew by about 490 entries. Some of those releases are one-line fixes. A few of them are the biggest changes the engine has had since day one. (0.7.4 shipped while I was finishing this post.)
- Releases since the last post
- 111
- 0.3.40 through 0.7.3, published on GitHub, Nov 5 to Nov 15
- Changelog entries
- ~490
- Fixes, features, tests and docs, every one written down
- Minor versions
- 4
- 0.4 caching, 0.5 mutations, 0.6 hardening, 0.7 subframes
- Prompt budget, every request
- 33,344
- Tokens. Still fixed, however long the run goes
Here's how they landed. The gap in the middle is real: for a few days the crew was busy getting Farside's research app out the door, and StateFrame mostly sat still.
#The four big ones
Most releases are small on purpose. Every one has to pass the full test suite before it ships, and small is how that stays honest. But four of them changed what StateFrame is.
#0.4: a prompt the server can actually cache
StateFrame never sends chat history. Every request is rebuilt from the instructions, the tools, the task, a typed state frame and the newest exchange. That's what keeps the prompt inside a fixed budget at turn 300 the same as at turn 3.
The catch is that a model server's prompt cache only helps when the start of a request matches the start of the last one. Rebuild the request from scratch every turn and you can shuffle bytes the server already had.
So 0.4.0 split the frame in two. The parts that change every turn (the action ledger, recent events, open sites, code bookmarks) moved into a volatile section near the end. The parts that hold still stay up front, in the same order, request after request. Source code the model has already seen keeps its place, and new files get appended at the end instead of reshuffling everything. That layout is on by default. Hosted APIs get the same layout too, but the cache win I've measured is on llama.cpp. On a hosted provider, its own caching decides the rest.
There's a stricter mode on top of it, STATEFRAME_STABLE_WIRE, which keeps every delivered source window byte-identical until something actually changes. On recorded runs, the headroom setting I shipped (16,000 tokens) cut fresh request changes by 10.3%, against 7.9% for the next best setting, with no compaction at all. It's still opt-in and off by default, because I'm not done measuring it. A lot of the 0.6 changelog is stable wire edge cases found by running it hard on the foundry.
A related win from 0.4.12 and 0.4.13: StateFrame now makes fewer copies of each request while it builds it (from 10 deep copies to 4 on the test fixture), and median time per request dropped about 10% on a 22-file example. That's three runs on a loaded host, so treat it as a direction, not a promise.
#0.5: mutations
This is the one I've been most excited to write about. Mutations let the agent build the tools it needs, on the fly, in the middle of a task.
Every agent you've used works out of a fixed toolbox. Somebody decided ahead of time what it can read, run and change. When a job needs a tool that isn't in the box, like a file format nobody wrote a reader for, the agent is stuck. It pokes at the problem with whatever it has, burns request after request, and usually gives up.
Mutations take that wall away. When the agent hits a job it has no tool for, it writes one right there: a small program built for exactly that job. StateFrame runs it in a sandbox, makes it pass a smoke test, and only then lets the agent use it. The toolbox grows to fit the task, instead of the task having to fit the toolbox.

Here's what that looked like in our tests, all on Qwen3.6-35B-A3B, an open model with about 3 billion active parameters that's small enough to fit on many local GPUs. Not a frontier model:
- It wrote a file reader it didn't have. The task needed an answer out of a compressed binary format with no tool for it. The agent wrote a 22-line reader for it, StateFrame smoke-tested it in the sandbox, and the agent used it to get the right answer. The task's own test went from failing to passing in 10 requests.
- It fixed its own mistake. Its first draft of that reader asked for the file the wrong way and failed its smoke test. StateFrame handed back the exact error, and the next version passed and went to work.
- It peeked instead of reading everything. One call gave it a data file's columns, row count and a few sample values.
The engine stays in charge the whole time. Helpers run in a sandbox with no network and no write access unless you grant them, and their output never counts as a passing check. The whole story, with every guardrail, is in its own post.
#0.6: hardening the cache-friendly layout
0.6.0 doesn't have one headline feature. It's dozens of fixes that make the cache-friendly layout hold up when things get messy: resuming a run mid-flight, a file changing under the model, a send that lands a few tokens over the line. The rule that came out of it for stable wire is simple: a resumed request has to rebuild the exact same bytes it sent the first time, or StateFrame says so and starts a fresh layout instead of guessing.
Late in 0.6 (shipped in 0.6.10), the frame also learned to notice when a decision has gone stale. If the model decided something about a file and a later edit changed that file, the decision now reads "STALE since edit to ..., recheck" instead of quietly pretending nothing happened.
#0.7: parallel subframes
0.7.0 shipped early this morning, and it's my answer to sub-agents.
The usual way to split up agent work is to hand pieces to other agents, each with its own chat history, a brief going in and a report coming out. Every one of those hand-offs is a chance for the truth to get blurry. Subframes skip all of it. StateFrame forks the task instead: each subframe starts from a frozen snapshot of the workspace, works only on the files it owns, and hands back a sealed proposal. The parent checks every proposal before joining it into its one source of truth.

No briefs, no reports, no drift between agents. It gets its own post too.
#Smaller things worth knowing
- Hosted endpoints work properly now. DeepInfra and other OpenAI-compatible providers that aren't llama.cpp get the same treatment as local servers, with token estimates checked before every send. A mock hosted endpoint now runs a multi-request check on every release.
- Clearer failures. Refusals, fallbacks and kills now say why, in numbers: the measured value against the limit it hit, what was dropped to fit, which part of the engine stopped a process and how long it had been running. "Something went wrong" is not an acceptable error message in an engine that keeps receipts for everything.
- Checks that don't die for no reason. A check killed by a signal now names the exact limit that stopped it. The CPU limit on checks went from 300 seconds to a 3,600-second backstop, because a slow but healthy test suite on a busy box is not a runaway.
- Faster checkpoints. Saving a checkpoint takes one file check per unchanged file instead of three, about 2.7 times faster on a 9,000-file tree. Changed files go into a shared content store, so checkpoints stop copying what they already have.
- Small model contexts fail cleanly. If a model's context is too small for the task, StateFrame now tries every safe way to fit first and then stops with the measured minimum it needs, instead of failing halfway in.
#The security fixes
Same deal as last time. I'd rather you hear these from me.
- 0.4.30: when a server redirected a request to a different host, scheme or port, StateFrame could carry authentication, API key and cookie headers along. It now strips them before following.
- 0.4.37: proxy credentials now stay with the proxy. A direct request drops any proxy credentials a caller supplied, and proxy credentials no longer come back after a cross-origin redirect.
- 0.4.42: on Linux, StateFrame could reuse a cgroup named
supervisorthat it hadn't created, which meant commands could inherit someone else's limits. It now only reuses one it can prove it made. - 0.7.3: I found an old, unused piece of code that could start a check outside the sandbox. Nothing called it, but "nothing calls it" isn't a security model. It's gone, and a test now blocks any new process call in the engine that skips the sandbox.
If you run StateFrame, update.
#Mistakes I shipped
- Hosted endpoints broke after a few requests from 0.3.37 to 0.4.22. On providers that aren't llama.cpp, runs could stop after two to four requests with a "provider identity changed" error. StateFrame was cleaning internal fields out of the request after measuring it, so its own pre-send check saw a different request than the one it measured. It measures the cleaned request now. Local llama.cpp users never saw it, which is exactly why I didn't either.
- Five 0.6 versions never got their own GitHub release. 0.6.4, 0.6.5, 0.6.7, 0.6.8 and 0.6.9 are in the changelog, and their changes shipped inside the next published release (0.6.6 and 0.6.10), but they don't have release pages of their own, and those two release pages don't list them. Not dangerous, just untidy, and it's why the changelog has 116 versions and GitHub has 111 releases.
--parallelcan stop a run. Found this one today on a live run on 0.7.3: with parallel turned on, the run ended with an engine error on its fourth request, before it split anything. The fix didn't make it into 0.7.4, so leave--paralleloff until a later patch.
#What's still rough
- Longer runs. Most of my live numbers are still from short tasks. The prompt budget has held to 300 requests replayed and about 100 live, and I'm designing for far beyond that, but I haven't measured far beyond that yet.
- Mutations are Linux-only. macOS is planned.
- Agents per GPU. I measured 4 agents on one 48 GB card. How many fit at most is still not benchmarked, and I'm not going to guess in public.
- Upgrading mid-run. A run started on one release still can't resume on another. Finish it or restart it.
#Why this pace works
Same answer as last time, with more hardware behind it. The foundry now has two servers instead of one (more on the new one here), so release gates no longer fight the agent crews for CPU. Every release is small enough to test on its own and ship the same day. A security fix never waits for a feature.
The full list is on the releases page, and the README says for every claim whether it's measured or by design. If your results don't match mine, open an issue with your setup. I mean that. Numbers from hardware I don't own are worth more than anything I can test myself.
