Skip to content

v0.21.5

Choose a tag to compare

@github-actions github-actions released this 20 May 05:45
· 570 commits to master since this release

What's New

Pi background compaction stops busting cache mid-tool-loop

Pi's historian used to apply the visible-message trim (sessionManager.appendCompaction()) immediately after publishing new compartments. That changed Pi's branch view mid-tool-loop, which meant the very next request to the model sent a different message shape than the one we were trying to cache. Background work was paying the cache cost every time it produced a result.

Pi now stages the marker in a durable queue and applies it on the next materialization pass — the same point where pending tool drops materialize — so a single execute pass covers both changes. Cache survives across background compaction work.

Equivalent to the OpenCode deferred-marker design that shipped in v0.19. Behavior visible to users: noticeably better cache hit rates on long Pi sessions with frequent autonomous tool runs.

What's Fixed

Pi parity with OpenCode

Several invariants OpenCode had but Pi was missing:

  • Pi historian trigger evaluation now uses live session config. Pi was deriving the trigger budget at startup and ignoring execute_threshold_tokens, commit_cluster_trigger, auto_drop_tool_age, protected_tags, clear_reasoning_age, and drop_tool_structure when deciding whether to fire historian. OpenCode threads all these from the live model. Pi now does the same — same inputs, same decisions.

  • Pi historian trigger evaluation now uses the fast tag query. OpenCode switched to getActiveTagsBySession in v0.17 (avoids a full-session tag scan on every pass). Pi was still on the slow path. Pi now uses the same fast path.

  • Pi sticky reminders, auto-search hints, and note nudges survive when extensions wrap messages. When a competing Pi extension clones or re-wraps message objects (e.g. condensed-milk-pi), our reference-based ID lookup could miss those messages and fall through to writing synthetic IDs that don't replay correctly. Added a fingerprint fallback (responseId + timestamp + role + first text hash) so cloned wrappers still resolve to real entry IDs. On strict failure we now replay existing anchors only, never write new ones with synthetic IDs. Direct fix for #81.

  • Pi historian/compressor publish callbacks no longer affect cleared sessions. If you switched sessions while a Pi historian or compressor run was in flight, the publish callback could write deferred refresh signals against the just-cleared session and leak state. Active-session registry now scopes the callbacks; cleared sessions ignore late publication signals.

  • Pi subagent runner respects abort signals before spawning. Aborting a historian/dreamer/sidekick run between request and spawn used to spawn the child process anyway. Now exits cleanly with the abort error.

  • Pi shutdown timers no longer hold the event loop. Shutdown drain timers were keeping the process alive briefly after Pi exited. Now properly unref'd.

  • Pi historian eligibility check cleans up its raw-message provider on every path. Provider was leaking when maybeFireHistorian exited without firing.

Storage atomicity

Four database paths could leave inconsistent state if a SQLite operation failed mid-sequence:

  • Compartment recomp promotion now clears stale memory block IDs. After full recomp, the cached memory block IDs in session_meta were stale until the next memory write — they could reference compartments that no longer existed.
  • deleteTagsByMessageId is now transactional. Tag deletion did two separate DELETEs (composite tool tags + plain tags) without wrapping. A failure between the two left orphan rows.
  • Orphan key-file cleanup is now transactional. Same pattern: two DELETEs without atomicity.
  • Migration race detector now actually validates. The "is this migration version already applied by a sibling process" check had a vestigial return true and skipped its SELECT entirely on the slow path, so concurrent OpenCode/Pi startups could race even though the code claimed to be protected.

Transform replay

  • Errored-tool truncation and processed-image stripping now replay on every pass. Both mutations only ran during cache-busting passes — defer passes saw the un-truncated content. Wire bytes diverged between bust and defer, costing cache. Now both replay on every pass.
  • stripSystemInjectedMessages no longer touches user-role messages. Aggressive cleanup could neutralize user-role placeholders, collapsing user turn boundaries.

Config security

  • {env:VAR} substitutions are now JSON-escaped. A user setting MAGIC_CONTEXT_FOO="bar\"; \"injected_key\": \"injected_value could break out of the JSON string boundary and inject arbitrary config keys. Now properly escaped.
  • Project-level configs no longer expand {env:...} or {file:...}. Only user-level configs do. Previously, a malicious .opencode/magic-context.jsonc checked into a repo could exfiltrate env vars or file contents into config warnings sent over the wire.
  • JSONC comments are stripped before variable substitution. A // {env:SECRET} comment used to expand the env var into the comment text, then the parser stripped the comment — but the substitution warning still surfaced the secret.

ctx_note scope

  • dismiss and update are now scoped to the caller's session and project. Previously they took only a note ID, so an agent in session A could dismiss notes belonging to session B. Now both actions require the caller's project identity to match the note's, and session notes additionally require session ID to match.

Compartment trigger observability

Three skip paths in the historian trigger used to return shouldFire=false with no log line, making it impossible to diagnose why historian wasn't firing at high pressure. All three now log:

  1. Historian already in progress — shows current usage so you can see the trigger was suppressed by an in-flight run rather than by the trigger evaluation itself.
  2. No new raw history past last compartment — dumps nextStartOrdinal, lastCompartmentEnd, rawMessageCount, and protectedTailStart so the missing condition is visible.
  3. Below proactive floor — shows the actual floor for the active session's execute threshold.

Pure observability change. No behavior difference.

Upgrade

npx @cortexkit/magic-context@latest doctor --force