Repository navigation
OpenCode V2 readiness: existing-state migration and remaining V2 capability gaps #492
Description
What happened?
This consolidates V2-specific findings against current source. It separates reproducible defects, missing V2 capabilities, and known host differences. It is not a request to transplant V1 internals or to fix unrelated MC bugs.
Related: #488 (V2 umbrella), #489 / #477 (store recognition), #481 (hidden-session lifecycle). Their already-merged fixes are accounted for below.
Revisions and test scope
- MC operational inventory:
00e7e5c17913ae252430eff56ac441adbd3f7bc2. - OpenCode V2:
58fcad77a8a2e4abcbe985c395e7192801003857(2.0.11 lineage). - Main boundary and tool-state reproductions: MC
e35736c7d19b48781aa75b16da30a599bcd3428d; subsequent changes through00e7e5c1do not alter those paths (TUI sidebar, parity documentation, Cargo lockfile). - Contributor branch reviewed:
blasphemy/magic-context:v2-fixes-updatesat31634ea3. Proposed implementations there are distinguished from merged support; its full host behavior was not independently accepted.
Tests use isolated source checkouts and synthetic SQLite data. No live migration, installed configuration changes, or provider calls were used. Reproducers and the detailed coverage matrix accompany this issue.
Findings at a glance
| # | V2-specific finding | Evidence / affected scope |
|---|---|---|
| 1 | Existing MC state is not reconciled with converted V2 messages/parts | Reproduced wrong compartment ranges, stale search coordinates, and broadened queued reductions; affects existing history |
| 2 | Ten of twelve canonical Dreamer tasks are filtered out | Registry, V2 admission, executor wiring and focused tests; affects fresh V2 projects too |
| 3 | Command surface is partial | Four V1 commands lack V2 registration; three others have TUI entry points, not equivalent server/CLI registration |
| 4 | Per-context tool transforms accumulate and contaminate full guidance | Actual MC hook + native host tool state/request preparation; affects fresh V2 sessions too |
| 5 | Hidden-session deletion discovers a default service rather than its owner | Actual helper reproduction with synthetic registrations/intercepted HTTP; follow-up to lifecycle work |
| 6 | Explicit experimental Rust mode is not wired through V2 | Configuration and execution-path evidence; default TypeScript mode is unaffected |
| 7 | Tool-definition telemetry is missing | V2 lacks measurements and supplies an empty tool-set hash; sidebar attribution, not a demonstrated provider-budget failure |
1. Existing compartment boundaries drift after native conversion
The importer can split synthetic content into a separate row or replace a completed compaction pair with a native compaction record. MC's canonical conversational ordinals therefore change even when endpoint message IDs survive.
The reproducible cases derive the old coordinates through MC's actual V1 reader, persist ordinary compartment rows, run OpenCode's native projection, and read through MC's actual V2 reader:
- Synthetic split: saved endpoint
a2moves from ordinal 4 to 5. The saved end remains 4.ctx_expandrange recovery omits a2 and treats ordinal 5 as live tail. Directmessage: 5recovery succeeds. - Completed compaction: saved endpoint
a2moves from 5 to 4. The saved end remains 5. Range recovery includesu3, which was outside the saved compartment.
The mismatch persists after reopening the fixture. This demonstrates incorrect range selection, not deletion of source text.
Relevant source: MC hooks/magic-context/read-session-raw.ts, v2/hooks/store.ts, features/magic-context/compartment-storage.ts, tools/ctx-expand/tools.ts; OpenCode core/src/database/v1-migration.ts.
Acceptance: before existing MC state is consumed against V2 history, preserve the relationship between saved endpoint IDs and canonical ordinals; handle removed endpoints explicitly and make normalization restart-safe. Equivalent hand-authored V1/V2 fixtures are not sufficient coverage.
Other coordinate-bearing state must be checked as part of the same transition: search source/FTS ordinals and watermarks, protected-tail floors, compression depth, note/provenance anchors, compartment-chunk ranges, and persisted recomp staging. Merely having a callable reset/rebase helper does not establish that migration invokes it. These are acceptance cases, not independently asserted corruptions.
Attachment: repro/ and migration-issue.md contain the complete reproduction and scope limits.
Additional existing-state consequences, reproduced separately
Search index: seed the entire six-message V1 index before conversion. After a synthetic split produces seven canonical V2 messages, the real reconciliation scheduler advances its watermark to 7 and clears the dirty floor, but keeps a2 indexed at its old ordinal 4 instead of 5 and never inserts the new synthetic row at 4. Existing entries before the old watermark were not revisited. A completed reconciliation is therefore insufficient to validate an index across a host-representation change. Explicit clear/reindex rebuilds correctly, but its availability does not prove migration invokes it.
Queued reduction / multipart identity: use the real V1 tagger to persist two authored user fragments as separate tags and queue a drop only for the first fragment. Native conversion merges them into one V2 user text. The real V2 tagger and pending-operation application reuse the first tag for the merged target, dropping both fragments. Per-fragment original source snapshots survive. The defect is that the old operation's target became broader, not loss of the original stored text.
Attachment: extended-state-repro/, executed at MC 00e7e5c1. It exercises native APIs directly, not full plugin startup. The shortened-index rebuild is a passing manual control, not an additional empty-index bug. Depth/recomp persistence and Historian telemetry are recorded for acceptance coverage; they are not independently proven consumer failures.
Additional acceptance: rebuild or reconcile pre-watermark search coordinates when representation changes, and define safe handling of merged/split message-part tags before replaying old queued reductions. Do not equate a surviving part-like ID with unchanged target content.
2. Dreamer task transport/admission is incomplete
The canonical registry contains twelve tasks. The V2 executor advertises no tool support, and the scheduled/manual selectors filter on that capability:
| Task | Current V2 admission / missing capability |
|---|---|
classify-memories |
Admitted; zero-tool path |
compress-cues |
Admitted; useful work additionally requires mural.enabled |
map-memories |
Filtered; read-only investigation transport missing |
verify |
Filtered; read-only investigation transport missing |
verify-broad |
Filtered; read-only investigation transport missing |
curate |
Filtered; memory-tool loop missing |
retrospective |
Filtered; search/investigation loop missing |
maintain-docs |
Filtered; file-tool loop missing |
evaluate-smart-notes |
Filtered by requiresTools, although evaluator is described as no-tool; hidden transport also needs wiring |
review-user-memories |
Filtered by requiresTools, although reviewer is JSON/prompt-only; transport needs wiring |
promote-primers |
Filtered by requiresTools, although promotion is host-side DB work |
refresh-primers |
Filtered; read-only investigation transport missing |
Source: features/magic-context/dreamer/task-registry.ts:13–48, v2/hooks/dream-manual.ts:34–58, dreamer/task-executor.ts, v2/hooks/hidden-child.ts.
V2 currently registers only deny-all historian and dreamer-classifier carriers and strips hidden request tools. Additional task capabilities need appropriate permission ownership, not simply a global tools: true switch or a literal copy of every V1 agent name.
The contributor branch already proposes substantial work here (including bc4c93a2, b873a1be, 7cb472be, deed524d, 741c2d32, 17824f95, 7d277a34, ef5b570c). Please reconcile that work rather than reimplement it independently.
Acceptance: account for every task above; execute the runnable tasks on the pinned real host with their intended tool restrictions and continuation behavior. Tool-free and host-only tasks should not require an unrelated tool loop.
3. V2 command entry points are incomplete
The V1 command registry contains seven commands. Current V2 TUI code directly exposes ctx-status, ctx-recomp, and ctx-dream. It does not register the V1 command definitions with the V2 host.
The other four are:
ctx-wrapupctx-session-upgradectx-flushctx-embed
This is not inherently a missing host API: V2 provides context.command.transform. MC's V2 context does not expose/use that domain, and the V1 config hook is not the V2 registration path.
Source: features/builtin-commands/commands.ts, v2/tui/index.ts, v2/hooks/types.ts; OpenCode core/src/plugin/host.ts:250–254, plugin/src/promise/command.ts, plugin/src/promise/adapter.ts:307–323.
Acceptance: provide V2-native entry points for the required behaviors, or explicitly document deliberate unsupported/replaced behavior. A TUI action alone should not be presented as server/CLI command parity. Some command semantics can still depend on separate host limitations; registering a name alone is not enough.
4. Shared tool transforms accumulate and leak light guidance
The V2 context callback already edits the request's tool descriptions, but also registers a persistent context.tool.transform() on every pass. Native host state retains and replays those registrations.
Reproduction using actual MC context registration and native OpenCode State/hooks/request preparation:
- Setup plus seven context passes leaves eight persistent registrations.
- Clean concurrent light/full requests both get the correct descriptions.
- After the shared baseline has been shortened by a light-preset request, later full-preset requests—including another session—still send light descriptions.
Source: v2/hooks/context.ts:552–565, shared/prompt-surface-runtime.ts; OpenCode core/src/tool.ts and State.
Acceptance: stable registered baseline, per-request model-specific guidance, no registration growth with context passes, and light→full/mixed-session regression coverage. The existing request-local mechanism is the first fix boundary to evaluate.
Attachment: tool-guidance-repro/. It uses a facade for unrelated host services but the real MC callback, native state, and native request preparation. No measured latency/memory regression is claimed.
5. Retired-child cleanup is tied to default service discovery
MC's v2/host-service.ts reads only opencode/service.json. OpenCode supports channel-specific registrations such as service-local.json; standalone hosts may have no registration.
The actual helper was tested with synthetic registrations and intercepted HTTP:
- Local-channel registration only: no discovery; deletion rejects without a request.
- Local and unrelated default registrations: helper selects the default service; its 404 is accepted as success without verifying ownership.
The executor then prunes retirement bookkeeping after successful removal. A 404 is meaningful only from the owning host. Existing settled-error reuse and deferred deletion fixes are present; this is a narrower follow-up to #481/#488.
Acceptance: bind cleanup to the creating host, or retain an explicit retriable limitation when no owner-bound deletion path exists. Do not probe arbitrary registrations and equate any 404 with successful cleanup.
Attachment: repro-host-service.ts / hidden-cleanup-follow-up.md. No actual wrong-server deletion was attempted.
6. Explicit Rust runtime configuration is ignored by the V2 path
transform_mode is a real experimental setting with "ts" as the default. V1 constructs SubcModuleTransport for "rust"; V2 constructs the TypeScript transform and passes rustModeModuleClient: undefined to RPC handlers. Rust recomp therefore reports unavailable.
Source: config/schema/magic-context.ts:1060–1065, V1 src/index.ts:289–317, V2 v2/hooks/context.ts, plugin/rpc-handlers.ts:1484–1490.
Acceptance: honor explicit Rust mode through the V2 owner, or fail/document it as unsupported rather than silently executing another mode. This does not block users of the default TypeScript implementation.
7. Tool-definition measurements / tool-set attribution are absent
V1 records tool-schema measurements through tool.definition. V2 does not populate the equivalent measurements and passes toolSetHash: "" to materialization. The sidebar's local tool-definition breakdown is consequently zero rather than a measured catalog cost.
Source: V1 src/index.ts:870–887; V2 v2/hooks/context.ts:495–497; plugin/rpc-handlers.ts:483–518,581–593.
This is a telemetry/attribution gap. The shared materializer treats tool-set changes as attribution rather than hard-fold triggers; no provider input-budget overflow or mandatory-compaction defect was demonstrated.
Acceptance: measure through the V2 tool surface or explicitly mark this component unavailable instead of displaying zero as if measured.
Existing support and exclusions
The following are present and should not be reported again as missing: current Promise/Effect entry compatibility, ordinary ctx_* tool registration, text-only Historian transport, native fold integration, model-limit/usage handling, mixed-store recognition, harness relabeling, hidden-child retry/settled-error reuse, and the newly merged V1 sidebar component on V2's TUI slot.
Known upstream host differences are documented in packages/plugin/PARITY.md, including MC-initiated compaction constraints, transcript cleanup for model-echoed tags, and the sidebar's hidden-by-default behavior. Native V2 replacements are acceptable; exact V1 internals are not the goal.
Two candidate claims were excluded after checking native ownership:
- Foreign-project event delivery: OpenCode's bus scopes subscriptions through ambient
Location.Service, preserved by the Promise adapter. - Mandatory cancellation of every task on plugin disposal: no concrete escaped-work/hang reproduction established; intentional draining cannot be called a bug merely because a local abort is absent.
Completion criteria and evidence limits
Use the attached coverage matrix to record each capability as supported, intentionally replaced, unsupported by the host, or still requiring work. Passing a narrow reader fixture is not full migration acceptance, and preserving a SQLite row is not proof that its coordinates retain their meaning.
Real-host acceptance still needs an isolated old-state startup/import → resumed conversation → compaction/restart/cleanup lifecycle, plus task-specific provider/tool-loop checks. The provider-free fixtures establish the listed behaviors without risking a user's live databases. No finite source audit proves that no other bugs exist.
Activity
blasphemy commentedon Sep 21, 2026
Hi — the contributor-branch work referenced here now lives at blasphemy/magic-context:v2-fixes-updates-r2 (
fd562021, 27 commits on master @26ea68bc, no merges). It supersedes the v2-fixes-updates @31634ea3copy reviewed in this issue; same four feature groups, re-cut onto the newer base with master's reimplementations winning where they overlap (#483 authored-only output cap, #481 child lifecycle, canonical scheduler prune).On finding 2, beyond the fixture scope: the full tool-surface port has been live-confirmed on OpenCode 2.0.5.
verify-broadran agentically through the host tool loop with its intended read-only restrictions and completed with full telemetry (harness opencode2, real provider/model attribution, nonzero input/output/cached token counts, no refusals). The pooled dreamer-memory-mapper child role held its prefix cache across runs.map-memories,verify,curate,retrospective,maintain-docs,refresh-primersrun through the same ported surfaces;review-user-memories,evaluate-smart-notes, andpromote-primersno longer require a tool loop at all (zero-tool transport / host-side DB work), so they are admitted without one. Step caps remain a documented host gap — v2 registrations carry none, tool loops are bounded by the run timeout only.Findings 1, 4, 5, 6, 7 are untouched by this branch — no overlap, no conflicting changes. Happy to reconcile the dreamer commits (
bc4c93a2,b873a1be,7cb472be,deed524d,741c2d32,17824f95,7d277a34,ef5b570con the old branch) against whatever shape upstream prefers.magic-alfonso commentedon Sep 21, 2026
Thank you — this is the most useful OpenCode 2 report we have had: separated, reproduced against named revisions, and honest about what is a defect versus a gap versus a host difference. Status per finding against master as of now:
4 (tool transforms accumulate, light leaks into full) — confirmed and fixed (
bac1d1fa). The context hook was editing the request draft and registering a persistentcontext.tool.transformon every pass; the registration is gone. The real-host test now asserts on the wire (the mock provider's received tool descriptions per model) with a light-then-full contamination arm that fails on the old code. Your reproduction also exposed that our earlier test observed its own stub tools through the editor fallback rather than the request; corrected.3 (commands) — you were right about
context.command.transform, and it is now the registration path (61c0f3b2): the six commands (ctx-status,ctx-recomp,ctx-dream,ctx-flush,ctx-embed,ctx-wrapup) are registered server-side, listed byGET /api/commandand executable throughPOST /api/session/:id/commandwith no TUI attached, with a real-host test that runsctx-flushthat way and observes the priced pass it causes.ctx-session-upgradewas removed from every host (it only re-ran recomp plus a one-time memory re-categorisation).2 (dreamer tasks) — the registry now records each task's transport rather than one boolean (
61c0f3b2).evaluate-smart-notes(both prompts),review-user-memoriesandpromote-primersrun on OpenCode 2 through the hidden single-shot carrier —promote-primersturned out to call no model at all — with a completion cut short at the output limit or empty refused before any parser sees it. The seven tool-loop tasks (map-memories,verify,verify-broad,curate,retrospective,maintain-docs,refresh-primers) stay refused on OpenCode 2 for now, but as a named per-task limitation (MC-D08) in/ctx-dream,/ctx-statusand the recorded run, not a silent filter. Giving hidden children a tool loop with proper permission ownership is a separate designed change; we have looked at the contributor branch and will reconcile with it where the shapes match.5 (deletion bound to a default service) — confirmed and fixed (
25d9325a). Cleanup now binds to the registration this process wrote (pid-matched at discovery, path persisted with the retired child), a 404 counts as success only from that owner, and a child with no owner-bound path stays recorded as retriable (MC-H02). One host fact from the bytes: released 2.0.5 and 2.0.11 builds have their channel baked in and always register atservice.json; theservice-<channel>.jsonbranch belongs to non-default-channel builds.6 (Rust mode silently ignored on V2) — confirmed and fixed (
25d9325a). Rust on OpenCode 2 is not wired; that is now loud (MC-S06, one warning, a PARITY entry) and TypeScript mode runs. This also exposed that the RPC handlers branched on the unresolved mode, so every status read with Rust configured had been returning "Rust module status unavailable"; resolved before registration.7 (tool-definition telemetry) — confirmed and fixed (
25d9325a). Measured from the request's tools on OpenCode 2, with a four-pass redacted byte pin proving the measurement changes nothing on the wire.1 (existing state after the V1→V2 store conversion) — the serious one, and not something we will patch reader-by-reader. We reproduced your three cases against the real 2.0.5 conversion (the split arm is byte-identical in 2.0.11) and found a fourth: the FTS index ends up with the last message indexed twice, at its old and new ordinal, because the duplicate check joins on the moved ordinal. The full inventory is 22 coordinate-bearing rows. The design choice is between a one-time per-session rebase on first V2 open (re-derive every ordinal from surviving message IDs, mark the unresolvable explicitly, rebuild that session's index and chunk windows, fold merged part tags, one priced fold) and treating a generation flip as a fresh Magic Context session with project memory kept. Until it lands, the honest statement is: opening a converted 1.x store on OpenCode 2 with existing Magic Context state can produce wrong compartment ranges, a stale search index and broadened queued drops; do not rely on that path yet. The chosen design will be posted here before implementation, and a real-host old-state startup → resume → compaction → restart lifecycle is in scope for it.
The two exclusions (bus scoping, disposal cancellation) match our reading. Items 2–7 ship in the next release.
magic-alfonso commentedon Sep 21, 2026
Design decision for finding 1, as promised: a per-session rebase, keyed on the store projection generation, bidirectional, anchored on message IDs.
Implementation is in progress with the real 2.0.5 conversion in the loop, including the way-back case and a crash mid-rebase. It moves the schema fence, so it ships in a release rather than a patch; I will note here when it lands.
ualtinok commentedon Sep 24, 2026
Shipped in 0.43.0, all seven findings: existing sessions keep correct coordinates across the OpenCode 1 → 2 conversion and back (one rebase per session on the first pass after a switch), dreamer tasks are routed by what the v2 host can run, server commands are registered, hidden child sessions are deleted through their owning host, Rust mode on OpenCode 2 reports itself as unavailable instead of failing silently, the tool-description leak across sessions is gone, and Tool Defs are measured from the real draft. Closing.