The problem
A long working session builds up a lot of state: names, decisions, code, and a list of things that did not work. When that session has to move to a fresh chat or a different model, the only thing that crosses over is text.
The usual options are a JSON-style payload or a prose summary. JSON spends tokens on quotes, braces and repeated key names. Prose spends them on grammar and narration. Neither keeps a clear record of what was rejected and why.
Some research skips text and shares a model's internal state, such as its KV cache. That needs access to the models' internals, and the models must be closely related. A hosted, closed model does not expose that state, so these methods cannot move a session from one provider to another.
What already existed
- DroidSpeak: a research system that lets one LLM reuse another LLM's KV cache, as long as both share the same architecture, for example models fine-tuned from the same base. It works below the text layer, so it does not help with closed models.
- LLMLingua-2 (Microsoft Research): a small model decides which tokens of a prompt to keep and which to drop. The result is a shorter copy of the original text, not a structured record of decisions and rejected paths.
- Sparse Priming Representations (SPR): a prompting technique that distills text into a short list of statements and associations that a model can expand again. It stays in natural language and has no fixed place for code signatures or rejected approaches.
- Gibberlink: a demo in which two voice agents, once they recognise each other as AI, switch from speech to a data-over-sound protocol (ggwave). It is about audio between voice agents, not text in a context window.
- MCP and A2A: open protocols for connecting models to tools (MCP) and agents to each other (A2A). They define how messages are exchanged, as JSON-RPC, but they do not compress the context of a session.
Design ideas
- Pointer dictionary. Each recurring name is defined once in
@MEMas$1,$2and so on. Every later line uses the pointer instead of the name. - Positional fields. Slots are separated by
|and their meaning comes from their position, so there are no JSON keys or quotes to repeat. - State operators.
:=means defined as,->means flow or resolution,!marks a rejected path,^marks its locked replacement, and?marks an open task. A line such as@LOG !sync_ping(timeout) -> ^async_arprecords what failed, why, and what replaced it, so the next model does not suggest it again. - Plain ASCII. BPE tokenizers often split unusual Unicode symbols into several tokens, while common ASCII sequences such as
->and:=are usually one token each. So the format sticks to ASCII.
From idea to the shipped format
The idea started in a brainstorming chat about moving a session between models. The first version was an "emergency eject" prompt: paste it into a chat that is about to run out of context, and the model writes a snapshot. That prompt used @MEM, @ARCH, @DECISIONS, @CODE_SIG and @NEXT.
AILv1 shipped with six sections: @MEM, @CTX, @ARCH, @LOG, @SIG and @NEXT. @DECISIONS became @LOG, and @CODE_SIG became @SIG. The grammar is in the README, and the decoder rejects anything outside it.
The prompt also became real code. The Python encoder redacts secrets first, extracts @SIG lines from Python with the ast module, and adds pointers only where they save tokens. The decoder only parses, checks every line, and treats a snapshot as data, not instructions.
The format is not always smaller. On a short paragraph it can cost more tokens than the prose. The benchmarks in the README show where it helps and where it does not.
- No install: prompt-only encoder
- Exact signatures and redaction: the Python encoder and its CLI
- Measured sizes: benchmarks