![]()
Every context compression is a bet. The agent picks what matters, folds the rest into a summary, and the raw exchange is gone. Ninety-five times out of a hundred the bet is fine. The hundredth time you ask "what was the exact error?" and get a confident paraphrase of something that was, forty minutes ago, sitting right there in the transcript.
That's not a summarization-quality problem. You can't fix it with a better prompt, because the information no longer exists. Compression was an amputation.
0.43.0 makes it a fold instead. Drained messages are archived verbatim, the summary cites them by content-addressed ID, and the agent gets a recall tool that pulls the originals back on demand. Context narrows under pressure and re-expands exactly where the work needs it.
The same release replaces the compression pressure ladder with an engine that computes depth from measured session dynamics, adds Agent Plugins 1.0.0 support, and closes a browser-origin hole in the WebSocket server that deserves your attention before you upgrade a dashboard.
Recall: the archive is addressable now
Here's the shape. When compression drains a range of messages, they're written verbatim to a per-session JSONL archive, and a sidecar index assigns each block a content-addressed ID — b:1a2b3c4d. The summary's <folded_state> units cite those IDs in their refs, and an <archive> pointer names the file.
Then the model can ask for them back:
recall(ids: ["b:1a2b3c4d", "b:9f0e1d2c"])Up to two block IDs per call, digest-verified against the archive, returned as the exact original messages. The tool description tells the agent the rule that matters: do not guess elided content — recall it.
Three design decisions are doing the real work here:
Recall appends, it never rewrites. The recalled text comes back as an ordinary tool result at the tail of the conversation. Nothing earlier in the context is mutated, so the prompt cache prefix survives intact — a recall costs you a tool round, not a full re-billing of the session.
It folds back automatically. Recalled content is a normal message. The next compression cycle treats it like any other tail content: once it stops being referenced, it drains again — back into the same archive, still addressable. You don't accumulate a growing pile of re-expanded history.
It's bounded on purpose. Two blocks per call, capped by the same mcp_response_tokens_threshold truncation as every other tool result. Recall is a scalpel for "what was the exact stack trace," not a mechanism for undoing compression wholesale.
recall is advertised whenever the PACT attention or governance machinery is active — and governance is on by default — so it's present in a default install without touching your config. It errors clearly before the first compression, because there's nothing archived yet.
The pressure ladder is gone
The other half of the compression work is the trigger. This one is a breaking config change, so read this section even if you skim the rest.
Octomind has had a three-rung pressure ladder since compression shipped: light / medium / aggressive thresholds, each with a target_ratio, the matched ratio then scaled by tool density and clamped. It worked, but it encoded a guess — that the right compression depth is a function of how much context you have, when in practice it's a function of how fast the session is growing and how much runway is left before the ceiling.
So the ladder is out and a single trigger is in:
[compression]
threshold = 90000That's the fire line — the point where compression becomes eligible. Depth is no longer configured. It's computed per cycle:
target_after = ceiling − runway × growthgrowth— measured output tokens per API call since the last compression checkpoint (lifetime average before the first one). Output only, because input re-bills the cached context and isn't new material.runway— predicted remaining calls: work-remaining-≈-work-done, corrected by how accurate the previous prediction turned out to be.ceiling— the lower ofmax_session_tokens_thresholdand the model's physical window minus the reserved completion budget.
The target is clamped so the derived ratio lands in [2.0, 16.0], and it has to fall at least five turns of measured growth below the fire line. A compression that would immediately re-fire is refused outright — you get a cooldown instead of a paid call that buys nothing.
The behavior you'll notice: a hot session — heavy tool use, long predicted runway — compresses deep and buys a long quiet stretch. A session winding down compresses gently and keeps fidelity where it still matters. Neither one required you to guess a ratio in advance.
There's a cache benefit that isn't obvious. Compressing toward a computed watermark keeps the post-compression prefix size consistent across cycles, which is exactly what keeps prompt caching effective. The old ladder produced a different prefix every time.
Two more trigger details worth knowing. On models with small windows, the fire line is pulled down automatically so at least five turns of growth still fit beneath the ceiling. And forced /done compression skips the controller entirely and uses the gentlest 2.0x — a task boundary has no session dynamics to project onto the next task.
Your config migrates itself
Config version moves to 4. The migration takes the lowest threshold from your [[compression.pressure_levels]] blocks and carries it over as compression.threshold, then drops the ladder. Your values and comments survive; nothing else is touched.
If you'd hand-tuned ratios, they're gone — not silently ignored, actually removed, because depth is a runtime decision now. If you'd tuned the lowest threshold because compression was firing too early, that tuning carries forward exactly as you meant it.
To disable compression entirely, set threshold = 0. The old "empty the pressure_levels array" trick no longer has anything to empty.
The full mechanics are in the context compression docs and the configuration reference.
Agent Plugins 1.0.0
Skills gave you portable instructions. Capabilities gave you tool bundles. What was missing was the package: a single directory someone can hand you that carries both, with no config edit on your side.
That's Agent Plugins 1.0.0, and 0.43.0 implements it. A plugin is a directory:
my-plugin/
plugin.json # manifest: $schema + name
skills/
review-api/
SKILL.md # standard Agent Skills format
mcp.json # optional: MCP servers this plugin bringsOctomind scans three locations, in priority order: your taps' plugins/ directories, <workdir>/.agents/plugins/ for project-local plugins, and ~/.config/agents/plugins/ for global ones. Plugins dedupe by manifest name, first source wins, and plugin skills sit at the lowest priority behind your own — a plugin can't shadow a skill you wrote.
The interesting part is the MCP wiring. A plugin's mcp.json servers aren't started at boot. They load when one of that plugin's skills is activated, with refcounting, and they're offloaded when the last skill using them is forgotten. Config-level servers you declared yourself are never touched. A plugin's tools exist while you're using the plugin and not a minute longer — which is the same dynamic tool-surface model skills already use, extended to a shipping unit someone else can author.
Servers get PLUGIN_ROOT and PLUGIN_DATA in their environment, and ${PLUGIN_ROOT} / ${PLUGIN_DATA} expand inside args, env values, and cwd. PLUGIN_DATA is a per-plugin persistent directory that Octomind creates before any server runs, so a plugin has somewhere to keep state without writing into its own install.
What a plugin is not allowed to do
Running someone else's directory as a set of child processes is exactly as dangerous as it sounds, so the loader is strict:
commandmust be a single token — a bare executable name or a./-relative path inside the plugin root. Not a shell string. Whitespace in the command is a rejection, not ash -c.- No escaping the plugin root.
./-relative commands andcwdvalues resolve inside the plugin, and any..component rejects the entry. - Reserved env keys are refused. A server declaring its own
PLUGIN_ROOTorPLUGIN_DATAis skipped rather than being allowed to lie to itself about where it lives. - Non-loopback HTTP must be HTTPS. A plain
http://URL to anything other than localhost is rejected. - Unsupported auth fails loudly. Our HTTP transport carries no custom headers, so an entry declaring
headersis skipped instead of connecting unauthenticated and failing in a confusing way later.
Failures are component-isolated, per spec. A malformed plugin.json rejects the whole plugin. A malformed mcp.json disables that plugin's MCP servers while its skills still load. One bad server entry skips itself and lets its siblings start.
Browser origin allowlisting — read this before you upgrade a dashboard
This one is a security fix with an upgrade action attached.
WebSocket upgrades are covered by neither CORS nor the same-origin policy. Before 0.43.0, a octomind server bound to 127.0.0.1 would accept a handshake from any page you happened to visit — including a third-party iframe — and that page could drive your agent with the full configured toolset and read every response frame back. Loopback binding was never a boundary against this.
Now the server refuses any handshake carrying an Origin header that isn't allowlisted, with a 403 before the welcome frame:
octomind server --allow-origin http://localhost:3000 --allow-origin https://dashboard.example.comThe flag is repeatable, origins match exactly as the browser sends them (scheme, host, port, no trailing slash). Native clients — websocat, scripts, anything that isn't a browser — send no Origin header and keep connecting with no configuration.
If you run a browser dashboard against octomind server, it will stop connecting until you pass --allow-origin. That's the intended failure mode: the fix is worthless if it's opt-in.
While we were in there, the WebSocket docs grew a section spelling out something that was true but unstated: the server has no notion of a user. One process serves one identity — config, MCP server processes and OAuth tokens are process-global, so every session's tool calls go out with the same credentials, and session_id is a name, not a capability. To serve multiple users, run one process per user with its own HOME.
The supervisor got cheaper and stricter
Four changes, and they pull in opposite directions on purpose.
answer_only task classification. The classifier can now mark a turn as an answer, not a task. When it does, an existing plan no longer applies to that turn — asking "wait, why does this function take a mutex?" in the middle of a planned refactor stops dragging the whole plan apparatus into a question that just needs an answer.
Plan adoption nudge. The mirror image: when a task visibly grows into multi-step work with nothing tracking it, the supervisor injects one advisory suggesting a plan. Deduplicated to fire once per user turn — it's a nudge, not a nag.
Enumerated items need exercised evidence. When your request names the items it covers — endpoints, files, cases, types — the verify gate now holds each one to evidence that demonstrably ran or probed that item. This applies equally to items the agent claims it fixed and items it claims were "already correct": a correctness claim about an enumerated item is a verification claim, and "I read it, it looks right" is not one. One global green check counts for an item only if the recorded output shows that item exercised. The bar applies strictly to items you enumerated — never to surfaces the gate infers on its own.
Condensation now budgets per round. [supervisor.condense] triggers on the total estimated tokens across a round's text results rather than any single result, so five middling outputs get narrowed the same as one huge one. Each result gets a proportional token budget, small results below a floor are skipped entirely, larger results are prioritized, and the model is shown the estimated token cost of what it's keeping. A single failed verdict no longer discards the whole round's condensation — that result is kept whole and the rest proceed.
Fixes that matter more than their diff size
The verify gate could silently switch itself off after a compaction. This is the one to know about. The gate read the live task straight out of the latest user message — but after compaction, the raw user turns are drained and the request survives only inside the <continuation> wrapper's <task>. So the read returned empty, and an empty task made verify() fail open. Not error — fail open, quietly, for the remainder of the session. Exactly the sessions that need verification most were running without it. The task is now read through the continuation-aware helper.
Compression was cost-gated off on cache-less providers. The net-benefit calculation priced the base context at the cache-read rate, which is right for providers that cache and badly wrong for providers that don't — they re-bill the entire context at full input price every call. On glm-5.2 that's $0.26/M assumed versus $1.40/M actual: a ~5x understatement of the do-nothing cost, which cost-gated compression off precisely where it pays most. The calculation now branches on the provider's own caching capability.
Background state survives compaction. Active monitors and schedules are injected into the compressed conversation entry, so an agent that just got compacted doesn't re-schedule a job it already has running.
Default max_tokens moved from 16,384 to 32,768. Modern models were being clipped by a ceiling set for older ones.
Learning skips retrieval when there's nothing to retrieve. A fast directory check short-circuits query preparation on projects with no stored lessons — startup work that was pure waste for every new project.
Upgrading
# Homebrew (macOS / Linux)
brew install muvon/tap/octomind
# or from crates.io
cargo install octomindPrebuilt binaries for every platform are on the releases page.
Three things to check after upgrading:
- Config migrates to version 4 automatically.
[[compression.pressure_levels]]is removed; its lowest threshold becomescompression.threshold. Your other values and comments are preserved. - Browser clients need
--allow-origin. Native clients are unaffected. - PACT is still opt-in.
[compression.attention] enabled = falseremains the default; governance and verification stay on.recallworks either way.
The model hub is open to everyone — octomind login, or /login from inside a session, and you're routing.
Compression that throws away evidence is a bet you re-place every hour of a long session. 0.42.1 made the summary prove its claims. 0.43.0 keeps the originals within reach — so when the summary turns out to be wrong about something, the agent has somewhere to look instead of something to guess.



