Animated Octomind icon — purple pixel-art octopus

Hour one with an agent is great. Hour four is where it falls apart.

The reason is always the same. The context window fills, something has to go, and the thing deciding what goes is a language model writing a summary. That summary is prose. Prose isn't evidence. Three compactions later you're arguing with your agent about a decision you made at the start of the session, and neither of you can point at where it was written down.

We've been patching around that for a year — knowledge retention, continuation wrappers, USER TASKS lists, goal recitation. All of it helps. None of it changes the shape: the summary is the only survivor, and nothing checks whether it's true.

0.42.1 changes the shape. Compression now archives what it drains, cites exact spans out of that archive, and verifies the citations round-trip before anything reaches your live context. Quality stops decaying with session length — because what survives can be proven.


PACT: the summary now has receipts

PACT is the compression path we've been building toward. Instead of handing the model a linear transcript and asking for a narrative, the controller does structural work first:

  • Groups tool calls with their contiguous results into causal packets with provenance labels, recording whether ownership was strong or a fallback guess.
  • Pins the real task and constraints as runtime-owned state, so they aren't something the model has to remember to mention.
  • Assigns each packet an exact / summarize / archive lane under the compression ratio budget you already configured.
  • Renders four bands, validates dependency closure and complete fold coverage, and reconstructs every selected exact span from the archive — with line ranges and content digests — before the commit.

The archive itself is a content-address-verified block sidecar written beside the raw pre-drain JSONL, with SHA-256 verification and stable-ID dereference. When a later compaction folds something that cites an earlier fold, the prior source gets dereferenced again before the new fold is accepted. If it can't be recovered, the fold is dropped rather than presented as established state.

That last sentence is the whole design in one line. A claim that can't be re-derived from the archive doesn't get to survive as fact.

There's a governance envelope on top: the hash is verified and the archive round-trip is checked before any live mutation happens, and PACT telemetry is content-free by construction.

It's opt-in, and it's staying opt-in until the data says otherwise

Here's the honest part. The selector ships in canary:

toml
[compression.attention]
enabled = false
validator = true
telemetry = true

[compression.attention.governance]
enabled = true
verify_hash = true

Governance and verification default on. The PACT selector defaults off.

The deterministic implementation is complete and the repository replay fixtures pass — scheduled-monitoring failure shapes, task continuation across recompression, real-user pivots, parallel and ID-less tool linkage, archive corruption, exact-span recovery, governance injection, credential-free telemetry. What hasn't run yet is the external canary against real sessions and real task-success numbers. So it's behind a flag, and it'll flip to default-on when the gates clear, not when the changelog wants a headline.

If you run long sessions and want to help find the edges, flip enabled = true and leave telemetry on. If you want boring, do nothing — the legacy path is untouched when the canary is off.

PACT repair: the model doesn't get to fail open

When the generative fold violates the attribution contract, the runtime now fixes it deterministically instead of paying for a rejection loop. Archive descriptors cited as evidence get stripped. Summarize packets the model forgot to mention get a reference unit so they survive the drain. Frontier packets folded as completed get downgraded to tentative — the active state isn't a done deal.

And when rejection is unavoidable, it's cooled down. A failed validation sets context_tokens_after_last_compression to the current baseline, so the next check waits for real growth instead of refiring the same paid fold every turn. The cooldown was the missing piece — without it, a session that kept tripping the validator would burn tokens on rejection loops without growing context.

Both the prompt and schema now spell out the hard citation rules explicitly: never cite an archive_reference packet ID, a unit citing a keep_exact frontier packet gets only pending/tentative/unknown status, and every summarize-lane packet must be cited by at least one folded unit. The model doesn't guess what the validator wants — it reads it.

What changes even with PACT off

Most of you won't turn the flag on this week. You still get the rest of the compression work, and it's the part that fixes the failure you actually notice: the agent forgetting a constraint you stated forty minutes ago.

Knowledge retention went from 10 entries to 25.

toml
[compression]
knowledge_retention = 25
analysis_findings_max_tokens = 4000

Ten was too small the moment sessions started running for hours. Each compaction extracts a short snippet of critical knowledge — decisions, constraints, your stated preferences — and those get injected into every later compaction. At 10, a long session silently FIFO'd your early decisions out. At 25 it holds.

Analysis findings now persist with a real budget. analysis_findings_max_tokens = 4000 is a hard token ceiling on findings carried across compaction cycles. When they don't all fit, they're ranked by relevance to the current task, recency, and diversity — not truncated at an arbitrary count. Findings also accumulate and deduplicate on the session rather than being overwritten wholesale by whatever the latest summary happened to mention, and they're cleared when you move to a new task.

Deduplication is adaptive rather than a fixed threshold: restatement detection uses Fisher-z and MAD, with embedding-based outlier detection for the findings themselves. The practical effect is that saying the same thing five different ways doesn't consume five slots.

The first pressure level moved from 60k to 80k. Compression at 60,000 tokens was firing earlier than the economics justified on current context windows.

One config gotcha worth knowing, because it bit us and it's now a comment in the template: scalar compression keys must stay before the nested [compression.attention] and [[compression.pressure_levels]] tables. A scalar placed after a pressure-level block gets parsed as a field of that level and silently ignored. TOML is doing exactly what it promised; it just isn't what you meant.

Config version moves to 3. The migration copies only missing keys and preserves your existing values and comments — including a [compression.attention] block you may have already written by hand.

The condenser stopped guessing

[supervisor.condense] — the thing that narrows oversized tool output before it eats your window — got rebuilt around one rule: the model selects, it never writes.

The budget is now fixed per round, not per result. A large result is represented in a numbered view by task and argument matches, diagnostics with surrounding context, head and tail lines, and stratified middle samples — every line carrying its original line number. The condenser answers with original line ranges. Kept lines are reconstructed verbatim from the source. Nothing is retyped, so nothing can be mis-copied.

Three things changed about what happens on the edges:

  • Irrelevant results get a deterministic system notice, not a model-authored summary. The condenser is no longer allowed to write a sentence describing tool output it just discarded — that sentence was a hallucination surface, and now it doesn't exist.
  • The response contract is validated atomically. Missing, duplicate, unknown, malformed, or unsafe entries mean every original is kept. A partial view can be extracted, but never discarded wholesale. Error and diagnostic lines are retained deterministically even if the model overlooks them.
  • Structured MCP payloads fail open instead of being flattened into text and corrupted. Condensation runs for plain-text results only, and only when the active role actually has a local file-reading tool to read the spill back with.

Relevance is conditioned on three separate signals now: trusted standing context (system prompt, project instructions, active skills), the live goal/request/plan, and the assistant text explaining why this tool batch was issued. Tool data is serialized as JSON and treated as untrusted reference material — it can't manufacture instructions for the condenser.

The trigger threshold went from 2,000 to 5,000 estimated tokens, which is where it should have been.

The self-report grew four fields

If you've read the supervisor docs you know about the hidden status token the agent emits at the end of each turn. It used to be a word:

text
<sup>STATE</sup>

It's now a structured handoff:

text
<sup>{"state":"STATE","focus":"current subgoal and why","next":"next action","carry":["minimum resume-critical fact or opaque reference"]}</sup>

Same five states — exploring, progressing, blocked, need_input, done. Still parsed and stripped before display, so you never see it. What's new is that focus, next, and carry give compression a cheap handoff from the agent that just did the work.

The compressor treats it as an attention hint, not evidence. It grounds the claim against the transcript and may promote supported durable protocol into critical knowledge; unsupported claims go nowhere. Credential values are forbidden in carry — only opaque pointers. Old one-word and STATE · reason reports still parse, so resuming an older session works.

The other detector change: sequential_max_steers_per_turn caps how many over-sequencing advisories fire within one genuine user turn (0 = unlimited, the default). A successful compression resets the budget, because the earlier advisory probably just left the live context. The distraction detector's centroid now follows every result rather than an "on-task" subset — a coherent pivot to another subsystem re-anchors after a couple of results, and only wandering that never anchors keeps the streak alive.

Prompts are also hardened: supervisor prompts are XML-tagged, compressor instructions can no longer leak into the summaries they produce, and stale task goals can't survive a compaction.

The monitor runs alongside you

There's a new sibling in the orchestration family. Alongside schedule and tap, you now get monitor — a tool that runs a long-lived shell command once, treats stdout as an event stream, and injects bounded batches into the session inbox at a guarded interval. The AI reacts to new events without active polling or a recurring schedule.

Actions: start launches a monitor with a command, description, and optional working directory. list shows running monitors. stop kills one. The inbox icon for a monitor delivery is 📡, so you see what's from a script and what's from a schedule.

Key design decisions

  • Coalescing, not queueing. A slow AI turn can't build an unbounded backlog. When the same monitor produces new output before the AI has processed the previous delivery, the new content is appended to the pending batch. If the combined size exceeds max_batch_bytes, a marker is inserted: [additional monitor output omitted while this delivery was pending]. The AI sees the omission, and no data structure grows without bound.
  • Omission reporting. When output exceeds max_batch_bytes, the excess is dropped with a clear marker. The AI knows it missed something and can decide whether to re-query.
  • Session lifecycle. Monitors are owned by the session that created them. When the session cleans up, all its monitors are cancelled. No orphan processes.

Configurable parameters

ParameterDefaultRange
flush_interval_seconds305–3600
max_batch_bytes64KB1KB–1MB
timeout_ms10min1s–24h
persistentfalse

Pass persistent = true for no deadline. The defaults are tuned for typical log-tail and docker logs --follow workloads.

Interactive sessions get it for free

Interactive CLI sessions now always see schedule and monitor as session-flow primitives — no per-role configuration needed. If the orchestration builtin is missing from the registry entirely, it's synthesized automatically. Websocket sessions don't get these tools: they're for driving a live terminal, not an API.

This is the same pattern we used for schedule in 0.41.0 — the role just works. If you've already hand-configured orchestration tool grants, the new ones merge in without overwriting yours.

The long-session dial, in one block

If you only take one thing from this release: these are the knobs that decide whether hour four is as sharp as hour one. Paste it, restart, run something long.

toml
max_session_tokens_threshold = 200000

[compression]
knowledge_retention = 25
analysis_findings_max_tokens = 4000

[compression.attention]
enabled = true            # PACT — the canary, off by default

[supervisor]
claim_check = true
max_consecutive_steers = 0

[supervisor.gate]
enabled = true
verifier_model = "openai:gpt-5-mini"   # a DIFFERENT family than your agent model

[supervisor.recite]
enabled = true

[supervisor.condense]
enabled = true
tokens_threshold = 5000

Four of those do the real work.

compression.attention.enabled is the one with the ceiling — folds that cite recoverable sources instead of prose nothing can check. verifier_model matters more than people expect: a same-family verifier inherits the same blind spots and rubber-stamps them, so pick a different family than whatever drives the agent. recite re-injects the live goal at the context tail every turn once a session has compacted, which is the cheapest available fix for "it forgot what we were doing." And claim_check makes load-bearing statements carry a verbatim quote that's checked against real tool output, with fabricated citations re-grounded through the gate.

The one to leave alone unless it's hurting you: max_consecutive_steers = 0. It's a circuit-breaker, and turning it on hard-stops a turn. Reach for it after you've watched a loop burn budget, not before.

Also since 0.39.0

We shipped 0.40.0, 0.40.1, 0.40.2, 0.41.0 and 0.42.1 without a blog post each. The parts you'd care about:

Evidence tags replaced quote marks (0.40.0). claim_check now asks the agent to wrap load-bearing quotes in <evidence> tags instead of guillemets. Each quoted line is verified against actual tool results line-wise, each file:line reference is checked on disk, and each cited URL against what was really fetched. Tags are stripped before display. Fabricated citations get re-grounded through the verify-gate's bounded re-run — so this needs gate.enabled to bite.

Read-back verification (0.40.0). The supervisor tracks paths the agent mutated and checks whether it read them back. Claiming a file was changed correctly without ever looking at the result is now a detectable state.

MCP moved to rmcp (0.40.0). New client and server management, environment-variable support with placeholder resolution in server configs, token rotation and server validation, multi-round tool responses with task polling, elicitation support, an OAuth resource param, and OS-process liveness checks for stdio servers so a dead server stops looking alive.

Subagent verification handbacks (0.41.0). A parent that delegates sees one tool round for a child's entire trajectory, so it can't tell a checked change from an unchecked one. The child now reports its own verification verdict up the ACP _meta side-channel. Silence is conservative: an untracked child reports nothing and the parent falls back to its own signal.

Lesson supersession is explicit (0.42.0). Learning used to delete old lessons by similarity. Now a new lesson names the ID it supersedes. Verification is fail-closed — an invalid or errored verify pass drops the candidate rather than storing it.

External spend is tracked (0.42.0). Costs incurred by subagents and supervisors are banked in a global accumulator and folded into session totals, so /info stops under-reporting what a delegating run actually cost. Compression stats in /info also expanded — runs broken down by kind, plus token counts, throughput and cost.

/schedule list has a real empty state (0.42.0). A bare /schedule used to be a dead end. It now shows what scheduling can do and how to drive it from chat.

Plus: MCP errors now extract detail out of the data field, path expansion accepts colons and backslashes, task intent prioritizes recent user messages, plans survive compaction, and there's Alibaba Model Studio provider documentation.

monitor tool for event-stream watching (0.42.1). A new orchestration builtin alongside schedule and tap. Runs a long-lived command, batches stdout, injects into the session inbox at a guarded interval. Coalesces pending deliveries so a slow AI turn doesn't build a backlog. Configurable flush interval, batch size, and timeout. Interactive sessions get monitor and schedule auto-injected — no setup needed.

PACT repair and cooldown (0.42.1). The fold validator can now fix mechanical contract violations before rejecting. Archive references cited as evidence get stripped. Frontier packets folded as completed get downgraded. Summarize packets the model forgot get a reference unit. When rejection is unavoidable, a cooldown prevents token-burning retry loops.


Upgrading

bash
# Homebrew (macOS / Linux)
brew install muvon/tap/octomind

# or from crates.io
cargo install octomind

Prebuilt binaries for every platform are on the releases page. Your config migrates to version 3 automatically — missing keys are copied from the template, your values and comments stay put. PACT stays off until you turn it on.

The model hub is open to everyoneoctomind login, or /login from inside a session, and you're routing.

A summary you can't check is a rumour your agent tells itself. 0.42.1 is the release where compression started keeping the receipts.