@agfpd/iapeer-memory 0.2.6 → 0.2.8

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.
@@ -14,8 +14,8 @@
14
14
  *
15
15
  * Step order: deps → vault → config → binary → templates → role peers +
16
16
  * doctrines + roles manifest → fleet map → watcher registration → direct
17
- * session surfaces sweep (ADR-009 v1.2) → legacy v1.1 plugin off (while the
18
- * old declaration is still readable) → slot declaration (v1.2 provision
17
+ * session surfaces sweep (ADR-009 v1.2) → legacy v1.1 manual hint (the
18
+ * plugin channel is removed, ADR-017) → slot declaration (v1.2 provision
19
19
  * command) → native-memory sweep (core verb, soft-skip on old cores) →
20
20
  * host-wide guide fragment. Ecosystem steps are skippable (--skip-ecosystem)
21
21
  * for sandboxed runs; the binary compile is skippable (--skip-binary) for
@@ -36,7 +36,7 @@ import { IAPEER_BIN, type Egress } from "../egress.js";
36
36
  import { memoryPaths } from "../paths.js";
37
37
  import { provisionVault, writeDefaultConfig } from "../provision.js";
38
38
  import { writeRolesManifest, type RoleEntry } from "../roles.js";
39
- import { applyMemoryPlugin, readSlot, writeSlot, SLOT_PROVIDER } from "../slot.js";
39
+ import { readSlot, writeSlot, SLOT_PROVIDER } from "../slot.js";
40
40
  import { readFleetMap, writeFleetMap } from "../fleet.js";
41
41
  import { withProvisionLock } from "../surfaces/lock.js";
42
42
  import { sweepProvision } from "../surfaces/sweep.js";
@@ -51,11 +51,13 @@ import {
51
51
  } from "../templates/index.js";
52
52
  import { packageVersion } from "../version.js";
53
53
  import {
54
+ DREAM_TARGET,
54
55
  dreamTimerMessage,
55
56
  patchWakePolicyEphemeral,
56
57
  registerTimer,
57
58
  registerWatcher,
58
59
  sweepTimerMessage,
60
+ writeDreamGateScript,
59
61
  writeLauncherScript,
60
62
  writeStaleCheckScript,
61
63
  } from "../watcher.js";
@@ -402,8 +404,15 @@ export async function cmdInit(argv: string[], egress: Egress): Promise<number> {
402
404
  message: sweepTimerMessage({ checkScriptPath: paths.checkScriptPath }),
403
405
  iapeerBin: flags.iapeerBin,
404
406
  });
407
+ writeDreamGateScript({
408
+ dreamGateScriptPath: paths.dreamGateScriptPath,
409
+ binaryPath: paths.binaryPath,
410
+ });
405
411
  const dream = registerTimer(egress, {
406
- message: dreamTimerMessage(),
412
+ message: dreamTimerMessage({
413
+ cron: process.env.IAPEER_MEMORY_DREAM_CRON,
414
+ dreamGateScriptPath: paths.dreamGateScriptPath,
415
+ }),
407
416
  iapeerBin: flags.iapeerBin,
408
417
  });
409
418
  const timersSandboxed = sweep.suppressed && dream.suppressed;
@@ -412,7 +421,7 @@ export async function cmdInit(argv: string[], egress: Egress): Promise<number> {
412
421
  timersSandboxed
413
422
  ? "skipped (test sandbox — sends suppressed)"
414
423
  : sweep.ok && dream.ok
415
- ? `sweep (@every 1h, check ${paths.checkScriptPath}) + dream-tick (weekly)target index`
424
+ ? `sweep (@every 1h, check ${paths.checkScriptPath}) + dream-tick (weekly, gated, ${DREAM_TARGET})`
416
425
  : `sweep: ${sweep.ok ? "sent" : sweep.detail}; dream: ${dream.ok ? "sent" : dream.detail}`,
417
426
  Boolean(timersSandboxed) || (sweep.ok && dream.ok),
418
427
  );
@@ -470,27 +479,25 @@ export async function cmdInit(argv: string[], egress: Egress): Promise<number> {
470
479
  }
471
480
  }
472
481
 
473
- // 8c. v1.1 → v1.2 migration: sweep the legacy session plugin off the
474
- // fleet while the old declaration is STILL readable and ONLY after
475
- // the direct surfaces landed cleanly.
482
+ // 8c. v1.1 → v1.2 migration: the plugin channel is REMOVED (ADR-017) —
483
+ // the package no longer shells the core verb; a v1.1 host gets the
484
+ // manual recipe and the slot migrates ONLY after the direct surfaces
485
+ // landed cleanly (never strand a host with neither channel).
476
486
  let migrationBlocked = false;
477
487
  if (!flags.skipEcosystem && existingSlot?.plugin) {
478
488
  if (!surfacesOk) {
479
489
  migrationBlocked = true;
480
490
  step(
481
491
  "plugin-off",
482
- "POSTPONED: direct surfaces did not land cleanly — legacy plugin and v1.1 slot kept (re-run init after fixing)",
492
+ "POSTPONED: direct surfaces did not land cleanly — v1.1 slot kept (re-run init after fixing)",
483
493
  false,
484
494
  );
485
495
  } else {
486
- const off = applyMemoryPlugin(egress, { mode: "off", iapeerBin: flags.iapeerBin });
487
496
  step(
488
497
  "plugin-off",
489
- off.suppressed
490
- ? "skipped (test sandbox core calls suppressed)"
491
- : off.ok
492
- ? "legacy v1.1 session plugin swept off the fleet (memory-plugin off --all)"
493
- : `legacy plugin off failed (${off.detail.slice(0, 120)}) — manual: iapeer memory-plugin off --all (or per peer: claude plugin uninstall iapeer-memory@agfpd --scope project)`,
498
+ "legacy v1.1 session plugin is NOT auto-removed (channel removed, ADR-017) — manual, per claude peer: " +
499
+ "`claude plugin uninstall iapeer-memory@agfpd --scope project` from its cwd; codex (host-global): " +
500
+ "`codex plugin remove iapeer-memory@agfpd`. Until then it stamps in parallel (idempotent).",
494
501
  );
495
502
  }
496
503
  }
@@ -21,7 +21,7 @@ import type { Egress } from "../egress.js";
21
21
  import { memoryPaths } from "../paths.js";
22
22
  import { removeBinary } from "../binary.js";
23
23
  import { readFleetMap } from "../fleet.js";
24
- import { applyMemoryPlugin, readSlot, removeSlot, SLOT_PROVIDER } from "../slot.js";
24
+ import { readSlot, removeSlot, SLOT_PROVIDER } from "../slot.js";
25
25
  import { withProvisionLock } from "../surfaces/lock.js";
26
26
  import { sweepUnprovision } from "../surfaces/sweep.js";
27
27
  import { guardedUnlinkSync } from "@agfpd/iapeer-memory-core";
@@ -130,19 +130,14 @@ export function cmdUninstall(argv: string[], egress: Egress): number {
130
130
  }
131
131
  }
132
132
 
133
- // Legacy v1.1 path: the slot still carries a plugin block sweep the
134
- // session plugin off via the core verb WHILE the declaration is alive
135
- // (it derives the identity from it; agreed order, auto-removal).
133
+ // Legacy v1.1 path: the slot still carries a plugin block. The plugin
134
+ // channel is REMOVED (ADR-017) no core verb is shelled; the manual
135
+ // recipe works without the slot.
136
136
  if (declared.plugin) {
137
- const off = applyMemoryPlugin(egress, { mode: "off", iapeerBin });
138
137
  console.log(
139
- `plugin : ${
140
- off.suppressed
141
- ? "skipped (test sandbox core calls suppressed)"
142
- : off.ok
143
- ? "legacy session plugin removed across the fleet (memory-plugin off --all; codex side is host-global)"
144
- : `off not applied (${off.detail.slice(0, 160)}) — manual fallback (works without the slot): per claude peer \`claude plugin uninstall iapeer-memory@agfpd --scope project\` from its cwd; codex (host-global): \`codex plugin remove iapeer-memory@agfpd\``
145
- }`,
138
+ "plugin : legacy v1.1 session plugin is NOT auto-removed (channel removed, ADR-017) — manual: " +
139
+ "per claude peer `claude plugin uninstall iapeer-memory@agfpd --scope project` from its cwd; " +
140
+ "codex (host-global): `codex plugin remove iapeer-memory@agfpd`",
146
141
  );
147
142
  }
148
143
  }
@@ -16,10 +16,10 @@
16
16
  * 5. surfaces — direct per-peer session surfaces sweep over the map
17
17
  * (ADR-009 v1.2: the «всё на местах у подключённых пиров»
18
18
  * duty — both runtimes, idempotent, repairs drift);
19
- * 6. plugin-off — v1.1→v1.2 migration: when the on-disk slot still
20
- * carries a plugin block, sweep the legacy plugin off the
21
- * fleet (while the old declaration is STILL readable by
22
- * the core verb) — only after surfaces landed cleanly;
19
+ * 6. plugin-off — v1.1→v1.2 migration: a slot still carrying a plugin
20
+ * block gets the MANUAL removal recipe (the plugin
21
+ * channel is removed, ADR-017) the slot migrates only
22
+ * after surfaces landed cleanly;
23
23
  * 7. slot — re-declare in the v1.2 form (provision command blocks,
24
24
  * new version — contract obligation);
25
25
  * 8. launcher + triggers + guide — regenerate;
@@ -44,17 +44,19 @@ import type { Egress } from "../egress.js";
44
44
  import { readFleetMap, writeFleetMap } from "../fleet.js";
45
45
  import { memoryPaths } from "../paths.js";
46
46
  import { readRolesManifest } from "../roles.js";
47
- import { applyMemoryPlugin, readSlot, writeSlot, SLOT_PROVIDER } from "../slot.js";
47
+ import { readSlot, writeSlot, SLOT_PROVIDER } from "../slot.js";
48
48
  import { withProvisionLock } from "../surfaces/lock.js";
49
49
  import { sweepProvision } from "../surfaces/sweep.js";
50
50
  import { mcpPort } from "./provision-peer.js";
51
51
  import { guideText, materialiseTemplates } from "../templates/index.js";
52
52
  import { packageVersion } from "../version.js";
53
53
  import {
54
+ DREAM_TARGET,
54
55
  dreamTimerMessage,
55
56
  registerTimer,
56
57
  registerWatcher,
57
58
  sweepTimerMessage,
59
+ writeDreamGateScript,
58
60
  writeLauncherScript,
59
61
  writeStaleCheckScript,
60
62
  } from "../watcher.js";
@@ -187,28 +189,25 @@ export function cmdUpdate(argv: string[], egress: Egress): number {
187
189
  }
188
190
  }
189
191
 
190
- // 6. v1.1 → v1.2 migration (one-shot per host): the on-disk slot still
191
- // carries a plugin block sweep the legacy plugin off the fleet WHILE the
192
- // old declaration is still readable (the core verb derives the identity
193
- // from it), and ONLY after the direct surfaces landed cleanly.
192
+ // 6. v1.1 → v1.2 migration (one-shot per host): the plugin channel is
193
+ // REMOVED (ADR-017) no core verb is shelled; a v1.1 host gets the
194
+ // manual recipe and the slot migrates ONLY after the direct surfaces
195
+ // landed cleanly (never strand a host with neither channel).
194
196
  let migrationBlocked = false;
195
197
  if (!slotForeign && existingSlot?.plugin) {
196
198
  if (!surfacesOk) {
197
199
  migrationBlocked = true;
198
200
  step(
199
201
  "plugin-off",
200
- "POSTPONED: direct surfaces did not land cleanly — legacy plugin and v1.1 slot kept (fix and re-run update)",
202
+ "POSTPONED: direct surfaces did not land cleanly — v1.1 slot kept (fix and re-run update)",
201
203
  false,
202
204
  );
203
205
  } else {
204
- const off = applyMemoryPlugin(egress, { mode: "off" });
205
206
  step(
206
207
  "plugin-off",
207
- off.suppressed
208
- ? "skipped (test sandbox core calls suppressed)"
209
- : off.ok
210
- ? "legacy v1.1 session plugin swept off the fleet (memory-plugin off --all)"
211
- : `legacy plugin off failed (${off.detail.slice(0, 120)}) — manual: iapeer memory-plugin off --all`,
208
+ "legacy v1.1 session plugin is NOT auto-removed (channel removed, ADR-017) — manual, per claude peer: " +
209
+ "`claude plugin uninstall iapeer-memory@agfpd --scope project` from its cwd; codex (host-global): " +
210
+ "`codex plugin remove iapeer-memory@agfpd`. Until then it stamps in parallel (idempotent).",
212
211
  );
213
212
  }
214
213
  }
@@ -248,18 +247,27 @@ export function cmdUpdate(argv: string[], egress: Egress): number {
248
247
  } catch {
249
248
  // unprovisioned env — registrations below still re-target
250
249
  }
250
+ writeDreamGateScript({
251
+ dreamGateScriptPath: paths.dreamGateScriptPath,
252
+ binaryPath: paths.binaryPath,
253
+ });
251
254
  const w = registerWatcher(egress, { launcherPath: paths.launcherPath });
252
255
  const s = registerTimer(egress, {
253
256
  message: sweepTimerMessage({ checkScriptPath: paths.checkScriptPath }),
254
257
  });
255
- const d = registerTimer(egress, { message: dreamTimerMessage() });
258
+ const d = registerTimer(egress, {
259
+ message: dreamTimerMessage({
260
+ cron: process.env.IAPEER_MEMORY_DREAM_CRON,
261
+ dreamGateScriptPath: paths.dreamGateScriptPath,
262
+ }),
263
+ });
256
264
  const sandboxed = w.suppressed && s.suppressed && d.suppressed;
257
265
  step(
258
266
  "triggers",
259
267
  sandboxed
260
268
  ? "skipped (test sandbox — sends suppressed)"
261
269
  : w.ok && s.ok && d.ok
262
- ? `re-sent: event→scriber, sweep+dream→index (same id = replace); confirm: verify`
270
+ ? `re-sent: event→scriber, sweep→index, dream→${DREAM_TARGET} (gated; same id = replace); confirm: verify`
263
271
  : `event: ${w.ok ? "sent" : w.detail}; sweep: ${s.ok ? "sent" : s.detail}; dream: ${d.ok ? "sent" : d.detail}`,
264
272
  Boolean(sandboxed) || (w.ok && s.ok && d.ok),
265
273
  );
@@ -35,6 +35,7 @@ import { checkFleetSurfaces, sweepProvision } from "../surfaces/sweep.js";
35
35
  import { mcpPort } from "./provision-peer.js";
36
36
  import { packageVersion } from "../version.js";
37
37
  import {
38
+ DREAM_TARGET,
38
39
  dreamTimerMessage,
39
40
  DEFAULT_EVENT_TARGET,
40
41
  DREAM_TRIGGER_ID,
@@ -43,6 +44,7 @@ import {
43
44
  registerWatcher,
44
45
  sweepTimerMessage,
45
46
  SWEEP_TRIGGER_ID,
47
+ writeDreamGateScript,
46
48
  writeLauncherScript,
47
49
  writeStaleCheckScript,
48
50
  WATCHER_TRIGGER_ID,
@@ -385,9 +387,24 @@ export function runVerify(egress: Egress, opts: VerifyOptions = {}): CheckResult
385
387
  id: DREAM_TRIGGER_ID,
386
388
  role: "time",
387
389
  expect: (t) =>
388
- t.target !== "index" ? `target is ${t.target ?? "?"}, expected index` : null,
389
- repairSend: () =>
390
- registerTimer(egress, { message: dreamTimerMessage(), iapeerBin: opts.iapeerBin }),
390
+ t.target !== DREAM_TARGET
391
+ ? `target is ${t.target ?? "?"}, expected ${DREAM_TARGET}`
392
+ : (t as { check?: string }).check !== paths.dreamGateScriptPath
393
+ ? `check is ${(t as { check?: string }).check ?? "?"}, expected ${paths.dreamGateScriptPath}`
394
+ : null,
395
+ repairSend: () => {
396
+ writeDreamGateScript({
397
+ dreamGateScriptPath: paths.dreamGateScriptPath,
398
+ binaryPath: paths.binaryPath,
399
+ });
400
+ return registerTimer(egress, {
401
+ message: dreamTimerMessage({
402
+ cron: process.env.IAPEER_MEMORY_DREAM_CRON,
403
+ dreamGateScriptPath: paths.dreamGateScriptPath,
404
+ }),
405
+ iapeerBin: opts.iapeerBin,
406
+ });
407
+ },
391
408
  },
392
409
  ];
393
410
  for (const c of checks) {
package/src/fleet.ts CHANGED
@@ -80,7 +80,7 @@ export function readFleetMap(fleetMapPath: string): FleetPeer[] | null {
80
80
  }
81
81
 
82
82
  /** Live-registry query — the ONE place `iapeer list --json` is parsed.
83
- * Shared by writeFleetMap (the persisted map) and dream-paths (the
83
+ * Shared by writeFleetMap (the persisted map) and dream-collect (the
84
84
  * tick-time resolution; freshness fact: birth does NOT touch fleet.json
85
85
  * and the SessionStart kick is heartbeat-gated, so the LIVE registry is
86
86
  * the only source that sees a newborn before the next update). */
package/src/paths.ts CHANGED
@@ -56,6 +56,9 @@ export type MemoryPaths = {
56
56
  launcherPath: string;
57
57
  /** Sweep check-script — gates the fail-open inbox sweep (ADR-015). */
58
58
  checkScriptPath: string;
59
+ /** Dream-tick gate check-script — the notifier `check` for the weekly timer
60
+ * (shells the registry-free `dream-collect --gate`; a dead week wakes no one). */
61
+ dreamGateScriptPath: string;
59
62
  /** Fleet map (personality → cwd) — written by init/update/verify --repair
60
63
  * from `iapeer list --json`, consumed by memoryd's fragment renderer
61
64
  * (docs/05; дыра 10.06: без карты пиры не получали paths-блок и индекс). */
@@ -99,6 +102,7 @@ export function memoryPaths(
99
102
  hooksDir: path.join(path.dirname(configFile), "hooks"),
100
103
  launcherPath: path.join(path.dirname(configFile), "memoryd-launcher.sh"),
101
104
  checkScriptPath: path.join(path.dirname(configFile), "inbox-stale-check.sh"),
105
+ dreamGateScriptPath: path.join(path.dirname(configFile), "dream-tick-gate.sh"),
102
106
  fleetMapPath: path.join(stateDir, "fleet.json"),
103
107
  };
104
108
  }
package/src/provision.ts CHANGED
@@ -176,6 +176,20 @@ export function defaultConfigContent(opts: ConfigContentOptions): string {
176
176
  "# Curator personalities exempt from needs_review stamping (ADR-006).",
177
177
  "# IAPEER_MEMORY_CURATOR_SET=index,scriber,dreamweaver",
178
178
  "",
179
+ "# Weekly dream-tick (deterministic pre-filter → DreamWeaver). Schedule is",
180
+ "# 5-field cron; the window is days BY TIME (not since-last-tick).",
181
+ "# IAPEER_MEMORY_DREAM_CRON=0 4 * * 1",
182
+ "# IAPEER_MEMORY_DREAM_WINDOW_DAYS=7",
183
+ "# description longer than this many chars → a reformulation candidate.",
184
+ "# IAPEER_MEMORY_DREAM_DESC_MAXLEN=250",
185
+ "# >threshold new notes in a folder → its own subagent; smaller folders are",
186
+ "# grouped up to the cap (sum of per-folder weights).",
187
+ "# IAPEER_MEMORY_DREAM_BATCH_THRESHOLD=20",
188
+ "# IAPEER_MEMORY_DREAM_GROUP_CAP=20",
189
+ "# Per-folder cap on transcripts handed to phase D (most recent N by mtime;",
190
+ "# bounds an ephemeral worker's hundreds of sessions). 0 = uncapped.",
191
+ "# IAPEER_MEMORY_DREAM_TRANSCRIPT_CAP=20",
192
+ "",
179
193
  ].join("\n");
180
194
  }
181
195
 
package/src/slot.ts CHANGED
@@ -31,7 +31,6 @@
31
31
 
32
32
  import fs from "node:fs";
33
33
  import path from "node:path";
34
- import { IAPEER_BIN, type Egress } from "./egress.js";
35
34
  import {
36
35
  guardedWriteFileSync,
37
36
  guardedUnlinkSync,
@@ -173,54 +172,7 @@ export function removeSlot(slotPath: string): SlotRemoveResult {
173
172
  return "removed";
174
173
  }
175
174
 
176
- export type MemoryPluginApplyResult = {
177
- ok: boolean;
178
- detail: string;
179
- /** True when the test-sandbox fuse blocked the call callers report a
180
- * SKIP, not a failure (same contract as watcher.ts iapSend). */
181
- suppressed?: boolean;
182
- };
183
-
184
- /**
185
- * Fleet-wide install/remove of the slot-declared session plugin via the core
186
- * verb `iapeer memory-plugin <on|off> --all` (iapeer ≥0.2.25; marketplace
187
- * ensure + stale-cache retry live INSIDE the verb). The verb derives the
188
- * plugin identity from the slot declaration — so `on` runs AFTER writeSlot
189
- * and `off` runs BEFORE removeSlot (agreed order, auto-removal: a dead
190
- * provider's plugin must not keep injecting).
191
- *
192
- * The hard fuse of incident 10.06 (`on --all` mutates the HOST fleet — no
193
- * sandbox env contains it) lives in the egress constructor now
194
- * (deny-by-default §4): a refusing handle blocks the spawn here.
195
- */
196
- export function applyMemoryPlugin(
197
- egress: Egress,
198
- opts: {
199
- mode: "on" | "off";
200
- iapeerBin?: string;
201
- },
202
- ): MemoryPluginApplyResult {
203
- const bin = opts.iapeerBin ?? IAPEER_BIN;
204
- const proc = egress.spawnSync([bin, "memory-plugin", opts.mode, "--all"], {
205
- explicitBin: opts.iapeerBin !== undefined,
206
- });
207
- if (proc.refused) {
208
- return {
209
- ok: false,
210
- suppressed: true,
211
- detail: "memory-plugin call suppressed (test sandbox)",
212
- };
213
- }
214
- if (proc.spawnError) {
215
- return { ok: false, detail: `${bin} unavailable: ${proc.spawnError}` };
216
- }
217
- if (proc.exitCode !== 0) {
218
- return {
219
- ok: false,
220
- detail:
221
- (proc.stderr.trim() || proc.stdout.trim() || "").slice(0, 200) ||
222
- `iapeer memory-plugin exited ${proc.exitCode}`,
223
- };
224
- }
225
- return { ok: true, detail: proc.stdout.trim() };
226
- }
175
+ // applyMemoryPlugin (the core verb `iapeer memory-plugin <on|off> --all`)
176
+ // was REMOVED with the plugin channel (ADR-017): v1.1 hosts get a manual
177
+ // recipe in init/update/uninstall instead of an auto-sweep. The `plugin`
178
+ // field on the slot type stays it is the v1.1 READ marker.
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Manifest version sync (docs/10 §Версионная синхронизация): propagate the
3
3
  * facade `package/package.json` version into every other manifest of the
4
- * monorepo — `core/package.json` + `adapters/{claude,codex}` plugin
5
- * manifests. Wired into the npm `version` lifecycle, runnable standalone:
4
+ * monorepo — `core/package.json` (the adapter plugin manifests left with
5
+ * the plugin channel, ADR-017). Wired into the npm `version` lifecycle,
6
+ * runnable standalone:
6
7
  *
7
8
  * bun src/sync-versions.ts
8
9
  *
9
- * Missing manifests are reported and skipped (the adapters land in P2/P5 —
10
- * the script must not fail before they exist). The reference
10
+ * Missing manifests are reported and skipped. The reference
11
11
  * sync-plugin-version pattern, generalised to N manifests.
12
12
  */
13
13
 
@@ -18,11 +18,7 @@ import { guardedWriteFileSync } from "@agfpd/iapeer-memory-core";
18
18
  export type SyncOutcome = { file: string; action: "updated" | "identical" | "missing" };
19
19
 
20
20
  /** Relative (to the monorepo root) manifests that must carry one version. */
21
- export const SYNC_TARGETS = [
22
- "core/package.json",
23
- "adapters/claude/.claude-plugin/plugin.json",
24
- "adapters/codex/.codex-plugin/plugin.json",
25
- ] as const;
21
+ export const SYNC_TARGETS = ["core/package.json"] as const;
26
22
 
27
23
  export function syncVersions(opts: {
28
24
  rootDir: string;
@@ -51,17 +51,12 @@ different world.
51
51
  scriber thread stalled: place the stale drafts UNVETTED by the usual
52
52
  rules; \`needs_review: true\` already travels with each file. The
53
53
  Scriber re-vets them with the next PERMANENT_BATCH once alive.
54
- - **DREAM_TICK** (notifier timer, weekly) — run \`iapeer-memory
55
- dream-paths\` (read-only; the LIVE registry at tick time) and fan out
56
- DreamWeaver over the folders of its output (including your own),
57
- strictly one folder per task, sequentially. DreamWeaver takes tasks ONLY
58
- from you (the one exception: a folder's owner may task it on their own
59
- folder). Task: \`{agent, path, mode, transcripts_window_days,
60
- transcripts}\` — copy \`transcripts\` from the verb's output AS IS
61
- (globs + the codex cwdFilter; path forms are the code's zone, not
62
- yours). A verb error = report to the owner, never guess the fleet. On
63
- the consolidation report: archive what it deprecated, act on its
64
- \`attention\` blocks yourself.
54
+ - **DreamWeaver consolidation report** (weekly) — DreamWeaver now
55
+ orchestrates the tick (a deterministic pre-filter finds the work, it
56
+ fans out subagents); you are OFF the entry and only FINALISE. On its
57
+ report: archive each note it deprecated (move to the archive subfolder),
58
+ build the links section for each new merged note via vault_search, and
59
+ act on its \`attention\` items yourself.
65
60
  - **Direct IAP** from agents or the human — structure questions; never
66
61
  run searches for others (they have their own vault tools).
67
62
 
@@ -114,8 +109,8 @@ it under the usual three conditions.
114
109
  Never write note content (authors own it); never answer other agents'
115
110
  search requests; never dispatch the Scriber (events reach it directly —
116
111
  it reports to you); never detect events yourself (memoryd detects, the
117
- notifier delivers); never let DreamWeaver take tasks from anyone but you
118
- (save the owner-on-own-folder exception).
112
+ notifier delivers); never orchestrate the dream-tick (DreamWeaver owns it
113
+ end to end now — you only finalise on its report).
119
114
  `;
120
115
 
121
116
  export const SCRIBER_DOCTRINE_EN = `---
@@ -233,47 +228,78 @@ locale: en
233
228
  ---
234
229
  # DreamWeaver — sleep-cycle memory consolidation
235
230
 
236
- You are DreamWeaver: an ephemeral worker peer, the weekly hygiene
237
- instrument for agent memory. Vault root on this host: \`{{VAULT_PATH}}\`
238
- (never guess it). Tasks arrive as IAP messages: weekly fan-out
239
- from the Index (one task per subfolder, including the Index's own), or
240
- on-demand from a folder's OWNER for their own folder only. One task = one
241
- clean window = ONE outbound message (the final consolidation report to the
242
- task sender). Discipline: touch ONLY the folder named in the task.
243
-
244
- Task: \`{agent, path, mode, transcripts_window_days, transcripts}\`.
245
-
246
- ## The four phases
247
-
248
- - **A Dedup.** Read the folder's notes; group the ones about one topic
249
- (LLM judgement). For each group of 2+: write ONE merging note (meaningful
250
- filename in the vault language, \`subtype\` + \`description\` + body with
251
- inline \`[[old note A]]\`, \`[[old note B]]\` mentions) and flip each old
252
- note's \`status\` to the outdated token.
253
- - **B Compress descriptions.** \`description\` longer than ~250 chars
254
- tighten to 1–2 sentences (~150 chars). Never touch the body in this
255
- phase.
256
- - **C Local fact verification.** Find file paths and env-variable
257
- mentions in bodies; read the targets; on a clear mismatch (file gone,
258
- function renamed) write an updated note and flip the old one to the
259
- outdated token. LOCAL checks only.
260
- - **D Transcript scan.** Read the session transcripts for the window
261
- (\`transcripts_window_days\`) per the task's \`transcripts\`: each entry
262
- is a glob; for \`runtime: codex\` the store is HOST-WIDE take ONLY the
263
- sessions whose \`session_meta.cwd\` equals the entry's \`cwdFilter\`
264
- (foreign cwds are foreign memory). No entries / empty glob skip the
265
- phase. Find user phrases that formulate a rule with 2+ explicit
266
- confirmations in different sessions; check against existing feedback
267
- notes; write new notes with quotes for what's missing.
268
-
269
- ## Hard limits
270
-
271
- - No hard deletes only the outdated status token; archiving and links
272
- are the Index's pass (it acts on your report), not yours.
273
- - Never touch canon folders; no vault MCP tools; no web fact-checking
274
- (that's the distill skill's domain, not yours).
275
- - Your edits are stamped \`last_edited_by: dreamweaver\`; the \`author\`
276
- constant is parsed from the subfolder path, so writing into a foreign
277
- subfolder ON TASK keeps the owner's attribution intact that is by
278
- design.
231
+ You are DreamWeaver: an ephemeral worker peer that ORCHESTRATES the weekly
232
+ agent-memory hygiene tick. Vault root on this host: \`{{VAULT_PATH}}\`
233
+ (never guess it a stale copy elsewhere on disk is a different world). A
234
+ deterministic pre-filter has already found the work; you turn its output
235
+ into subagent tasks, then report once. The notifier delivers DREAM_TICK to
236
+ you, weekly, only on a week that has work (a gate skips dead weeks)
237
+ nobody else tasks you.
238
+
239
+ ## Running the tick
240
+
241
+ 1. Run \`iapeer-memory dream-collect\` (Bash, read-only). It returns JSON:
242
+ \`{vault, windowDays, tasks[], skipped[]}\`. Each task is
243
+ \`{kind, folders[]}\`; each folder carries \`{agent, path,
244
+ newNotes:[{path, flags}], transcripts:[{runtime, files}]}\`. A
245
+ \`folder\` task is one busy folder for one subagent; a \`grouped\` task
246
+ is several small folders for one subagent. The pre-filter already
247
+ dropped inactive folders, so you spawn ONLY where there is real work.
248
+ 2. A verb error line = report it to the human owner and stop; never guess
249
+ the fleet. Empty \`tasks\` = a clean week finish with \`iapeer
250
+ self-done\` (the non-waking finish), no report.
251
+ 3. Fan out one subagent per task, using your runtime's own subagent
252
+ mechanism (whatever it is called there). Run tasks concurrently when
253
+ your runtime allows, otherwise in sequence.
254
+ 4. Collect the subagents' results into ONE consolidation report to the
255
+ Index: the notes they deprecated (for archival), the new merged notes
256
+ (for linking), and any attention items. The Index finalises — archival
257
+ and the links section are its pass, not yours.
258
+
259
+ Your window is one clean cycle = ONE outbound message (the report to the
260
+ Index, or \`self-done\` on an empty week).
261
+
262
+ ## The subagent's task
263
+
264
+ Each subagent sees ONLY the task you write — make it self-sufficient. Copy,
265
+ verbatim from the verb's output, the folder path(s), the \`newNotes\` with
266
+ their flags, the \`transcripts\` files, and the vault root. State the
267
+ objective, the four judgement phases, the boundaries, and the report shape
268
+ below. The subagent JUDGES the supplied material; it never re-discovers
269
+ (the script already did the finding, so a subagent that re-scans the folder
270
+ wastes the whole point).
271
+
272
+ - **A Dedup.** Group the supplied \`newNotes\` that cover one topic. Read
273
+ sibling notes in the folder for CONTEXT — including ones outside the
274
+ window — so a fresh note merges with an older twin; edit ONLY in-window
275
+ notes (the rest are already settled). For each group of 2+: write one
276
+ merging note (meaningful filename in the vault language, \`subtype\` +
277
+ \`description\` + body with inline \`[[old note]]\` mentions) and flip
278
+ each merged note's \`status\` to the outdated token.
279
+ - **B — Compress descriptions.** For a note flagged \`long-desc\`: tighten
280
+ \`description\` to 1–2 sentences (~150 chars), leaving the body untouched.
281
+ - **C — Verify flagged references.** For a note flagged \`broken-ref\`: the
282
+ script already located the suspect path/env mention — read the target,
283
+ and on a clear mismatch (file gone, var unset, function renamed) write an
284
+ updated note and flip the old one to outdated. Local checks only.
285
+ - **D — Extract rules from transcripts.** Read the supplied
286
+ \`transcripts.files\` (concrete paths — no globbing). Find user phrases
287
+ that state a rule with 2+ explicit confirmations across sessions; check
288
+ against existing feedback notes; write what is missing, with quotes. No
289
+ files → skip the phase.
290
+
291
+ Boundaries for every subagent: touch only the folder(s) named in the task,
292
+ and only in-window notes; stay out of canon folders; no hard deletes (only
293
+ the outdated status token, because archiving and links are the Index's
294
+ pass); no vault MCP tools and no web fact-checking (that is the distill
295
+ skill's domain). Edits are stamped \`last_edited_by: dreamweaver\` and the
296
+ \`author\` constant is parsed from the subfolder path, so writing into an
297
+ owner's folder ON TASK keeps their attribution intact — by design.
298
+
299
+ ## What you never do
300
+
301
+ You never discover work yourself (the pre-filter does) and never write a
302
+ note's substance (its author owns that). You act through subagents and
303
+ report to the Index; you do not archive or set links — that is the Index's
304
+ finalising pass.
279
305
  `;