Releases: cortexkit/magic-context
v0.21.7
v0.21.7 — Compressor finally fires on busy sessions, cleaner agent disable, startup release announcements
What's New
-
Startup release announcement. On the first session after upgrading, Magic Context now shows a short "what's new" dialog in the OpenCode TUI, an ignored startup message on Desktop/web, and a Pi notification — so you don't have to dig through release notes to learn what changed. The dialog is one-shot per version and includes a persistent Discord invite footer below the version-specific bullets.
-
Single, unified off-switch for hidden agents. Disabling historian, dreamer, or sidekick is now a single field:
disable: trueon the agent block inmagic-context.jsonc. The legacydreamer.enabledandsidekick.enabledfields are gone — they were confusing becausedisable: trueandenabled: falsewould have been duplicate inverse meanings of the same intent, and only one (disable) actually unregistered the OpenCode agent.- Manual
/ctx-dreamstill works as long asdreamer.disableis nottrue. To get the old "scheduled dreaming off, manual dreaming still on" behavior, leavedisableunset and setschedule: "". - Existing configs auto-migrate.
dreamer.enabled: false→dreamer.disable: true(this changes manual/ctx-dreamfrom working to disabled — see migration below).enabled: trueis silently stripped because it was already the default. The dashboard Config Editor renders the new field directly; legacy values are cleaned when you save. - Running
npx @cortexkit/magic-context@latest doctor --forcerewrites your config on disk to the new shape with comments preserved.
This change unblocks issue #50: previously, having a stray
enabled: truein yourhistorianblock triggered a confusing schema error becausehistoriannever accepted that field. - Manual
-
TUI execute-threshold display: shorter, cleaner. Percentages over 100 (when using token-based thresholds with large windows) and very long fractional formats now round cleanly (
14.099783%→14.1%) and the sidebar context bar resizes itself to fit narrower terminal widths instead of wrapping. (Fixes GH #90.)
What's Fixed
-
Compressor no longer underfires on busy sessions. Per GH #91, with low
execute_threshold_tokensor low overall context pressure, the compressor could go indefinitely without running even while the compartment block grew far past its budget — because the previous gate suppressed the compressor on the exact cache-busting passes when it was structurally safe to fire. The new design:- Adds a database-backed cross-process lease (
compartment_state_lease) that serializes compartment-state mutators across OpenCode instances, Pi instances, and/ctx-recompruns. No more silent overwrites when two harnesses share a project. - Snapshots cache-busting signals at the start of each transform pass, so a compressor that finishes mid-pass writes its "history needs refresh" signal to the next pass's set — preventing the previous race where the same-pass drain would consume the compressor's own signal and make the compressed state invisible until some unrelated future refresh.
- Atomicizes compressor publish — compartment writes, fact writes, depth bumps, and cache invalidation all commit in a single
BEGIN IMMEDIATEtransaction. Holder identity is verified as the first statement inside the transaction, so a stale lease holder can never overwrite a fresh historian publish. - Removes the old suppression gate entirely. The compressor now fires on the same execute/cache-busting passes that historian fires on, and the lease keeps them safely serialized.
Practical effect: sessions with
execute_threshold_tokens=30000and low usage that previously accumulated 70+ compartments before any compression now get compressed on the very next execute pass, with no manual/ctx-flushneeded. - Adds a database-backed cross-process lease (
-
Auto-search no longer embeds plugin-internal notification messages. The startup-announcement message, conflict warnings, "preparing augmentation" notifications, and other
ignored: trueplugin messages were being sent to the embedding provider as if they were real user prompts, wasting embedding throughput and polluting the auto-search budget. The latest-meaningful-user-message detection now uses the canonical helper that filters ignored parts, system reminders, and OMO internal markers — matching how every other part of Magic Context already counted "real" user messages. -
Migration warnings only fire for meaningful changes. If your
magic-context.jsonchaddreamer.enabled: trueorsidekick.enabled: true(the no-op alias for the new defaultdisable: false), the unified-disable migration in v0.21.7 initially showed a "config warning" suggesting you had something to fix. That warning is now silent forenabled: true(nothing semantically changes — the key is just obsolete) and only fires forenabled: false, which is the case where the migration triggers a real behavior change. -
Pi parity hardening. Drain-signal correctness, historian parity mirror, and runner/entry anchors received targeted fixes from the 2026-05-20 Pi audit pass, closing the last batch of pre-release Pi-specific findings.
Upgrade
npx @cortexkit/magic-context@latest doctor --forceThe --force flag clears OpenCode's cached plugin install so v0.21.7 takes effect on next launch.
Heads up for users with dreamer.enabled: false
The auto-migration rewrites dreamer.enabled: false to dreamer.disable: true, which also disables manual /ctx-dream (the old enabled: false only stopped scheduled dreaming). If you want scheduling off but manual dreaming on, edit magic-context.jsonc after running doctor:
This is the only behavior-changing migration in v0.21.7.
v0.21.6
v0.21.6 — Hidden subagent isolation, TUI threshold visibility, doctor report size cap
What's New
- TUI sidebar and
/ctx-statusheader now show the execute threshold alongside the percentage. The header changes from47.5% · 475K / 1.0Mto a left/right layout:47.5% / 65%on the left (current usage vs the execute threshold that triggers compaction),475K / 1.0Mon the right (absolute tokens vs the model's context window). Resolved per-model, so users withexecute_threshold_percentageorexecute_threshold_tokensoverrides see the actual value applied to their session.
What's Fixed
-
Historian, dreamer, and sidekick can no longer spawn subagents or run unsafe tools. Each hidden agent now ships with a
permissionruleset that denies everything by default and explicitly allows only the tools that agent actually needs:- Historian / compressor:
read,aft_outline,aft_zoom(state-file offload + lightweight symbol navigation for accurate summaries) - Dreamer:
read,grep,glob,bash,aft_outline,aft_zoom,ctx_memory,ctx_search,ctx_note(memory CRUD + verification against project source + smart-note evaluation viagh/git/curl).task,edit,write,webfetch,websearchstay denied. - Sidekick:
ctx_search,ctx_memory,aft_outline,aft_zoom(memory retrieval + symbol-scoped structural context for/ctx-aug)
Previously these agents inherited the full primary-agent tool surface and historian was occasionally observed dispatching
task(subagent_type=explore)for repo-wide fanout, which contradicts its job of summarizing the input it was given. Allow-lists were derived from auditing real tool usage in the local OpenCode DB across hundreds of past historian and dreamer child sessions. - Historian / compressor:
-
doctor --issuereports now respect GitHub's 64KB body limit. When the rendered report exceeds the budget, the main log section is truncated from the top (oldest lines dropped) with a visible[truncated for GitHub 64KB limit — older log lines dropped]marker. A new "Recent errors (last 20, sanitized)" section is always included before the main log so error context survives even when the bulk log gets clipped.
Upgrade
npx @cortexkit/magic-context@latest doctor --forceIf OpenCode still loads a cached older plugin after upgrading the CLI, the --force flag clears the cached install so the new version takes effect on next launch.
v0.21.5
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, anddrop_tool_structurewhen 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
getActiveTagsBySessionin 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
maybeFireHistorianexited 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_metawere stale until the next memory write — they could reference compartments that no longer existed. deleteTagsByMessageIdis 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 trueand 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.
stripSystemInjectedMessagesno 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 settingMAGIC_CONTEXT_FOO="bar\"; \"injected_key\": \"injected_valuecould 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.jsoncchecked 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
dismissandupdateare 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:
- 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.
- No new raw history past last compartment — dumps
nextStartOrdinal,lastCompartmentEnd,rawMessageCount, andprotectedTailStartso the missing condition is visible. - 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 --forceDashboard dashboard-v0.4.8
Dashboard v0.4.8 — Memories project picker fix
What's Fixed
-
Memories tab project picker showed raw
git:<hash>/dir:<hash>identifiers instead of project names. A regression from the v0.4.7 Memories filter fix: when the plugin started writing resolved project identities into memory rows, the dashboard's project picker still tried to resolve those identities as filesystem paths. With no path match, the OpenCode/Pi project lookup was skipped and the dropdown rendered the bare identifier.Fixed by normalizing each memory's project value to its identity (handling both already-resolved identifiers and legacy raw paths), then matching against the full enumerated project list from OpenCode/Pi by identity. Picker now shows real project names like "magic-context" or "AFT".
Upgrade
The Tauri auto-updater handles this release. Existing installations should pick it up on next launch or via Help → Check for Updates.
Dashboard dashboard-v0.4.7
Dashboard v0.4.7 — Memories tab project filter fix
What's Fixed
-
Memories tab returned zero results when filtering by project (#87). The frontend sends the resolved project identity (e.g.
git:<commit-hash>) as the filter value, but the backend was queryingmemories WHERE project_path = ?against raw filesystem paths stored on each memory row. The two never matched, so filtering any project showed no memories even when memories existed for that project.Fixed by resolving the incoming identity to all stored paths that belong to it (a single
git:identity can cover multiple worktrees and clones writing into the same shared memory pool) and querying with anIN (...)clause across the full set. Identical to the path History already used, so both tabs now agree on what "this project" means.
Upgrade
The Tauri auto-updater handles this release. Existing installations should pick it up on next launch or via Help → Check for Updates.
v0.21.4
What's New
Compaction markers are now always on — no longer behind a config flag. They've been default-true since v0.9.0 and there's no scenario where disabling them helps; the knob just let users silently degrade themselves on long sessions. Doctor will quietly remove any stale compaction_markers setting from your config.
What's Fixed
Stuck "compacting history" notification loop
Fixes #85. The ⏳ Context at 95% — Magic Context is compacting history before continuing notification could fire on every turn even though real context usage was tiny (3.7% in the reporter's case).
Root cause: an earlier transient overflow error armed an internal "recovery needed" flag, and the only paths that cleared the flag required a successful historian publication. When historian had nothing eligible to compact — short session, mostly ignored notifications, etc. — it bailed silently before publish, leaving the flag armed. The next turn synthetically bumped pressure back to 95%, fired the notification, ran historian, bailed silently again. Forever.
The runner now disarms recovery when it correctly determines there's nothing to compact (no eligible raw history, or all eligible messages filtered as noise). Diagnostics also surface two previously-silent failure paths (existing-validation, chunk-coverage) so future variants of this bug show up in doctor --issue instead of looking like a clean session.
TUI sidebar
-
Sidebar breakdown no longer disappears on first user prompt. Reopening a project session showed the full context breakdown, then the whole drilldown vanished the moment you sent your first message — until the assistant's first response arrived. Caused by the sticky-cache misclassifying the "user just typed, model hasn't responded" window as a real session reset. The cache now sticks unless compartments and memories also dropped to zero.
-
Conversation row always shows in the legend. When the calibrator rounded Conversation tokens down to zero (heavy tool-call sessions), the entire Conversation row vanished from the legend, leaving the breakdown looking truncated. The row is now always rendered, even at 0%.
-
Breakdown bar respects 0-token segments. A 0-token segment used to consume 1 character of bar width, making the bar non-proportional. Zero-token segments now get zero width; the bar matches real token distribution.
-
Cleaner header line. Dropped the redundant "Context" label and "tokens" suffix from the line above the breakdown bar. Now reads
34.3% · 343K / 1.0Mright-aligned.
Key file pinning
- Prose docs and project-meta files no longer get pinned. README.md, CONTRIBUTING.md, CHANGELOG, LICENSE, lockfiles, and
*.md/.mdx/.rst/.txtfiles were leaking into the key-file candidate pool. Heavily read, but not useful as repeated-reference orientation context — they crowded out real source. Filtered both at candidate collection AND in the LLM prompt; existing pinned doc rows will be replaced on the next Dreamer key-files pass.
Auto-search
- All XML/HTML markup stripped before embedding. Auto-search previously enumerated specific tags to strip (
<instruction>,<ctx-search-hint>,<sidekick-augmentation>, temporal HTML comments, plugin markers). Anything outside that allowlist — pasted code with<Component>, arbitrary custom tags, future plugin markers — reached the embedding endpoint as part of the query. Now strips all HTML comments and all XML/HTML tags generically. System-reminder content is still dropped entirely; text between other paired tags is preserved (e.g. pasted<thing>useful content</thing>keeps "useful content" in the embedding).
Diagnostics report
doctor --issueno longer redacts non-secret config fields. Bug reports were showing entries like"pin_key_files": "<REDACTED:pin_key_files>"because the secret-detector substring-matched onkey,token,auth, etc. — flagging benign config names likepin_key_files,token_budget,nudge_interval_tokens,injection_budget_tokensas secrets. The detector now requires the field name to actually be a credential (api_key,access_token,client_secret, etc.) rather than just contain a secret-shaped word, while still redacting all real credentials.
Smart notes
- Tightened guidance to prevent session-context misuse. Smart notes (
ctx_notewithsurface_condition) are evaluated by the dreamer in a separate background process, with read-only access to external signals — GitHub state viaghCLI, web pages, files on disk, git history. The dreamer cannot see your active conversation, cannot detect when the user says something, and has no session memory. Agents were writing smart notes with conditions like "When the user mentions X" or "When we revisit this code path" — unevaluatable. The tool description, parameter description, and dreamer's evaluation prompt now make this boundary explicit with both ✓ GOOD and ✗ BAD examples, and the dreamer now omits unevaluatable conditions from its results rather than marking themmet=false, letting the archive-stale task retire them eventually.
Upgrade
npx @cortexkit/magic-context@latest doctor --forcev0.21.2
v0.21.2 — Pi extension compatibility and high-pressure safety
A short patch release fixing real cache-stability and Pi subagent issues found in production over the past week.
What's improved
Pi: extensions adding custom messages no longer break compartment boundaries
Pi extensions that emit customType messages — Magic Context's own /ctx-status output, plus several common Pi extensions like anthropic-auth, pi-http-dump-src, and condensed-milk-pi — add real entries to agent.state.messages that are stored as custom_message (rather than message) branch entries. Our previous walk through getBranch() assumed those two counts were always equal, so any custom messages mixed in shifted every subsequent index by one. Compartment boundary lookups stopped matching, <session-history> injection fell into degraded mode, the visible tail couldn't be trimmed, and context kept growing until the 95% emergency.
The mapping is now resolved by AgentMessage reference identity instead of by array position, so custom messages from any extension can sit anywhere in state.messages without affecting boundary resolution. Resolves the symptom reported in issue #81 and fixes a smaller off-by-one drift around agent_end that we observed in our own sessions.
Pi: subagent spawn works in npm-only installs
PiSubagentRunner was spawning the pi binary from PATH for historian, dreamer, and sidekick child processes. In environments without a globally installed Pi binary (npm-only installs, CI, sandboxed setups) that silently failed, leaving subagent features unusable. The runner now resolves @earendil-works/pi-coding-agent from node_modules and falls back to PATH only when resolution fails, then invokes Pi directly under Node — no Bun dependency.
OpenCode: no infinite loop at high context pressure
At ≥95% pressure during historian work, the "Magic Context is compacting history" notification was being emitted on every transform pass. Each notification persists as a new user message; OpenCode's assistant runLoop break condition compares the latest user and assistant message IDs, so a stream of new user messages kept the loop iterating. Sessions that hit very high pressure could spin through hundreds of identical requests before recovering. The notification now fires at most once per historian run.
OpenCode: bounded session message fetches
Several internal paths (historian, compressor, dreamer, sidekick, key-files, conflict-warning) were calling OpenCode's session.messages endpoint without an explicit limit, which the underlying API treats as "load the entire session into memory." All nine callsites now request at most the latest 50 messages — enough for what they actually need. Reported by an external audit.
Upgrade
This release does not add any database migrations.
# OpenCode
npx @cortexkit/magic-context@latest doctor --force
# Pi (requires Pi >= 0.74)
npx @cortexkit/magic-context@latest setup --harness piDashboard dashboard-v0.4.6
Cache page rebuilt around real conversation turns
The per-session cache timeline used to render one bar per HTTP request — meaning a single conversation turn with several tool-using steps would appear as multiple events with confusing severities. This release groups cache events by turn (using finish=tool-calls as the continuation signal) and shows expandable detail underneath:
- Each row now represents one user-visible turn, even when the agent ran multiple steps.
- Step badges show how many sub-steps the turn contained.
- Severity (cache hit, warming, bust) is calculated against the whole turn and rolls up the worst child step.
- Per-session timelines fetch enough events to surface 200 visible turns; the global Cache view stays on raw-event counts.
Other fixes
- Config editor now lists Pi models alongside OpenCode models when editing Pi config (was OpenCode-only).
- Cache row hover background no longer adds an unwanted highlight; row card layout fixed after the turn-grouping refactor.
- Multi-step turn aggregation correctly uses the final step's prompt size (not the cumulative across steps).
- Cache turn ordering: newest events first inside expanded turn detail.
Internal
- Compaction markers config knob removed from the editor — the feature is now always-on (see plugin v0.21.4 notes).
- Dashboard tracks plugin's project-scoped key files schema; no UI change visible yet.
- Tauri 2.10.3 → 2.11.1; security bumps for openssl, rustls-webpki.
Update
The desktop app's auto-updater will offer this release on next launch. Or download from the assets below.
Full changelog: dashboard-v0.4.5...dashboard-v0.4.6
v0.21.1
v0.21.1 — Pi reliability and stability sweep
This release brings Pi much closer to OpenCode in transform behavior, cache stability, and lifecycle handling. OpenCode gets two smaller fixes: better setup detection on uncommon shell environments, and corrected XML escaping in injected context.
What's improved
Pi: fewer mid-session cache busts
A handful of subtle Pi transform regressions could each individually fragment Anthropic-style prompt cache prefixes during a normal turn. Four of them landed in this release:
- Heuristic cleanup no longer re-runs on every execute pass within a single turn. Pi was missing the once-per-turn guard OpenCode has had since the early threshold work, so a long turn with several tool calls could re-clean the same prefix multiple times — each cleanup bumping wire bytes and busting cache. Cleanup now runs at most once per turn for primary Pi sessions, matching OpenCode.
- Historian and compressor publication no longer eagerly invalidates injected history mid-turn. Both signals now defer until the same execute pass that materializes pending drops, so the cost of background publication is absorbed into the next natural cache-busting boundary rather than triggering a separate one.
- Sticky
ctx_reducecleanup reminders now actually replay. The reminder was being written to durable state correctly, but the postprocess pass that should re-anchor it on every transform was never wired in. Reminders now persist visibly across turns until either a newctx_reducecall lands or the anchored user message leaves the window. - Model switches now reset all stale pressure and recovery state. Switching models on Pi previously cleared only basic usage counters, leaving historian-failure state, reasoning watermarks, detected-context-limit overrides, and emergency-recovery flags from the previous model in place. The next pass on the new model now starts from a clean baseline.
Pi: parity with OpenCode behavior
A wider parity pass closed the remaining drift between the two harnesses:
/ctx-dreamcan now recover from interrupted runs the same way OpenCode does (force-clear of stale started rows past the lease window). It also reports a friendly "not configured" message instead of failing silently when Dreamer is disabled, and gates ondreamer.enabledat registration./ctx-statusnow correctly reports Dreamer state./ctx-searchresults show the ordinal range and actx_expand(start, end)footer hint, matching the OpenCode tool output.ctx_memory listis now dreamer-only on Pi (was: also exposed to primary), matching the OpenCode permission model.- Pi honors
system_prompt_injection.{enabled, skip_signatures}config and dedupes against pre-existing<project-docs>,<user-profile>,<key-files>, and Magic Context guidance markers in the base system prompt. - Project resolution now follows the current session's directory instead of being frozen at extension startup. Switching projects mid-session no longer leaves Pi using stale docs, memories, or project identity. Session-switch invalidates cached system-prompt adjuncts cleanly.
- Tool dedup keys include the owner message ID, so identical-looking tool calls from different turns are no longer collapsed.
- Auto-search marker stripping handles nested
<system-reminder>blocks and Alfonso internal-initiator markers, so embedded queries match OpenCode's behavior. - Inline
<thinking>/<think>markup is now stripped on execute passes (was: replay-only — older messages kept the noise). - Dreamer publish now refreshes Pi's system-prompt adjunct caches across every session in the affected project. Project docs, user-profile, and key-files updates take effect on the next turn rather than waiting for restart or
/ctx-flush.
Pi: correctness and lifecycle fixes
- Subagent runner termination now actually escalates SIGTERM to SIGKILL when a child Pi process ignores the first signal (the previous predicate checked the wrong flag and the SIGKILL never fired).
- Subagent
lengthtruncation is reported as truncated, not success. A child Pi run that stopped because the model hit its output-length limit was previously treated as a successful completion, so the partial output got persisted as authoritative. message_endindexes the just-ended assistant message, not the message that preceded it. The previous read happened before Pi had appended the new message, so terminal assistants stayed unindexed.- Raw-message provider is cleaned up on session shutdown (was: leaked across sessions).
- Compressor runs on execute passes even when the pass isn't otherwise cache-busting, so history budget headroom is reclaimed at the natural cadence. The compressor cooldown is now stamped only when compression actually publishes — failed or no-op runs don't suppress later compression attempts.
- Embedding cache no longer invalidated every turn during the auto-search path.
- Dreamer timer no longer races on cancel-before-resolve: registering then immediately unregistering a project no longer leaks the timer when the initial timer-creation promise settles after cancellation.
OpenCode: small fixes
<project-docs>and<user-profile>content is now XML-escaped before injection into the system prompt. Project documentation or user memories containing literal</>/&characters or substrings that look like closing tags no longer break the surrounding wrapper. The same fix applies to Pi.doctornow detects OpenCode reliably in unusual shell environments. Previously,doctorshelled out towhich opencode; in environments wherewhichitself isn't on PATH (Alpine, slim containers,bunxsandboxes, NixOS, custom wrapper-script setups), this could fail even whenopencodewas reachable.doctornow walks$PATHdirectly using Node primitives, with proper handling for wrapper scripts, symlinks, and Windows.exe/.cmd/.batlookup (issue #75).
Upgrade
This release does not add any database migrations.
# OpenCode
npx @cortexkit/magic-context@latest doctor --force
# Pi (requires Pi >= 0.74)
npx @cortexkit/magic-context@latest setup --harness piv0.21.0
v0.21.0 — Cache-stable reminders and per-project embeddings
What's new
Note nudges and auto-search hints are now cache-stable
Both reminder types append a small block to a recent user message. Previously the plugin remembered only the latest sticky anchor per session, so the next new reminder rewrote the old anchor's bytes on the wire, busting the prompt cache prefix for everything past it. Auto-search hints were worse: they lived only in process memory and disappeared entirely on restart, so every restart busted cache the moment the first user message came back into the prompt.
Both paths now persist every anchor as an append-only JSON list keyed by messageId. Each anchored reminder replays byte-identically on every subsequent request until the underlying user message leaves the visible window — across many concurrent triggers and across plugin restarts. The append uses bounded compare-and-set retries so sibling OpenCode/Pi processes can't lose each other's writes.
Pi reaches parity here too: new durable anchors resolve to real Pi SessionEntry.id values, with strict-fail behavior when ID resolution is uncertain so a misidentified anchor can never poison cache.
If you've been seeing surprise cache-misses on long sessions — particularly after restart, or after a few note triggers fire — this release should noticeably reduce them.
Per-project embedding configuration
Embedding configuration (provider, model, endpoint, dimensions) is now scoped per project rather than shared process-wide. Two side-effects:
- Switching embedding provider/model on one project no longer disturbs stored vectors in other projects.
- The dream timer's periodic embedding sweep re-resolves the right config for each project on every tick, so mixed-config workspaces all stay current.
Existing memory and git-commit embeddings remain valid wherever the per-project identity matches what they were written under; mismatched rows are detected and cleared safely so the next sweep can rebuild them with the right vectors.
Pi logging matches OpenCode
Pi's transform/historian/dreamer logs now carry the same stage-timing, scheduler-decision, cleanup-result, and transform-completion lines OpenCode emits. magic-context.log is now equally useful for diagnosing either harness.
What's fixed
-
Cargo-culted
§N§markers in assistant text. When an execute pass dropped a lot of tool structure, some models started emitting fake§N§markers as if they were tool-call delimiters, including malformed shapes like§40827"›.... The plugin now defensively strips embedded, malformed, and stray§characters from assistant text at the persistence boundary — leading prefixes that the model emits correctly are preserved because correct mimicry actually helps prefix stability. -
Electron desktop local embeddings. Local MiniLM embeddings failed to load in Electron with native-binding errors because
@huggingface/transformersv4 mandates a top-level native ONNX import. The plugin now injectsonnxruntime-webas the ONNX backend in Electron only, leaving Pi and the OpenCode CLI on nativeonnxruntime-node(issue #78). -
Context-limit cache regressions. Active sessions could see impossible 700–900% usage readings when the hourly model-cache refresh lowered a model's reported context limit mid-session. Limits are now resolved once at session start; the sidebar shows the active limit; and a one-time self-healing path detects and alerts when a cached limit is clearly below previously-observed usage (issue #77).
Upgrade
This release adds a database migration. Existing sticky note-nudges and auto-search hints are backfilled into the new schema on first start; you don't need to do anything special.
# OpenCode
npx @cortexkit/magic-context@latest doctor --force
# Pi (requires Pi >= 0.74)
npx @cortexkit/magic-context@latest setup --harness pi