Fig. 01 / The frameCode, tests, receipts / captured each turn

Open Source Forever / Open Agent Engine

StateFrame. Agents built on state, not transcripts.

Most agents resend a chat transcript that grows every turn. StateFrame never sends chat history. Each request is built fresh from the system instructions, the tools, the task, a typed state frame that holds what the task needs right now, and the newest exchange. Earlier turns stay on your machine, in the run's own records.

  • Local by default
  • Every request on the record
  • AGPL-3.0 or commercial
StatusPreview release

Open source under the AGPL-3.0-only, or a commercial license from Farside Research.

Built and measured on software engineering work, with early live results on document and data tasks that come with an automatic check.

Runs on
macOS (Apple silicon) and Linux (x86_64, tested on Ubuntu 22.04)
Talks to
Your own model server
Checks with
Your project's test suite (pytest today)

Why StateFrame

What a typed state frame changes.

Each point says whether it is measured (we have the number) or by design (it follows from how requests are built; no benchmark yet).

  • Measured

    The prompt does not grow with the task.

    Every request fits a fixed prompt budget (33,344 tokens by default), however many turns came before; that holds whatever budget size is chosen. Measured: across a 300-request run the largest prompt was 32,159 tokens.

  • Tested to 300 requests

    No history compaction.

    There is no chat history to summarize, so nothing is lost to repeated summaries. The engine never summarizes away its authoritative state; to stay in budget it only trims lower-priority detail, and it always keeps a ledger of what was read. Tested so far to 300 requests replayed and about 100 live; it is designed to hold beyond that, and the evidence points that way.

  • By design

    Bigger models on the same hardware.

    A 65,536-token server slot is enough, so memory a long-context cache would take can go to model weights. By design.

  • By design

    More agents per GPU.

    Each agent's cache is capped by its fixed slot, so its memory stays flat however long the task runs. By design; 4 at once is measured below, and how many fit at most is not benchmarked yet.

  • By design

    Steady prompt size every turn.

    The model reads about the same amount at turn 300 as at turn 3, so prefill does not creep up. By design. Engine time per turn now holds steady too: on the final build it stayed about flat across a long run, where the previous build slowed down as the run went on.

  • By design

    Behavior is enforced by the engine, not left to the model.

    The engine owns the tool menu, the guards, the completion checks, the test gates and exactly what the model sees each turn, so the model cannot step outside those rails. Engine settings live in a versioned profile file you can supply with --profile. By design.

  • Early evidence

    Weak spots get fixed in the frame, not by buying a bigger model.

    The engine keeps the bookkeeping (facts, receipts, what was read and tested) in typed state instead of the model's memory, so when a model stumbles you give the frame the missing fact or guard. Early evidence: in our tests one such fix took a recurring finishing failure from 21 of 24 runs correct to 24 of 24.

  • Early evidence

    Work that isn't code.

    Early evidence: tasks that produce a report, a data file, or a plan document, each checked by an automatic test, passed 11 of 11 live runs (5/5, 3/3, 3/3) on the build just before this release. These were short tasks (3-4 model requests each); longer non-coding work hasn't been measured yet.

  • Measured

    Several agents on one GPU.

    Measured: with the default settings, 4 sessions ran at once on one 48 GB GPU (an RTX 4090 48 GB variant) serving Qwen3.6-35B-A3B (a mixture-of-experts model, 35B total, about 3B active, 4-bit): 4 of 4 passed and GPU memory held at about 26.4 GB the whole time, because the model and its shared 262,144-token cache are allocated up front. Sharing has a cost: each session ran about 2 times longer than alone, while total throughput was about 1.9 times one-at-a-time. Measured on short tasks; a stock 24 GB card was not tested.

  • By design

    Any model can take any step.

    Each request is self-contained (task, typed state, newest exchange), so nothing ties a run to one model's history. By design.

  • Measured

    Identical inputs, identical requests.

    Request ids are not random and volatile test output is normalized. Measured on the build just before this release: two runs on the same input sent byte-identical requests on 40 of 40 requests, apart from three clock fields; in live runs an edit-receipt ID can also differ between otherwise identical runs.

  • Measured

    Crash-proof resume.

    stateframe resume continues after Ctrl-C, kill -9, a crash or a lost server: it replays the recorded run without calling the model and checks the next request byte for byte before going live. Measured on this release: a killed long run resumed with its next request byte-identical.

  • Measured

    Steady memory on long runs.

    Run history lives in the run directory on disk, not in memory. Measured (short runs, release-candidate build): the engine's memory footprint held at about 1.7 GB from request 10 to request 60, growing about 1 MB per request; the engine before this fix grew about 10.5 MB per request. The 1.7 GB is a fixed base for repository analysis and grows with repository size. Longer runs are being measured.

  • By design

    You can see exactly what was sent.

    Every request is recorded, with typed state, receipts and ledgers, and stateframe inspect shows the frame, the messages or the exact bytes of any request. By design.

  • By design

    Local first.

    It talks to any OpenAI-compatible server on your machine, such as llama.cpp, uses the server's prompt cache, and only allows loopback endpoints unless you opt in. By design.

Every measured and early-evidence result above was run with Qwen3.6-35B-A3B (a 35B MoE, about 3B active per token, in UD-Q4_K_XL, a 4-bit quantization, served by llama.cpp on a single 48 GB GPU (RTX A6000 class)).

The ceiling

Agents do not fail at the start. They fail at hour two.

A transcript agent carries every earlier turn into the next request. As the work stretches on, the prompt grows, the thread gets harder to follow, and steps get repeated. StateFrame takes a different route: every request is rebuilt from the task's current state and fitted to a fixed budget, however many turns came before.

The long-work curveDrag through a working session
0h2h4h6h8h
Typical agent

Summarizing earlier work and losing details in the process.

StateFrame target

Earlier decisions are still there, reasons included.

Illustration of a well-known failure pattern, not a measurement. Where StateFrame stands today, measured, is further down this page.

What gets sent

Five parts per request. No chat history.

Every request carries the same five parts, rebuilt from the task's current state. Earlier turns are never resent. They stay on your machine, in the run's own records, where you can inspect them.

  1. System instructionsHow the engine expects the model to work
  2. ToolsThe actions the model can take
  3. The taskWhat you asked for, unchanged
  4. Typed state frameCheck results, the code in play, what each recent action did, and what is left
  5. Newest exchangeThe model's last action and its result

Not sentChat historyEarlier turns stay in the run's local records.

Every request, every turn. The typed state frame changes as the work moves. The structure does not.
  • 33,344Default token prompt budget (adjustable)

    Each request is fitted to the budget, however many turns came before. If a task cannot fit what it needs, the run stops with a clear reason.

  • 65,536Default token server slot (adjustable)

    The context slot your local model server needs at the default. It is server capacity, so size the server's cache memory to your machine.

  • EveryRequest on the record

    stateframe inspect shows any request's state frame, its messages, or the exact bytes that were sent.

These are the sizes we built and measured with: a single 48 GB GPU (RTX A6000 class) running Qwen3.6-35B-A3B, a mixture-of-experts model (35B total, about 3B active per token), 4-bit UD-Q4_K_XL, under llama.cpp, with one server sharing a 262,144-token cache across 4 slots (-np 4 -c 262144 --kv-unified), so 65,536 tokens per run. A stock 24 GB card was not tested. They are the defaults; both can be changed for bigger or smaller servers.

Set STATEFRAME_CONTEXT_TOKENS to your server's slot size and the prompt budget follows at the same ratio (32,768 gives 16,672; 131,072 gives 66,688); STATEFRAME_PROMPT_BUDGET sets the budget directly. The server's slot must equal the setting exactly. Only the default 65,536 slot is measured on long runs; other sizes are tested on short scripted runs.

Why now

The model is no longer the bottleneck.

Open models can now fix real bugs and build real features on your own hardware. What holds them back is everything around the model: what it is shown each turn, how its work is checked, and how a long task stays on track. That layer is what StateFrame is.

Where it works today, and where it does not yet

  1. Works today

    More source context for multi-file work

    In 0.2.2, current editable product code appears before notes about what was read and changed. Coverage lines show which source ranges are in view and whether a file changed since its last read when that can be checked. Files up to 60 lines and 2 KiB can be shown in full when the request budget allows. Bookmarks are on by default and keep a failing test and related product code in view when the check provides a usable source location. On a recorded multi-file task that stopped on every previously observed build, all 5 of 5 live runs on the release build completed and were submitted with all 14 tests passing. This is a small sample on one task, not a general success rate. An offline rebuild of one recorded request showed all 11 product files in full, up from 1 of 11, while the complete request fell from 85,664 to 77,017 bytes.

  2. Works today

    Bug fixes

    A focused fix with tests that decide pass or fail. The demo bug fix finishes in three model requests.

  3. Works today

    Features across several files

    A 7-file feature passed all of its protected tests and 17 of 18 hidden edge-case tests, with Qwen3.6-35B-A3B self-hosted on a rented cloud GPU.

  4. Works today

    Any test command as the check

    In 0.2.7, a run can use any test command as its check, such as a Go, Rust, JavaScript or shell test runner, and the exit code and a short failure excerpt go into the frame. You choose how much of your existing tests a run may edit: none, all, or only the tests that judge the task. Every changed test is reported, and a result is qualified when tests look weakened.

  5. Works today

    Hooks for your own checks and gates

    In 0.2.6, you can add opt-in hooks that run at the start of a run, before and after a tool call, before a submission is accepted, and when a run ends. A hook can add context, veto a tool call, or hold a submission until your own check passes. Hooks run in the same sandbox as other commands, and their results are recorded so a resumed run replays them. With no hooks configured, nothing changes.

  6. Works today

    Requests go straight to your model server

    In 0.2.5, model requests never go through an HTTP proxy, whether the proxy is set in a profile or by the macOS system settings. Earlier releases could send a run's requests, including the task text and the API key, to such a proxy.

  7. Works today

    Skills and MCP tools

    In 0.2.4, you can install a reusable instruction bundle as a skill and select it for a run; the agent sees a short catalog and loads a skill only when it needs it. Trusted local MCP servers that speak stdio can add tools with --mcp. Each server call runs inside the same sandbox as other commands and is recorded for resume.

  8. Works today

    Shell, jobs and your own tools

    Sandboxed shell commands (builds, linters, `df -h`, dependency installs into a run-local environment), background jobs such as a dev server that the agent can wait on until it exits, prints a line or opens its port, file moves and deletes, read-only Git history, opt-in web fetch, and command tools you define. Every command's effects are checked and recorded for resume.

  9. Not yet

    Larger changes and very long tasks

    Changes of around 10 files or more, and tasks past roughly 25 model turns, can stop with a refusal. Nothing is lost: the partial result is saved.

Where it stands

A preview. Here is what we measured.

These numbers come from the preview engine, measured on software engineering tasks, on one rented cloud machine with one self-hosted open model (Qwen3.6-35B-A3B, a 35B MoE with about 3B active per token, in UD-Q4_K_XL, a 4-bit quantization, served by llama.cpp on a single 48 GB GPU, RTX A6000 class), before the final build. Re-measuring on the final engine is pending. What does not work yet is listed too.

Real repositories13 of 14 complete

Recorded real tasks run through the released path

  • 13 completed
  • 1 long task stopped with a clear refusal
14 recorded real software tasks, run through the released path on the preview engine.

The one stop is a long task: once its task state no longer fit the prompt budget, the engine stopped with a clear refusal instead of sending a broken request. Nothing is lost when that happens: the partial result is saved. A real multi-file bug fix in a production Python codebase was also solved live with Qwen3.6-35B-A3B and passed hidden tests.

0 crashes across all 14 runs.

Demos3 of 3 pass

Demos on the final build

  • PassSmall bug fixFix a pricing bug so the tests pass
  • PassMulti-file static siteBuild a site from a short spec
  • PassInteractive chatDiff, undo and apply in one session

Each demo runs end to end on the final build with Qwen3.6-35B-A3B self-hosted on a rented cloud GPU.

A bigger taskA 7-file feature

Share of tests passed on a 7-file feature

  1. Protected tests
  2. Hidden edge cases
  1. All pass
  2. 17 of 18
Share passed. The hidden tests were never shown to the model.

With Qwen3.6-35B-A3B self-hosted on a rented cloud GPU, the feature passed all of its protected tests and 17 of 18 independent edge-case tests. Changes of around 10 files or more can still stop with a refusal.

One real run3 requests, 102.4 seconds

Prompt tokens per request in one bug-fix run

  1. Request 1
  2. Request 2
  3. Request 3
  1. 7,298
  2. 10,983
  3. 10,233
Prompt tokens per request. The dashed line is the 33,344-token budget.

A pricing bug fixed with Qwen3.6-35B-A3B: read the code, make one edit, tests pass, done. Every request was built from state, and each stayed well inside the budget.

SpeedAbout 89% model time

Share of wall time in one live run

  1. Model generation
  2. Everything else
  1. About 89%
  2. About 11%
Share of wall time. One live run, Qwen3.6-35B-A3B, a single 48 GB GPU (RTX A6000 class).

On a single 48 GB GPU (RTX A6000 class) in one live run, the model's own generation took about 89% of the wall time, and the engine added about 19 seconds per turn.

How it works

Every turn, rebuilt from state.

No magic prompt. Every turn runs the same loop, and the model owns only one step of it. The engine carries out the action, records it, checks it, and builds the next request from what actually happened. Every piece of it is in the code, open for you to read.

  1. The model

    The model proposes the next action

    Read a file, make an edit, run a shell command or the checks, or say the work is done. The model only proposes. The engine carries it out.

  2. The engine

    Run in a sandbox, on a copy

    Work happens on a copy of your repository. Shell commands run in a sandbox that can only reach your own machine unless you allow the network, checks get no network at all, and the agent cannot edit the tests that decide pass or fail.

  3. The engine

    Every request and result recorded

    Each request and each action result is kept in the run's local records. stateframe inspect shows any request's state frame, its messages, or the exact bytes that were sent.

  4. The engine

    Checked, not announced

    Check results flow into the next state frame. When the model says it is done, the engine checks the result, runs your checks again on it, and prints the diff with an outcome line.

  5. The engine

    The request is rebuilt from state

    System instructions, tools, the task, the typed state frame and the newest exchange. Nothing is appended to a transcript, because there is no transcript to send.

  6. The engine

    Fitted to a fixed budget

    The state frame and the selected source code are fitted into the prompt budget (33,344 tokens by default), however many turns came before.

  7. The engine

    Qualified before it is sent

    The engine checks that the request is complete, within budget, and addressed to an allowed endpoint. If a task cannot fit what it needs, the run stops with a clear reason instead of sending a broken request.

Beneath every turn

  • Your repository stays yours

    Nothing in your repository changes unless you ask. With --in-place, a result is applied only when its checks pass. In chat, /diff shows an attempt, /undo drops it, and /apply applies it.

  • Local by default

    Only loopback endpoints are allowed. A remote endpoint needs an explicit override and prints a warning that your source code will leave the machine.

  • Stopped runs pick up where they left off

    stateframe resume continues a run stopped by Ctrl-C, a crash, power loss, or a lost model server. It replays the recorded run without calling the model, checks the next request matches the recorded one byte for byte, then continues live.

Getting started

One command to install. Your model, your machine.

StateFrame runs against a model server you already control. Install it, point it at your server, and give it a repository and a task. Without --in-place, the result stays in the run directory and your repository is untouched.

  • macOS or Linux

    macOS on Apple silicon (sandbox-exec, built in) or Linux x86_64 (tested on Ubuntu 22.04; needs bubblewrap: sudo apt install bubblewrap). Docker is not supported.

  • A few GB of free disk per long run

    Measured on a large repository: a run directory uses about 3 MB per request (about 550-750 MB for a 209-request run), plus checkpoints while the run is live if you turn them on.

  • Python 3.11 or newer

    With venv and ensurepip. The installer pins and hash-checks its dependencies, with an offline fallback.

  • A local model server

    Any OpenAI-compatible chat server with tool calling, such as llama.cpp, with a 65,536-token context slot at the default settings. From 0.2.3, requests are sent uncompressed, so a stock llama-server works directly; earlier releases compressed requests of 32 KiB or more, which llama-server refuses. These are the sizes we built and measured with: a single 48 GB GPU (RTX A6000 class) running Qwen3.6-35B-A3B, a mixture-of-experts model (35B total, about 3B active per token), 4-bit UD-Q4_K_XL, under llama.cpp, with one server sharing a 262,144-token cache across 4 slots (-np 4 -c 262144 --kv-unified), so 65,536 tokens per run. A stock 24 GB card was not tested. They are the defaults; both can be changed for bigger or smaller servers.

  • A model that handles tools well

    The results above were measured with Qwen3.6-35B-A3B, a mixture-of-experts model (35B total, about 3B active per token), 4-bit UD-Q4_K_XL, under llama.cpp on a single 48 GB GPU (RTX A6000 class); other models and smaller GPUs are untested.

In a terminal, from the StateFrame folder
./install.shexport PATH="$HOME/.local/bin:$PATH"export STATEFRAME_ENDPOINT=http://127.0.0.1:8080/v1export STATEFRAME_MODEL=my-local-modelstateframe run --repo examples/discount_bug --task "Discounts are charged wrong at checkout: a 25% discount on an 80.00 order should cost 60.00, and a 100% discount should be free. Fix the pricing code so the tests pass."stateframe chat --repo path/to/your-repostateframe inspect <run-dir> --send 2 --framestateframe resume <run-dir>

Known limits

What it does not do yet.

This is a preview. These are the limits known today, written down before you hit them.

  1. Very long tasks and big changes

    Tasks past roughly 25 model turns, and changes of around 10 files or more, can stop with a refusal when the task state no longer fits the prompt budget. Nothing is lost: the partial result is saved.

  2. Re-reading on very long, hard tasks

    In 0.2.2, current editable product code appears before notes about earlier reads and navigation. A completed batch of proved unchanged reads counts once against the no-progress watch, regardless of file count. Mixed or unproved batches keep per-action accounting. Repeated unchanged reads can still stop the run, and partial changes are saved for inspection. Bookmarks are on by default, but need a failing check with a usable source location. Setup or collection failures, and failures without a file and line, do not activate them. Very large repositories still show selected windows, not every file. The optional read restriction is off by default. When enabled, it can hide reading actions for up to two requests after unchanged reads; failing checks, bookmarked editable source, and the edit and check route stay available. The run can stop before the restriction helps if the no-progress watch is one step from the limit.

  3. Two models tested, one or two runs each

    Tested with Qwen3.6-35B-A3B (local) and DeepSeek V4 Flash. On this release Qwen3.6-35B-A3B solved the HTTP transport task, and DeepSeek V4 Flash solved the 8-file batch refactor in 2 of 2 runs. On that refactor Qwen3.6-35B-A3B ended in a no-progress stop, as the previous release did the same day. That is one or two runs per model and task, so this is a smoke test and not a success rate. Other models are untested.

  4. MCP is stdio tools only

    From 0.2.4, trusted local MCP servers that speak stdio can add tools with --mcp. MCP over HTTP, resources, prompts and sampling are not supported, and a new engine-owned tool still needs code. Background jobs are not relaunched after a resume.

  5. Checks are yours to name

    From 0.2.7, the check can be any test command, not only pytest, and a repository without tests can name a check or run without one. A run with no check evidence is labelled "unverified: review the diff" and is never reported as verified.

  6. Linux: tested on short runs

    Linux x86_64 is tested end to end on short runs (install, demos, the sandbox, and kill -9 then resume). Long runs and the speed and memory numbers were measured on macOS. Linux arm64 and Ubuntu 24.04 are untested.

  7. Resume replays the whole run

    On this release resume replays the whole recorded run, and checkpoints are off by default: in our tests a checkpoint restore did not rebuild the next request faithfully. The replay takes time in proportion to the run's length. Turning checkpoints on (STATEFRAME_CHECKPOINT=save) is safe, because resume checks every restore and falls back to the full replay, but on a large repository each run's checkpoints take about 1.1 GB of disk.

  8. Prompt cache reuse is modest

    Every request is rebuilt as a fixed-size frame instead of appended to a history, so the server can reuse less of the previous request than a chat-history agent does. Measured: about 25% of prompt tokens reused on a real multi-step task (about 94% when a prompt repeats exactly).

  9. Name the files in big repositories

    In a repository with more than 200 source files, --target and --edit say which files a change may touch. The rest stays available as read-only context.

AGPL-3.0-only

Open source, on terms that keep it open.

Farside Research licenses StateFrame under the GNU Affero General Public License v3.0 only. Use it, study it, change it, and share it. If you modify StateFrame and let others use it over a network, the AGPL requires offering them its source; a commercial license is the alternative.

Contributions are welcome. They require signing a contributor license agreement (CLA).

Get StateFrame
Commercial license

Building something closed?

A commercial license from Farside Research lets you use StateFrame in proprietary products and hosted services without the AGPL obligations. It is priced by company size, from a solo developer up.

$9.99 per month for a solo developer, or $99.99 per year. One flat price per company size after that.

See license pricing

Contributing

Come build it with us.

We'd genuinely love your help. Bug reports, small fixes, docs, new benchmarks, or a wild idea about how agents should keep state: all welcome. Open an issue or pull request on GitHub, and first-timers are very welcome too. Code contributions need a one-time CLA (about a minute, and you keep your copyright).

Questions

Before you try it.

Is StateFrame a model?

No. It is the engine around a model. It talks to an OpenAI-compatible chat server on your own machine, such as llama.cpp, and builds each request from typed task state.

Does my code leave my machine?

Not by default. Only loopback endpoints are allowed. A remote endpoint needs an explicit override and prints a warning that your source code will leave the machine.

How big a change can it handle?

A 7-file feature has passed all of its protected tests. Changes of around 10 files or more can stop with a refusal, with the partial result saved.

Is there a time limit?

No time or step limit: a run continues until it is done or you stop it with Ctrl-C, and a stopped run can be resumed. If you want a cap, --max-turns sets one on model turns and --timeout sets one on time.

Who makes StateFrame?

Farside Research, the company behind the Farside deep research platform. We build it, maintain it, and license it.

Do I have to pay for it?

Not to use it in the open. StateFrame is free under the AGPL-3.0-only. A commercial license, priced by company size, is for teams that want to keep their own code closed.

Free to use in the open. Licensed for everything else.

StateFrame is built and maintained by Farside Research, and licensed under the AGPL-3.0-only or a commercial license for closed products and hosted services.