When the window overflows, the common move is to hard-truncate earlier turns — throwing away the working knowledge, and the escape routes, they carried. flowctx is an OpenClaw ContextEngine that makes a different trade-off: fidelity by distance to the current task — the farther away, the harder it's compressed; the closer, the more it's kept intact; whatever you're working on right now stays untouched. The originals remain retrievable on demand. So the prefix cache stays warm, reasoning doesn't slow down, and the task never drops a thread — all three at once, not one traded for another. Three tiers:
flowctx_retrieve(hash) — the compressed view is lossy, the original is not.assemble() projection — on disk, the uncompressed original is always the source of truth.The session just began, with no history yet. Every turn, OpenClaw calls the engine's assemble(), and messages flow into the window passed through by reference — bytes unchanged, the provider's KV cache always hits, no LLM called. Right now there's only this one turn, window usage is low, nothing is compressed, and the right side is all original.
Turns pile up and the assembled size rises to about 52k — still below the 100k gate, so no summary is needed yet. flowctx applies structured compression first: in the past turns that have left the current task, the oversized tool results (whole source files read, long logs run) are deterministically compressed by content type (zero LLM, byte-exact reversible); structurally similar middle turns are simply elided. The current turn stays raw throughout.
sha256[:24] and replaced with a marker:[flowctx: code compressed 8400→190 chars · flowctx_retrieve(hash="a91f…")]Structured compression alone still pushes usage toward the 100k gate. Only now does flowctx reach for summarization — and it does so sparingly: it won't compress the whole history in one shot, it just carves out the oldest segment and folds it into a leaf node (d0-1-3). The rest of the older history queues up, folded segment by segment as its turn comes; the nearer past turns are still only structurally compressed, not summarized. The folded segment leaves behind a handoff note — failed approaches and key identifiers, word for word, and the original can be pulled back by hash anytime.
caller=context-engine.turn.maintenance · input 12,875 → output 3,400 tok · $0.059leafChunkTokens, fires only past the gate — summary is the last resort.The conversation continues, and older history is folded into independent leaves one chunk after another (d0-1-3 · d0-4-6 · d0-7-16 …) — each chunk is its own block by topic, undiluted, so early topics never fade out. When same-level leaves reach condenseFanout, flowctx condenses them into a single depth-+1 parent node d1-1-31; the child leaves are masked and only this one block is injected — so the number of summary blocks stays bounded even in very long sessions. That's "progressive folding": from a single chunk, to many chunks, to condense.
d1-1-31 · childIds=[d0-1-3 · d0-4-6 · d0-7-16 …]The system reaches a steady state, and the right side shows three clean tiers: older history converges into a bounded condense node · recent past turns are structurally compressed · the current turn is always raw, untouched. Originals, scratchpad, and every summary layer are all persisted to SQLite, retrievable byte-exact via flowctx_retrieve(hash / node), rebuilt after a restart with zero LLM calls. This layering holds prefix cache, reasoning performance, and task completion all at once.