@elvisw/dsh-magic-context 0.0.0-stage → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +23 -0
  2. package/NOTICE +93 -0
  3. package/README.md +501 -2
  4. package/README.zh-CN.md +398 -0
  5. package/cordis.patch.yml +20 -0
  6. package/lib/client.js +2753 -0
  7. package/lib/core/compartments.js +424 -0
  8. package/lib/core/corpus-store.js +176 -0
  9. package/lib/core/directive-detect.js +147 -0
  10. package/lib/core/domain-reconcile.js +394 -0
  11. package/lib/core/domains.js +127 -0
  12. package/lib/core/embedding-local.js +247 -0
  13. package/lib/core/global-autostage.js +223 -0
  14. package/lib/core/global-domains.js +302 -0
  15. package/lib/core/journal.js +248 -0
  16. package/lib/core/llm-deadline.js +53 -0
  17. package/lib/core/machine-domain.js +106 -0
  18. package/lib/core/maintenance.js +380 -0
  19. package/lib/core/memory-bridge.js +818 -0
  20. package/lib/core/memory-categories.js +84 -0
  21. package/lib/core/memory-scoring.js +65 -0
  22. package/lib/core/memory-search.js +414 -0
  23. package/lib/core/memory-write.js +1176 -0
  24. package/lib/core/policy.js +111 -0
  25. package/lib/core/profile-confirmation.js +55 -0
  26. package/lib/core/profile-review.js +375 -0
  27. package/lib/core/profile-write.js +283 -0
  28. package/lib/core/reference-seeds.js +63 -0
  29. package/lib/core/route-health.js +106 -0
  30. package/lib/core/stores.js +1021 -0
  31. package/lib/core/tagger.js +7 -0
  32. package/lib/core/types.js +15 -0
  33. package/lib/core/vector-backfill.js +140 -0
  34. package/lib/core/vector-lane.js +184 -0
  35. package/lib/dsh/command-surface.js +376 -0
  36. package/lib/dsh/compaction-engine.js +231 -0
  37. package/lib/dsh/config-switches.js +56 -0
  38. package/lib/dsh/config.js +13 -0
  39. package/lib/dsh/context-pressure.js +36 -0
  40. package/lib/dsh/drop-landing.js +42 -0
  41. package/lib/dsh/emergency.js +89 -0
  42. package/lib/dsh/flush-command.js +148 -0
  43. package/lib/dsh/global-review.js +412 -0
  44. package/lib/dsh/historian.js +907 -0
  45. package/lib/dsh/live-journal.js +81 -0
  46. package/lib/dsh/maintenance-wiring.js +229 -0
  47. package/lib/dsh/management-pass.js +564 -0
  48. package/lib/dsh/memory-lane.js +535 -0
  49. package/lib/dsh/pre-step.js +675 -0
  50. package/lib/dsh/project-drift.js +116 -0
  51. package/lib/dsh/recomp.js +192 -0
  52. package/lib/dsh/reduce-guidance.js +188 -0
  53. package/lib/dsh/seams.js +588 -0
  54. package/lib/dsh/search-hint.js +186 -0
  55. package/lib/dsh/session-notice.js +100 -0
  56. package/lib/dsh/status-command.js +75 -0
  57. package/lib/dsh/telemetry.js +349 -0
  58. package/lib/dsh/tool-bridge.js +1009 -0
  59. package/lib/dsh/volatile.js +89 -0
  60. package/lib/index.js +931 -0
  61. package/package.json +108 -4
  62. package/tools/migrate-profile-package-name.mjs +317 -0
  63. package/vendor/onnx/LICENSE +27 -0
  64. package/vendor/onnx/ort-wasm-simd-threaded.mjs +59 -0
  65. package/vendor/onnx/ort-wasm-simd-threaded.wasm +0 -0
  66. package/vendor/onnx/ort.node.min.mjs +8 -0
@@ -0,0 +1,675 @@
1
+ import { decidePass, pressurePercent, pressureWindowTokens } from "../core/policy.js";
2
+ import { PROFILE_M0_BUDGET_TOKENS, resolveIdentitySpec } from "../core/memory-bridge.js";
3
+ import { errText } from "../core/types.js";
4
+ import { estimateTokens } from "../core/compartments.js";
5
+ import { DSH_EVENTS, enumerateSurfaceToolResults, requestContextOf, wireStrandedResults, } from "./seams.js";
6
+ import { runManagementPass } from "./management-pass.js";
7
+ import { landPendingDrops } from "./drop-landing.js";
8
+ import { syncMachineDomain } from "../core/machine-domain.js";
9
+ import { buildChannel1Reminder, buildChannel2Reminder, buildDeskSection, decideReduceNudge, } from "./reduce-guidance.js";
10
+ import { appendSessionNotice } from "./session-notice.js";
11
+ import { detectProjectDrift } from "./project-drift.js";
12
+ import { liveBoolean, liveRoute, liveString } from "./volatile.js";
13
+ import { planSearchHint } from "./search-hint.js";
14
+ /**
15
+ * The newest REAL user prompt in this request, for the auto-search hint. Host
16
+ * context nodes and this adapter's own entries are user-role messages too, so they
17
+ * are skipped by their markers instead of by role alone.
18
+ */
19
+ function latestUserPromptText(messages) {
20
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
21
+ const message = messages[index];
22
+ if (message === undefined || message.role !== 'user' || !Array.isArray(message.content))
23
+ continue;
24
+ const text = message.content
25
+ .filter(block => block.type === 'text' && typeof block.text === 'string')
26
+ .map(block => block.text)
27
+ .join('\n');
28
+ if (text.trim().length === 0)
29
+ continue;
30
+ if (text.includes('<magic-context>') || text.startsWith('Magic Context ·'))
31
+ continue;
32
+ return { text, turnKey: typeof message.id === 'string' ? message.id : text.slice(0, 64) };
33
+ }
34
+ return undefined;
35
+ }
36
+ export function preStepListener(deps) {
37
+ const { ctx, config, stores, journal, llm, reduceTracker, lane, projects, seenSessions, activePolicy } = deps;
38
+ const { seenIdentities, sessionIdentity } = projects;
39
+ const { memoryRecall, memoryWriter, profileWriter, visibleMemoryIdsBySession } = lane;
40
+ /** Consecutive defers per session, keyed by reason, to log transitions only. */
41
+ const deferStreak = new Map();
42
+ /** One-shot flag: a drifted pre-step payload must warn once, not every step. */
43
+ let payloadDriftWarned = false;
44
+ return async (payload, next) => {
45
+ // Engine mount/unmount rides the step band: reconcile is two boolean
46
+ // reads when nothing changed and must never fail the turn.
47
+ deps.reconcileEngine?.();
48
+ const decision = await next();
49
+ if (decision.kind !== 'enter')
50
+ return decision;
51
+ // Shape-tolerant identity read: this line sits BEFORE every fail-open band
52
+ // below, so a host refactor that drops `agent` from the pre-step payload
53
+ // (the agent dispatcher injects it today) would otherwise throw into the
54
+ // host's waterfall and fail the whole turn. Skipping this step's adapter
55
+ // work degrades to "adapter idle" instead; the doctor heartbeat is what
56
+ // surfaces the drift durably.
57
+ const sessionId = payload.agent?.session?.id;
58
+ if (sessionId === undefined) {
59
+ if (!payloadDriftWarned) {
60
+ payloadDriftWarned = true;
61
+ journal?.error('error', 'pre-step payload has no agent.session.id — adapter bands skipped this step (host payload drift?)', { scope: 'pre-step' });
62
+ }
63
+ return decision;
64
+ }
65
+ // Admission stamps NOTHING — deliberately. `ctx_reduce` and the drop /
66
+ // emergency lanes resolve tags against surface TOOL RESULTS; `ctx_expand`
67
+ // resolves the persisted tag record and its call's original event, so it
68
+ // needs no live placeholder. A handle on a user message or on a
69
+ // host-injected context node (runtime-context, skill-catalog, time-context)
70
+ // could never be acted on: `ctx_reduce` would answer "no such tool-output
71
+ // tag on the current surface", and `ctx_expand` would answer "not a
72
+ // tool-result tag". Unlike opencode/pi — which stamp a throwaway per-call
73
+ // array, so the stamp never reaches the record — DSH has ONE surface shared
74
+ // by the model and the UI, and no wire-only seam. A dead handle is not
75
+ // free: it shows up in the conversation transcript, and `session-title`
76
+ // derives its deterministic fallback from the first `source: 'user'`
77
+ // message, so it becomes the session title too (measured live: seq 9
78
+ // `§1§ …` -> seq 15 `title: "§1§ 一个新的问题:现在会话"`). Only tool
79
+ // results carry handles; the stream is untouched.
80
+ const messages = decision.messages;
81
+ // Manual `/ctx-flush`: the command only persisted markers. Land them here,
82
+ // before the pressure decision, so the NEXT request sees the placeholders
83
+ // even when the ordinary execute band would defer. This is the same landing
84
+ // helper the automatic pass uses; it never starts a Historian or a summary.
85
+ // `flushLanded` also makes the pressure reading stale: the surface changed
86
+ // after that reading, so the same turn's force band must not reclaim a
87
+ // second batch against it.
88
+ let flushLanded = false;
89
+ const replacedTags = [];
90
+ const generationBeforeFlush = payload.agent.session.surface?.replaceGeneration;
91
+ try {
92
+ const flushTags = deps.stores.listFlushRequestedDrops(sessionId);
93
+ if (flushTags.length > 0) {
94
+ const landing = landPendingDrops({
95
+ session: payload.agent.session,
96
+ sessionId,
97
+ stores: deps.stores,
98
+ tags: flushTags,
99
+ journal: deps.journal,
100
+ onReplaced: tag => {
101
+ flushLanded = true;
102
+ replacedTags.push(tag);
103
+ },
104
+ });
105
+ flushLanded = flushLanded || landing.landed.length > 0;
106
+ deps.stores.clearFlushRequests(sessionId, flushTags);
107
+ }
108
+ }
109
+ catch (error) {
110
+ // Keep the markers so a later preStep can retry; the command already
111
+ // told the user the change is pending, not applied. No session notice is
112
+ // appended: the flush lane must not mutate the surface outside landing.
113
+ deps.journal?.warn('error', `flush landing failed: ${errText(error)}`, { scope: 'flush', replacedTags }, sessionId);
114
+ }
115
+ finally {
116
+ // A partial landing can throw after replacing a node but before the
117
+ // caller sees `landing`; the surface generation is the authoritative
118
+ // signal that this turn's pressure reading is stale.
119
+ if (generationBeforeFlush !== undefined
120
+ && payload.agent.session.surface.replaceGeneration !== generationBeforeFlush) {
121
+ flushLanded = true;
122
+ }
123
+ }
124
+ // Reduce guidance (desk section + Channel 1/2) is wired through the
125
+ // SystemPrompt service below — a static section and a dynamic context —
126
+ // not by rewriting messages here. The only preStep duty is the REAL USER
127
+ // TURN counter the sticky refire gap needs: observing the newest message
128
+ // id is idempotent per step, so repeated preSteps on one turn count once.
129
+ if (liveBoolean(config.reduceGuidance, true)) {
130
+ try {
131
+ const newest = messages.at(-1);
132
+ if (newest !== undefined)
133
+ reduceTracker.observeUserMessage(sessionId, newest.id);
134
+ }
135
+ catch {
136
+ // counting is advisory; never fail the turn for it
137
+ }
138
+ }
139
+ // Auto-search hint (upstream `auto-search-runner`): one hint per user turn,
140
+ // built from the user's own words. Awaited, because the hint has to ride
141
+ // the request it was computed for, but every path is fail-open and the
142
+ // search carries its own deadline.
143
+ //
144
+ // The hint does NOT append to the session here. This waterfall band runs
145
+ // BEFORE the host writes step/start and the first system/message, so a
146
+ // direct append on a fresh session's first step took surface node 0 and the
147
+ // loader rejected the log on replay (the 2026-10-09 protected-head
148
+ // incident). The hint instead travels inside the admitted batch: the
149
+ // appendix below joins `decision.messages`, which the host persists after
150
+ // its own system-prompt commits — the head always lands first, on every
151
+ // turn, and the hint still rides this request. First-half stopgap (skip
152
+ // while the surface was headless) retired by this carrier change.
153
+ let hintAppendix;
154
+ if (deps.searchHints !== undefined && liveBoolean(config.searchHint, true)) {
155
+ try {
156
+ const prompt = latestUserPromptText(messages);
157
+ if (prompt !== undefined) {
158
+ const outcome = await planSearchHint({
159
+ search: lane.searchForSession,
160
+ tracker: deps.searchHints,
161
+ ...journal === undefined ? {} : { journal },
162
+ }, { sessionId, session: payload.agent.session, text: prompt.text, turnKey: prompt.turnKey });
163
+ if (outcome.action === 'hinted')
164
+ hintAppendix = outcome.message;
165
+ }
166
+ }
167
+ catch {
168
+ // a hint is an optimisation; never fail the turn for it
169
+ }
170
+ }
171
+ // Cross-project drift observation: the turn's own text is the only signal
172
+ // DSH gives about WHERE work is happening (there is no per-turn working
173
+ // directory). Foreign git roots in the newest message get one warn-level
174
+ // journal row per turn — the data that quantifies how often cross-project
175
+ // sessions actually happen (and whether facts land in the wrong project).
176
+ try {
177
+ const newestMessage = messages.at(-1);
178
+ const driftText = newestMessage === undefined
179
+ ? ''
180
+ : newestMessage.content
181
+ .map(block => (block.type === 'text' && typeof block.text === 'string' ? block.text : ''))
182
+ .join('\n');
183
+ if (driftText.length >= 8) {
184
+ const drift = detectProjectDrift(driftText, payload.agent.session.header.cwd);
185
+ if (drift !== undefined) {
186
+ journal?.warn('project-drift', `cross-project work observed (${String(drift.foreignRoots.length)} foreign root(s))`, {
187
+ sessionRoot: drift.sessionRoot,
188
+ foreignRoots: drift.foreignRoots,
189
+ }, sessionId);
190
+ }
191
+ }
192
+ }
193
+ catch {
194
+ // observation only; never fail the turn for it
195
+ }
196
+ // Register the session's project identity for the maintenance sweep HERE,
197
+ // not only in the pass-execute branch: a quiet conversation never crosses
198
+ // the compaction threshold, and the sweep then had no identity to serve —
199
+ // every tick spun empty (measured live 18:24, zero maintenance rows after
200
+ // a remount despite an active session).
201
+ try {
202
+ const projectSpec = config.memory?.project ?? payload.agent.session.header.cwd;
203
+ const memoryIdentity = projectSpec === undefined ? undefined : resolveIdentitySpec(projectSpec);
204
+ if (memoryIdentity !== undefined) {
205
+ seenIdentities.add(memoryIdentity);
206
+ sessionIdentity.set(sessionId, memoryIdentity);
207
+ if (sessionIdentity.size > 512) {
208
+ const oldest = sessionIdentity.keys().next().value;
209
+ if (oldest !== undefined)
210
+ sessionIdentity.delete(oldest);
211
+ }
212
+ }
213
+ }
214
+ catch {
215
+ // registration is advisory; never fail the turn for it
216
+ }
217
+ // ── Pressure decision + the execute-band management pass (Phase 1 + 2) ──
218
+ // The session surface is mutated only inside the execute band or the
219
+ // explicit `/ctx-flush` lane above: the execute band is where a priced
220
+ // bust buys reclaim (MC's "drops land on the next bust cycle", translated
221
+ // to durable surface); flush is the user's explicit exception.
222
+ try {
223
+ if (!seenSessions.has(sessionId)) {
224
+ seenSessions.add(sessionId);
225
+ journal?.info('session-first-seen', 'session first seen by the adapter', {
226
+ turn: payload.turn,
227
+ step: payload.step,
228
+ }, sessionId);
229
+ }
230
+ const latest = stores.latestUsage(sessionId);
231
+ const context = requestContextOf(payload.agent.session);
232
+ // Live thresholds: a settings-form edit reaches this pass immediately.
233
+ const policy = activePolicy();
234
+ // A route that discloses no window is scheduled against the assumed one:
235
+ // an undefined denominator defers 'no-window' on every step, so the
236
+ // execute band — and every drop waiting behind it — would never open.
237
+ const pressureWindow = pressureWindowTokens(context.contextWindow, policy);
238
+ const pressure = pressurePercent(latest, pressureWindow);
239
+ // A reading recorded before the injected surface last changed measures a
240
+ // prompt that no longer exists (a compartment landed / the baseline was
241
+ // re-frozen after it). It remains the only size signal for the pass
242
+ // decision, but the force band must not reclaim against it — that is how
243
+ // compaction ends up dropping a second batch of content for nothing.
244
+ const historyChangedAt = stores.latestHistoryChangeAt(sessionId);
245
+ const pressureStale = flushLanded || (latest !== undefined && historyChangedAt !== undefined
246
+ && historyChangedAt > latest.recordedAt);
247
+ const pass = decidePass({
248
+ latest,
249
+ pressure,
250
+ now: Date.now(),
251
+ policy,
252
+ // Iron rule: a stranded tool_result 400s every strict-provider request,
253
+ // so it forces the execute band regardless of pressure.
254
+ wireDefects: wireStrandedResults(payload.agent.session).length,
255
+ });
256
+ const usage = latest === undefined ? undefined : {
257
+ input: latest.inputTokens ?? 0,
258
+ cacheRead: latest.cacheReadTokens ?? 0,
259
+ cacheWrite: latest.cacheWriteTokens ?? 0,
260
+ };
261
+ if (context.provider !== undefined && context.model !== undefined) {
262
+ deps.noteSessionRoute?.({ provider: context.provider, model: context.model });
263
+ }
264
+ const basePassData = {
265
+ reason: pass.reason,
266
+ pressure: pressure === undefined ? undefined : Number(pressure.toFixed(1)),
267
+ // Visible in the pass row: a stale reading explains why a high
268
+ // percentage did not reclaim (see the force-band guard).
269
+ ...pressureStale ? { pressureStale: true } : {},
270
+ // The denominator the decision actually used, flagged when it was the
271
+ // assumption rather than the route's own reading.
272
+ contextWindow: pressureWindow,
273
+ ...context.contextWindow === undefined ? { contextWindowAssumed: true } : {},
274
+ route: context.provider === undefined || context.model === undefined
275
+ ? undefined
276
+ : `${context.provider}/${context.model}`,
277
+ usage,
278
+ };
279
+ if (pass.kind === 'execute') {
280
+ // The project identity is resolved once per pass (the git lookup is
281
+ // cached for the process). An explicit `git:`/`dir:` value is already
282
+ // an identity; only a directory has to be resolved.
283
+ const projectSpec = config.memory?.project ?? payload.agent.session.header.cwd;
284
+ const memoryIdentity = projectSpec === undefined
285
+ ? undefined
286
+ : resolveIdentitySpec(projectSpec);
287
+ // The maintenance sweep maintains every identity this process has
288
+ // actually served (the configured project is added unconditionally),
289
+ // and the session→identity map scopes the telemetry panel's stats to
290
+ // the project the viewed conversation is working on.
291
+ if (memoryIdentity !== undefined) {
292
+ seenIdentities.add(memoryIdentity);
293
+ if (sessionId !== undefined) {
294
+ sessionIdentity.set(sessionId, memoryIdentity);
295
+ if (sessionIdentity.size > 512) {
296
+ const oldest = sessionIdentity.keys().next().value;
297
+ if (oldest !== undefined)
298
+ sessionIdentity.delete(oldest);
299
+ }
300
+ }
301
+ }
302
+ // WP10: keep the machine domain's membership in step with the projects
303
+ // this process has actually served, BEFORE the recall resolves peers —
304
+ // otherwise a project's first pass would read the domain without being
305
+ // in it. Idempotent, and a no-op until `memory.global.machine.enabled`.
306
+ if (memoryRecall !== undefined) {
307
+ syncMachineDomain(stores, {
308
+ identities: [...seenIdentities],
309
+ config: {
310
+ ...config.memory?.global?.machine?.enabled === undefined
311
+ ? {}
312
+ : { enabled: liveBoolean(config.memory?.global?.machine?.enabled, true) },
313
+ ...config.memory?.global?.machine?.excludeProjects === undefined
314
+ ? {}
315
+ : { excludeProjects: config.memory.global.machine.excludeProjects },
316
+ },
317
+ now: Date.now(),
318
+ });
319
+ }
320
+ const memories = memoryRecall?.recall({
321
+ ...projectSpec === undefined ? {} : { project: projectSpec },
322
+ domainProjects: config.memory?.domainProjects,
323
+ shareCategories: config.memory?.shareCategories,
324
+ budgetTokens: policy.memoryBudgetTokens,
325
+ limit: policy.memoryLimit,
326
+ // B2 (MC m0_compose.rs:104-116): the frozen prefix evaluates expiry
327
+ // against the HARD-rebuild clock — a row expiring mid-cycle must not
328
+ // bust the cache. The next HARD rebuild re-composes with a live clock
329
+ // and freezes the new time.
330
+ now: stores.baselineState(sessionId)?.frozenAt ?? Date.now(),
331
+ // The additive lane: everything above the stored watermark is an
332
+ // in-session addition and renders in m[1], keeping m[0] byte-stable.
333
+ ...stores.baselineState(sessionId)?.maxMemoryId === undefined
334
+ ? {}
335
+ : { maxMemoryId: stores.baselineState(sessionId)?.maxMemoryId },
336
+ // S(t) decay scoring (schema v7): the sidecar stats + live config;
337
+ // `enabled` inside gates the ranking takeover, stats always ride.
338
+ ...memoryIdentity === undefined ? {} : { scoring: lane.memoryScoring(memoryIdentity) },
339
+ });
340
+ // WP8: the user profile. Its own read with its own watermark — the
341
+ // profile is unaffected by project identity, and this lane can be
342
+ // switched off (`memory.global.user.render`, default true) without
343
+ // touching memory recall at all.
344
+ const profileEnabled = liveBoolean(config.memory?.global?.user?.render, true);
345
+ const profile = !profileEnabled || memoryRecall === undefined
346
+ ? undefined
347
+ : memoryRecall.profileRecall({
348
+ // Frozen-prefix discipline, same as memory recall: the m[0] profile
349
+ // evaluates against the HARD-rebuild clock, not a live one.
350
+ now: stores.baselineState(sessionId)?.frozenAt ?? Date.now(),
351
+ budgetTokens: PROFILE_M0_BUDGET_TOKENS,
352
+ ...stores.baselineState(sessionId)?.profileWatermark === undefined
353
+ ? {}
354
+ : { maxId: stores.baselineState(sessionId)?.profileWatermark },
355
+ });
356
+ // A2 (MC getVisibleMemoryIds): the ids the injected block renders NOW
357
+ // are what ctx_memory search suppresses as already-visible. Replaced
358
+ // per pass, process-memory only, fail-open when absent.
359
+ if (memories !== undefined) {
360
+ visibleMemoryIdsBySession.delete(sessionId);
361
+ visibleMemoryIdsBySession.set(sessionId, new Set([
362
+ ...memories.rows.map(row => row.id),
363
+ ...memories.newRows.map(row => row.id),
364
+ ]));
365
+ if (visibleMemoryIdsBySession.size > 256) {
366
+ const oldest = visibleMemoryIdsBySession.keys().next().value;
367
+ if (oldest !== undefined)
368
+ visibleMemoryIdsBySession.delete(oldest);
369
+ }
370
+ }
371
+ // S(t) sidecar (schema v7): recall injections strengthen the rows for
372
+ // future ranking, and the orphan sweep drops stats rows whose memory
373
+ // left the visible pool. Shadow collection — runs whether or not the
374
+ // ranking takeover is enabled. Fail-open: scoring stats are advisory.
375
+ if (memories !== undefined && memoryIdentity !== undefined) {
376
+ try {
377
+ const statsNow = Date.now();
378
+ for (const row of memories.rows)
379
+ stores.recordMemoryEvent(memoryIdentity, row.id, 'injection', statsNow);
380
+ for (const row of memories.newRows)
381
+ stores.recordMemoryEvent(memoryIdentity, row.id, 'injection', statsNow);
382
+ const knownIds = new Set(memories.eligibleIds);
383
+ for (const row of memories.newRows)
384
+ knownIds.add(row.id);
385
+ stores.pruneMemoryStats(memoryIdentity, [...knownIds]);
386
+ }
387
+ catch {
388
+ /* scoring stats are advisory */
389
+ }
390
+ }
391
+ // The watermark is read straight from the store, so a HARD rebuild can
392
+ // freeze it even when this pass recalled no rows at all (the first
393
+ // pass of a fresh session, which is exactly when it matters).
394
+ const currentMaxMemoryId = memoryRecall?.maxMemoryId(projectSpec) ?? 0;
395
+ // The NON-additive lane (contract §6): `memory_mutation_log` rows above
396
+ // the frozen cursor render as `<memory-updates>` in the m[1] region, so
397
+ // an update/archive/supersede reaches the model without rebuilding m[0].
398
+ // `afterId` is the cursor frozen by the last HARD rebuild (0 for a
399
+ // session that never folded), and the epoch fingerprint is passed on
400
+ // EVERY pass: management-pass only flags an epoch change when both the
401
+ // previous fingerprint and this one are present.
402
+ const mutations = memoryRecall?.mutations({
403
+ ...projectSpec === undefined ? {} : { project: projectSpec },
404
+ domainProjects: config.memory?.domainProjects,
405
+ shareCategories: config.memory?.shareCategories,
406
+ afterId: stores.baselineState(sessionId)?.maxMemoryMutationId ?? 0,
407
+ limit: policy.memoryLimit,
408
+ now: Date.now(),
409
+ });
410
+ if (memories !== undefined && (memories.lines.length > 0 || memories.newLines.length > 0)) {
411
+ journal?.info('memory-recall', `recalled ${String(memories.rows.length)} project memories into m[0]`
412
+ + `${memories.newLines.length > 0 ? `, ${String(memories.newLines.length)} new in m[1]` : ''}`, {
413
+ rows: memories.rows.length,
414
+ newRows: memories.newLines.length,
415
+ digest: memories.digest.slice(0, 12),
416
+ budgetTokens: policy.memoryBudgetTokens,
417
+ estTokens: memories.lines.reduce((total, line) => total + estimateTokens(line), 0),
418
+ identity: memories.identity,
419
+ maxMemoryId: memories.currentMaxMemoryId,
420
+ domains: memories.domains,
421
+ members: memories.members?.length,
422
+ projects: [...new Set(memories.rows.map(row => row.projectPath.slice(0, 24)))],
423
+ }, sessionId);
424
+ }
425
+ if (profile !== undefined && (profile.lines.length > 0 || profile.newLines.length > 0)) {
426
+ journal?.info('profile-recall', `injected ${String(profile.lines.length)} profile row(s) into m[0]`
427
+ + `${profile.newLines.length > 0 ? `, ${String(profile.newLines.length)} new in m[1]` : ''}`, {
428
+ rows: profile.rows.length,
429
+ newRows: profile.newRows.length,
430
+ digest: profile.digest.slice(0, 12),
431
+ watermark: stores.baselineState(sessionId)?.profileWatermark,
432
+ currentMaxId: profile.currentMaxId,
433
+ estTokens: profile.lines.reduce((total, line) => total + estimateTokens(line), 0),
434
+ }, sessionId);
435
+ }
436
+ // The generation pin is a live settings-form field: re-read it every
437
+ // pass, and treat a half-filled pair as "follow the session route".
438
+ const generationRoute = liveRoute(config.historian?.provider, config.historian?.model);
439
+ const generationEffort = liveString(config.historian?.reasoningEffort, '').trim();
440
+ // Auto follows the session route; the effort still applies. Only when
441
+ // neither a pin nor a live session route exists is there nothing to
442
+ // attach it to.
443
+ const sessionRoute = context.provider === undefined || context.model === undefined
444
+ ? undefined
445
+ : { provider: context.provider, model: context.model };
446
+ const historianBaseRoute = generationRoute ?? sessionRoute;
447
+ const historianRoute = historianBaseRoute === undefined
448
+ ? undefined
449
+ : generationEffort === ''
450
+ ? historianBaseRoute
451
+ : { ...historianBaseRoute, reasoningEffort: generationEffort };
452
+ const outcome = runManagementPass({
453
+ session: payload.agent.session,
454
+ sessionId,
455
+ stores,
456
+ policy,
457
+ logger: ctx.logger,
458
+ pass,
459
+ pressure,
460
+ ...pressureStale ? { pressureStale: true } : {},
461
+ ...journal === undefined ? {} : { journal },
462
+ ...context.provider === undefined || context.model === undefined
463
+ ? {}
464
+ : { route: { provider: context.provider, model: context.model } },
465
+ ...context.contextWindow === undefined ? {} : { contextWindow: context.contextWindow },
466
+ ...liveBoolean(config.historianEnabled, true) === false || llm === undefined
467
+ ? {}
468
+ : {
469
+ historian: {
470
+ llm,
471
+ ...historianRoute === undefined ? {} : { route: historianRoute },
472
+ },
473
+ },
474
+ ...memoryRecall === undefined
475
+ ? {}
476
+ : {
477
+ memories: {
478
+ digest: memories?.digest ?? '',
479
+ lines: memories?.lines ?? [],
480
+ newLines: memories?.newLines ?? [],
481
+ // An `update` on a memory that is still above the watermark
482
+ // writes a mutation-log row without changing the id, so the row
483
+ // would otherwise render once in `<new-memories>` (current
484
+ // content) and again in `<memory-updates>` (same content).
485
+ // Passing the ids lets the renderer suppress the duplicate.
486
+ newMemoryIds: memories?.newRows.map(row => row.id) ?? [],
487
+ // MC only announces a mutation whose target is a row the m[0]
488
+ // baseline actually rendered (inject-compartments.ts:2843-2848):
489
+ // mutations for budget-truncated rows would be phantom pointers.
490
+ baselineIds: memories?.rows.map(row => row.id) ?? [],
491
+ // A1: the pre-budget visible set — the authority for forced
492
+ // rows in `<memory-updates>`/`<new-memories>`
493
+ // (inject-compartments.ts:2822-2869).
494
+ eligibleIds: memories?.eligibleIds ?? [],
495
+ eligibleRows: memories?.eligibleRows ?? [],
496
+ maxMemoryId: currentMaxMemoryId,
497
+ },
498
+ },
499
+ ...profile === undefined ? {} : { profile },
500
+ // `cursor` is deliberately the CURRENT max mutation id, not the paged
501
+ // read cursor: on a HARD rebuild m[0] swallows everything up to now,
502
+ // so freezing a partial page would re-render rows it already folded.
503
+ ...mutations === undefined
504
+ ? {}
505
+ : {
506
+ memoryUpdates: {
507
+ updates: mutations.updates,
508
+ cursor: mutations.currentCursor,
509
+ projectEpochFingerprint: mutations.projectEpochFingerprint,
510
+ },
511
+ },
512
+ ...memoryWriter === undefined || memoryIdentity === undefined || config.memory?.autoPromote === false
513
+ ? {}
514
+ : { memoryPromote: (promote) => memoryWriter.promoteFacts(promote), memoryIdentity },
515
+ // WP5: the `user` domain's collection switch — read LIVE, so it can be
516
+ // flipped without a remount, and default OFF because this lane stages
517
+ // rows in the shared contract. The historian journals what it staged
518
+ // (including what normalization dropped), so the switch's effect is
519
+ // visible from the journal rather than inferred from the pool size.
520
+ ...profileWriter === undefined || profileWriter.status.ok !== true
521
+ || !liveBoolean(config.memory?.global?.user?.collect, true)
522
+ ? {}
523
+ : {
524
+ profileCollect: (collect) => profileWriter.recordCandidates(collect),
525
+ },
526
+ });
527
+ journal?.info('pass', `execute (${pass.reason}) at ${pressure?.toFixed(1) ?? '?'}%`, {
528
+ decision: 'execute',
529
+ ...basePassData,
530
+ landed: outcome.landedDrops,
531
+ published: outcome.published,
532
+ forceDropped: outcome.forceDropped,
533
+ baseline: outcome.baselineUpdated ? 'refreshed' : 'unchanged',
534
+ historianStarted: outcome.historianStarted,
535
+ // Net reclaim proof: surface estimate before/after this pass.
536
+ tokensBefore: outcome.tokensBefore,
537
+ tokensAfter: outcome.tokensAfter,
538
+ tokensDelta: outcome.tokensAfter - outcome.tokensBefore,
539
+ factsPromoted: outcome.factsPromoted,
540
+ factsSeenAgain: outcome.factsSeenAgain,
541
+ }, sessionId);
542
+ // Channel A of the live session stream: one notice entry per pass
543
+ // that actually did something (publish / force-drop / promotion).
544
+ // `appendSessionNotice` is fail-open, quiet on nothing-notable, and
545
+ // refuses a headless surface (an out-of-band append before the host
546
+ // wrote surface node 0 would make the log unloadable — 2026-10-09
547
+ // incident; see docs/dsh-surface-write-audit-2026-10-09.md). A refusal
548
+ // means the execute band opened on a headless surface, which should be
549
+ // impossible (no surface ⇒ no usage and no wire defects ⇒ defer), so it
550
+ // is journaled as an anomaly rather than silently dropped.
551
+ if (liveBoolean(config.sessionNotices, true)) {
552
+ const notice = appendSessionNotice({ session: payload.agent.session, result: outcome });
553
+ if (notice.ok) {
554
+ journal?.info('session-notice', notice.summary, { seq: notice.seq }, sessionId);
555
+ }
556
+ else if (notice.reason === 'headless-surface') {
557
+ journal?.warn('session-notice', 'notice suppressed: session surface has no protected head yet', {
558
+ reason: notice.reason,
559
+ }, sessionId);
560
+ }
561
+ else if (notice.reason === 'append-failed') {
562
+ journal?.warn('session-notice', `notice append failed: ${notice.detail ?? '?'}`, {
563
+ detail: notice.detail,
564
+ }, sessionId);
565
+ }
566
+ }
567
+ deferStreak.delete(sessionId);
568
+ }
569
+ else {
570
+ // Defer passes repeat every step: record the reason TRANSITIONS only,
571
+ // carrying how many consecutive defers shared the current reason, so
572
+ // the journal stays reviewable instead of flooding.
573
+ const streak = deferStreak.get(sessionId);
574
+ if (streak === undefined || streak.reason !== pass.reason) {
575
+ deferStreak.set(sessionId, { reason: pass.reason, count: 1 });
576
+ journal?.info('pass', `defer (${pass.reason}) at ${pressure?.toFixed(1) ?? '?'}%`, {
577
+ decision: 'defer',
578
+ ...basePassData,
579
+ consecutive: 1,
580
+ }, sessionId);
581
+ }
582
+ else {
583
+ streak.count += 1;
584
+ }
585
+ }
586
+ }
587
+ catch (error) {
588
+ const detail = errText(error);
589
+ journal?.error('error', `management pass failed: ${detail}`, { scope: 'pre-step', detail }, sessionId);
590
+ // A failed management pass must never wedge the turn: log and continue
591
+ // with the tagged messages (MC per-turn fail-open). The next pass
592
+ // re-decides from durable state.
593
+ ctx.logger.warn(`magic-context: management pass failed (continuing): ${detail}`);
594
+ }
595
+ return { ...decision, messages: hintAppendix === undefined ? messages : [...messages, hintAppendix] };
596
+ };
597
+ }
598
+ /**
599
+ * Reduce guidance through the SystemPrompt service (MC parity): a STATIC desk
600
+ * section (identical bytes every assembly, so the system prompt stays
601
+ * prefix-cache stable) plus a DYNAMIC context whose provider re-measures the
602
+ * unstamped tail at every assembly and renders the MC reminder copy only when
603
+ * a band fires — an empty text contributes nothing. The listeners dispose with
604
+ * the inject fiber (a scope reload re-runs the whole inject).
605
+ */
606
+ export function injectReduceGuidance(deps) {
607
+ const { ctx, config, stores, journal, reduceTracker, lane } = deps;
608
+ if (liveBoolean(config.reduceGuidance, true) === false)
609
+ return;
610
+ ctx.inject(['systemPrompt'], (scope) => {
611
+ const systemPrompt = scope.systemPrompt;
612
+ if (systemPrompt === undefined)
613
+ return;
614
+ scope.effect(() => systemPrompt.section({
615
+ name: 'magic-context-desk',
616
+ text: buildDeskSection(lane.memoryEnabled),
617
+ order: 900,
618
+ }));
619
+ scope.effect(() => systemPrompt.context({
620
+ name: 'magic-context-reduce',
621
+ order: 910,
622
+ text: (assembly) => {
623
+ try {
624
+ const agent = assembly.agent;
625
+ const session = agent?.session;
626
+ if (session === undefined)
627
+ return '';
628
+ const sessionId = session.id;
629
+ const tagged = enumerateSurfaceToolResults(session, callId => stores.getTag(sessionId, { kind: 'tool-result', callId }))
630
+ .filter(node => node.tagNumber !== undefined);
631
+ if (tagged.length === 0)
632
+ return '';
633
+ const tailToolTokens = tagged.reduce((total, node) => total + Math.ceil(node.textLength / 4), 0);
634
+ const undropped = tagged.filter(node => !node.isPlaceholder);
635
+ const undroppedTokens = undropped.reduce((total, node) => total + Math.ceil(node.textLength / 4), 0);
636
+ const state = reduceTracker.state(sessionId);
637
+ const nudge = decideReduceNudge({
638
+ tailToolTokens,
639
+ undroppedTokens,
640
+ realUserTurnCount: state.realUserTurnCount,
641
+ ...state.lastBand === undefined ? {} : { lastBand: state.lastBand },
642
+ lastOrdinal: state.lastOrdinal,
643
+ ...state.lastOrdinalTurn === undefined ? {} : { lastOrdinalTurn: state.lastOrdinalTurn },
644
+ });
645
+ // The turn MUST be the session's real user-turn count, not a literal:
646
+ // record persists it as lastOrdinalTurn, and decideReduceNudge's
647
+ // sticky gap is realUserTurnCount - lastOrdinalTurn. A literal 0 made
648
+ // the gap read as the WHOLE turn count, so any session past 5 user
649
+ // turns re-fired its channel2 reminder on every system-prompt
650
+ // assembly (measured live 2026-10-25: 33 reminders in 11 minutes,
651
+ // each double-journaled by the host's assemble→preStep-append→
652
+ // reassemble cycle).
653
+ reduceTracker.record(sessionId, nudge, state.realUserTurnCount);
654
+ if (!nudge.fire || nudge.level === undefined)
655
+ return '';
656
+ const hints = undropped.slice(0, 4).map(node => ({ tagNumber: node.tagNumber }));
657
+ const reminder = nudge.level === 'channel2'
658
+ ? buildChannel2Reminder(undroppedTokens, undropped.length, hints)
659
+ : buildChannel1Reminder(nudge.level, undroppedTokens, undropped.length, hints, nudge.sticky);
660
+ journal?.info('pass', `reduce nudge (${nudge.level})`, {
661
+ decision: 'reduce-nudge',
662
+ band: nudge.band,
663
+ tailToolTokens,
664
+ undroppedTokens,
665
+ }, sessionId);
666
+ return reminder;
667
+ }
668
+ catch {
669
+ // A closed store mid-remount must degrade to no reminder.
670
+ return '';
671
+ }
672
+ },
673
+ }));
674
+ });
675
+ }