@@ -25,8 +25,9 @@ const adapter = await createBridgeAdapter({
2525const unsubscribe = adapter .onOutcome (async (messageId , subject , outcome ) => {
2626 // Observe committed ops/unavailability; context.db remains the replay owner.
2727});
28- const view = await adapter .run ({ sessionId , messages: rawHostWindow , kind: " model" });
29- // Send this view, not the unchecked rawHostWindow.
28+ const { messages : view, kind } = await adapter .run ({ sessionId , messages: rawHostWindow , kind: " model" });
29+ // Send this view, including its synthetic user head, not the unchecked rawHostWindow.
30+ // Log kind: "ordinary", "bootstrap", or "rebuild".
3031unsubscribe ();
3132await adapter .dispose ();
3233```
@@ -48,11 +49,113 @@ whether the selected model, system/tool fingerprint or persisted source state ha
4849changed. Idle-cache time and token pressure can also require a rebuilt history
4950view, using the same rules as the in-tree provider path.
5051` run ` owns ` SessionMeta ` , publication, normalization through ` providerIncoming ` ,
51- and the bootstrap/rebuild record; it returns a ` Promise<MessageLike[]> ` view.
52+ and the bootstrap/rebuild record; it returns `Promise<{ messages: MessageLike[ ] ,
53+ kind: "ordinary" | "bootstrap" | "rebuild" }>`.
5254Supply the complete raw host window in append order with its real identities,
5355not just the new tail or a previous provider projection. No private type or
5456mutable output parameter is needed.
5557
58+ ### Head placement and project identity
59+
60+ The OpenCode provider's head is ** inside ` messages ` ** , not a system contribution.
61+ Its first two entries, m[ 0] and m[ 1] , are id-less synthetic ** user** messages, just
62+ as in the in-tree plugin. m[ 0] carries project docs, the user profile,
63+ ` <session-history> ` and ` <project-memory> ` ; m[ 1] carries incremental history and
64+ memory corrections when present. Send both entries at the head of the provider
65+ window. Do not filter them out, turn them into system text, persist them as raw
66+ host history, or add a second memory injection. ` run ` does not drop a separate
67+ system field: there is no such head field on the OpenCode provider answer.
68+
69+ Pass ` projectRoot ` ** exactly as the host spells it; do not realpath it first** .
70+ The shared identity resolver uses ` git:<root-commit> ` for repositories with
71+ history and ` dir:<MD5-prefix of path.resolve(root)> ` otherwise. A trailing slash
72+ does not change the key, but symlink spellings and macOS ` /var ` versus
73+ ` /private/var ` can. Transport still binds a realpath for filesystem authority.
74+ Before provider rendering the bridge records the host-resolved identity in
75+ ` session_projects ` under its instance's harness. ck-mc prefers that binding to
76+ recomputing the identity from the canonical route. Host watermarks and all
77+ project-scoped module reads use that identity; docs use the equivalent bound
78+ filesystem directory. Re-keying existing directory memories is not part of this
79+ API. Seed/write memories using the same host spelling and shared resolver.
80+
81+ Setup's ` answer: "ready" ` inserts placeholders; the bootstrap's final status pass
82+ renders the real head even with ** zero compartments** (stored history summaries)
83+ and no historian execution (background model calls that create those summaries).
84+ Memory and profile injection need ` memory.enabled ` (default true),
85+ ` memory.injection_budget_tokens ` (default 4,000) and
86+ ` memory.user_profile_budget_tokens ` ; docs need ` dreamer.inject_docs ` (default true). The provider lane requires
87+ ` compaction.enabled ` (default true); disabling it refuses setup, rather than
88+ silently serving a memory-less provider window.
89+
90+ The TS facade's ` configPath ` does not configure an independently launched ck-mc.
91+ At this pin ck-mc reads its user tier from
92+ ` $XDG_CONFIG_HOME/cortexkit/magic-context.jsonc ` (or ` .json ` ) plus the project's
93+ ` .cortexkit ` tier; it does ** not** read ` MAGIC_CONTEXT_CONFIG_PATH ` .
94+ ` MAGIC_CONTEXT_STORAGE_DIR ` selects storage, not head settings. An env-only
95+ launch still gets the default enabled head, but explicit overrides must be
96+ installed in ck-mc's isolated XDG config tier as well. Staged ck-mc ` cc8617ec `
97+ has identical Rust provider/identity/render/config code to ` 245aa977 ` ; it does
98+ not need rebuilding for this fix.
99+
100+ ### Whole-step budgets and cancellation
101+
102+ The facade classifies the pass using the provider adapter's own bootstrap and
103+ rebuild checks (source revert, model/system/tool manifest, explicit refresh and
104+ idle cache). ` kind: "model" ` remains the input; it is not a budget hint.
105+
106+ | Result/error ` kind ` | Whole-pass budget |
107+ | --- | --- |
108+ | ` ordinary ` | 5,000 ms |
109+ | ` bootstrap ` | 30,000 ms |
110+ | ` rebuild ` | 30,000 ms |
111+
112+ The budget starts on entry to ` run ` and covers preflight, all provider/ck-mc calls,
113+ assembly and the final host-record commit. Individual RPC limits can refuse
114+ earlier. At the deadline ` run ` rejects with ` StepBudgetExceededError ` , carrying
115+ ` { code: "step_budget_exceeded", budget_ms, kind } ` . Errors from other pass
116+ failures also carry the classified ` kind ` . The caller may then use
117+ ` isFallbackAdmissible ` ; timeout itself never authorizes unchecked raw history.
118+
119+ Bridge host-record writes are staged until a single final atomic transaction.
120+ Cancellation is checked between asynchronous stages, before each record write,
121+ and immediately before COMMIT, including a clock check when synchronous SQLite
122+ work prevents the timer from running. An expired transaction rolls back whole;
123+ no outcomes, served state, request fences or usage from an unserved timed-out
124+ pass are published. Request fences associate a fresh RPC ID with its lineage and
125+ newest ordinal, preventing stale replies from overwriting a newer view. A retry
126+ reloads the prior durable record. Transport receives
127+ the cancellation signal; a remotely finishing ck-mc reply is discarded and
128+ cannot resume host writes. Session/project binding and module state-sync are
129+ independent authority metadata, not proof that a provider step was served.
130+
131+ Bridge ` onOutcome ` is a fire-and-forget notification ** after successful commit
132+ and publication** , outside the step budget. It emits nothing for cancelled
133+ passes, is not awaited by ` run ` , and has no ordering guarantee relative to
134+ ` run ` 's resolution. Observer failures are isolated and logged once per session.
135+ ` dispose ` drains outstanding notifications before closing handles.
136+
137+ ### Raw host-history markers and open items
138+
139+ Budgeted bridge passes ** do not invoke the native raw-history marker writer** .
140+ That writer commits OpenCode's own database independently of ` context.db ` ; it
141+ cannot safely trim raw history inside a transaction that might roll back the
142+ bridge view at the deadline. Coverage is still committed in the bridge's durable
143+ view. Marker advancement is an optimization, never a condition for serving a
144+ step.
145+
146+ Without markers, OpenCode keeps handing the full raw history to each step. The
147+ returned compacted view stays correct, but per-step host work grows with the
148+ session. The bridge integration must also ** keep OpenCode's native
149+ auto-compaction declined while Magic Context owns compaction** ; the facade does
150+ not install the host's compaction-decline hook on the integration's behalf.
151+
152+ Open follow-up: add bridge marker advancement as a separate step ** after view
153+ publication** , outside the step budget and idempotent on retry. Commit
154+ ` context.db ` coverage first, then advance the ` opencode.db ` marker. A crash
155+ between these writes must leave an untrimmed host (safe), never a trimmed host
156+ whose coverage Magic Context has not recorded. The native in-tree marker path
157+ is unchanged.
158+
56159The store is an independently owned handle opened by the shared storage module,
57160with the ordinary WAL/NORMAL policy, foreign keys, 5-second busy timeout, private
58161permissions and write-transaction helpers. It does not reuse or close another
@@ -88,7 +191,7 @@ v2's host-history registration lifecycle.
88191rejects new calls immediately, drains in-flight passes and stops the historian,
89192then closes the module connection and its store handle exactly once. Methods
90193other than repeated disposal refuse after disposal. Observers can unsubscribe;
91- they are awaited after durable commit, as described below .
194+ the bridge dispatches them after durable commit, as described above .
92195
93196### Refused provider handoffs
94197
@@ -187,10 +290,12 @@ outcomes are superseded by the durable replacement in `host_runner_views`; cover
187290messages are not re-hooked. A lineage descent/revert can deliberately re-admit a
188291changed suffix.
189292
190- ` OpenCodeProviderOptions.onOutcome(messageId, subject, outcome) ` is optional and
191- awaited for newly committed outcomes, after the pass commit and before publication
192- or ` run() ` returning. It observes/mirrors the record; it never replaces persistence.
193- An observer failure refuses that pass's publication but leaves the frozen outcomes
293+ The low-level native ` OpenCodeProviderOptions.onOutcome(messageId, subject, outcome) `
294+ is optional and awaited for newly committed outcomes, after the pass commit and
295+ before publication or ` run() ` returning. The supported bridge instead dispatches
296+ observers after publication, without awaiting them, as described above. Both
297+ observe/mirror the record; neither replaces persistence.
298+ On the native path an observer failure refuses publication but leaves the frozen outcomes
194299committed. Notifications are not an exactly-once delivery queue and are not re-sent
195300on replay; a process can die after commit but before observation. Bridges must not
196301depend on a separate mirror to reconstruct the served view. The SIGKILL fixture
0 commit comments