@agfpd/iapeer-memory 0.1.7 → 0.1.9

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer-memory",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "iapeer-memory — peer memory for the iapeer ecosystem: vault, memoryd (index/search/MCP-http), layer-5 context fragments, role doctrines. The package IS the system; the claude/codex plugins are thin session sockets (docs/10-distribution.md, ADR-009).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@agfpd/iapeer-memory-core": "0.1.7"
30
+ "@agfpd/iapeer-memory-core": "0.1.9"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/bun": "^1.2.0",
@@ -33,7 +33,7 @@ import { installBinary } from "../binary.js";
33
33
  import { memoryPaths } from "../paths.js";
34
34
  import { provisionVault, writeDefaultConfig } from "../provision.js";
35
35
  import { writeRolesManifest, type RoleEntry } from "../roles.js";
36
- import { writeSlot } from "../slot.js";
36
+ import { applyMemoryPlugin, writeSlot } from "../slot.js";
37
37
  import {
38
38
  doctrineOwnership,
39
39
  guideText,
@@ -318,7 +318,7 @@ export async function cmdInit(argv: string[]): Promise<number> {
318
318
  continue;
319
319
  }
320
320
  const template = roleTemplatePath(paths.templatesDir, locale, role);
321
- const rendered = renderDoctrine({ templatePath: template, peerCwd, version });
321
+ const rendered = renderDoctrine({ templatePath: template, peerCwd, version, vaultPath: vault });
322
322
  if (rendered.action === "missing-template") {
323
323
  rolesOk = false;
324
324
  console.log(` roles ${role}: template missing at ${template}`);
@@ -388,7 +388,8 @@ export async function cmdInit(argv: string[]): Promise<number> {
388
388
  );
389
389
  }
390
390
 
391
- // 8. slot declaration (the contract: written by the provider, atomic)
391
+ // 8. slot declaration (the contract: written by the provider, atomic)
392
+ // v1.1: includes the plugin block the core derives installs from.
392
393
  const slot = writeSlot({
393
394
  slotPath: paths.slotPath,
394
395
  version,
@@ -402,6 +403,26 @@ export async function cmdInit(argv: string[]): Promise<number> {
402
403
  slot.action !== "refused-foreign",
403
404
  );
404
405
 
406
+ // 8b. session plugin across the fleet — the core verb derives the plugin
407
+ // from the block JUST written (order matters), installs per-peer on claude
408
+ // and host-globally on codex; new peers get it from the core's birth-hook.
409
+ // Soft-skip on an older core (verb landed in iapeer 0.2.25).
410
+ if (flags.skipEcosystem) {
411
+ step("plugin", "skipped (--skip-ecosystem)");
412
+ } else if (slot.action === "refused-foreign") {
413
+ step("plugin", "skipped (slot refused — nothing to derive the plugin from)");
414
+ } else {
415
+ const plug = applyMemoryPlugin({ mode: "on", iapeerBin: flags.iapeerBin });
416
+ step(
417
+ "plugin",
418
+ plug.suppressed
419
+ ? "skipped (test sandbox — core calls suppressed)"
420
+ : plug.ok
421
+ ? "session plugin rolled out across the fleet (iapeer memory-plugin on --all)"
422
+ : `soft-skip: ${plug.detail.slice(0, 160) || "verb failed"} — upgrade the iapeer core (≥0.2.25) or run manually: iapeer memory-plugin on --all`,
423
+ );
424
+ }
425
+
405
426
  // 9. native-memory sweep — the core's lever (one home of runtime forms);
406
427
  // soft-skip when the verb is unavailable (older core), verify re-runs later.
407
428
  if (flags.skipEcosystem) {
@@ -18,7 +18,7 @@
18
18
  import fs from "node:fs";
19
19
  import { memoryPaths } from "../paths.js";
20
20
  import { removeBinary } from "../binary.js";
21
- import { removeSlot } from "../slot.js";
21
+ import { applyMemoryPlugin, readSlot, removeSlot, SLOT_PROVIDER } from "../slot.js";
22
22
  import {
23
23
  DREAM_TRIGGER_ID,
24
24
  SWEEP_TRIGGER_ID,
@@ -95,6 +95,28 @@ export function cmdUninstall(argv: string[]): number {
95
95
  const paths = memoryPaths();
96
96
  let failed = false;
97
97
 
98
+ // Session plugin OFF across the fleet BEFORE removing the declaration —
99
+ // the core verb DERIVES the plugin identity from the slot's v1.1 block;
100
+ // once the declaration is gone there is nothing to derive from (agreed
101
+ // order with the core, auto-removal: a dead provider's plugin must not
102
+ // keep injecting). Guard: only when the slot is OURS — running `off`
103
+ // against a foreign declaration would strip the FOREIGN provider's plugin.
104
+ // codex nuance: the codex plugin is host-global, so `off` there is always
105
+ // `--all` semantics; per-peer off on codex answers «use --all».
106
+ const declared = readSlot(paths.slotPath);
107
+ if (declared && declared.provider === SLOT_PROVIDER) {
108
+ const off = applyMemoryPlugin({ mode: "off", iapeerBin });
109
+ console.log(
110
+ `plugin : ${
111
+ off.suppressed
112
+ ? "skipped (test sandbox — core calls suppressed)"
113
+ : off.ok
114
+ ? "session plugin removed across the fleet (memory-plugin off --all; codex side is host-global)"
115
+ : `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\``
116
+ }`,
117
+ );
118
+ }
119
+
98
120
  const slot = removeSlot(paths.slotPath);
99
121
  if (slot === "refused-foreign") {
100
122
  console.log("slot : held by a FOREIGN provider — left intact");
@@ -93,14 +93,21 @@ export function cmdUpdate(argv: string[]): number {
93
93
  const tmpl = materialiseTemplates({ templatesDir: paths.templatesDir, locale });
94
94
  step("templates", `${tmpl.written.length} written, ${tmpl.identical.length} identical`);
95
95
 
96
- // 3. role doctrines (version marker follows the package — ADR-010)
96
+ // 3. role doctrines (version marker follows the package — ADR-010).
97
+ // vaultPath feeds the {{VAULT_PATH}} doctrine substitution (host fact).
98
+ let vaultPathForDoctrines: string | undefined;
99
+ try {
100
+ vaultPathForDoctrines = configFromEnv().vaultPath;
101
+ } catch {
102
+ vaultPathForDoctrines = undefined; // unprovisioned env — placeholder fallback
103
+ }
97
104
  const manifest = readRolesManifest(paths.rolesManifestPath);
98
105
  if (!manifest || manifest.roles.length === 0) {
99
106
  step("doctrines", "no roles manifest — init has not run (nothing to re-render)");
100
107
  } else {
101
108
  const outcomes = manifest.roles.map((r) => ({
102
109
  role: r.role,
103
- ...renderDoctrine({ templatePath: r.template, peerCwd: r.peerCwd, version }),
110
+ ...renderDoctrine({ templatePath: r.template, peerCwd: r.peerCwd, version, vaultPath: vaultPathForDoctrines }),
104
111
  }));
105
112
  const missing = outcomes.filter((o) => o.action === "missing-template");
106
113
  step(
@@ -74,9 +74,11 @@ export function runVerify(opts: VerifyOptions = {}): CheckResult[] {
74
74
 
75
75
  // 1. config / env context
76
76
  let configOk = false;
77
+ let vaultPathForDoctrines: string | undefined;
77
78
  try {
78
79
  const config = configFromEnv();
79
80
  configOk = true;
81
+ vaultPathForDoctrines = config.vaultPath;
80
82
  results.push({
81
83
  name: "config",
82
84
  status: "ok",
@@ -333,6 +335,7 @@ export function runVerify(opts: VerifyOptions = {}): CheckResult[] {
333
335
  templatePath: entry.template,
334
336
  peerCwd: entry.peerCwd,
335
337
  version,
338
+ vaultPath: vaultPathForDoctrines,
336
339
  });
337
340
  if (outcome.action === "missing-template") {
338
341
  results.push({
package/src/slot.ts CHANGED
@@ -11,7 +11,15 @@
11
11
  * - `version` = the package version (the same single source as the doctrine
12
12
  * marker, ADR-010); our `update` re-writes it (P4 obligation);
13
13
  * - `heartbeat` (optional) = the absolute path whose mtime memoryd touches —
14
- * the core may show staleness in `iapeer status`, never acts on it.
14
+ * the core may show staleness in `iapeer status`, never acts on it;
15
+ * - `plugin` (v1.1, agreed 10.06 + live in iapeer 0.2.25) = the marketplace
16
+ * identity of the session plugin. The core DERIVES installs from this block:
17
+ * birth-hook installs for new peers, `iapeer memory-plugin on|off (--peer|
18
+ * --all)` is the operator verb (built-in marketplace ensure + stale-cache
19
+ * retry). Reference reader: iapeer src/status/index.ts parsePluginBlock —
20
+ * all three fields required non-empty, anything less = treated as a v1
21
+ * declaration (no install). marketplaceRef matches iapeer onboard's
22
+ * MARKETPLACE_REF for the distribution default.
15
23
  */
16
24
 
17
25
  import fs from "node:fs";
@@ -20,12 +28,29 @@ import path from "node:path";
20
28
  export const SLOT_PROVIDER = "iapeer-memory";
21
29
  export const SLOT_PACKAGE = "@agfpd/iapeer-memory";
22
30
 
31
+ /** Mirror of iapeer's MemoryProviderPlugin (src/status/index.ts). */
32
+ export type MemoryProviderPlugin = {
33
+ /** Plugin id in the marketplace (forms `<name>@<marketplace>`). */
34
+ name: string;
35
+ /** Marketplace NAME the plugin id keys on. */
36
+ marketplace: string;
37
+ /** Source ref for `plugin marketplace add` when absent on the host (owner/repo). */
38
+ marketplaceRef: string;
39
+ };
40
+
41
+ export const SLOT_PLUGIN: MemoryProviderPlugin = {
42
+ name: "iapeer-memory",
43
+ marketplace: "agfpd",
44
+ marketplaceRef: "agfpd/agfpd-marketplace",
45
+ };
46
+
23
47
  export type MemoryProviderSlot = {
24
48
  provider: string;
25
49
  package: string;
26
50
  version: string;
27
51
  registeredAt: string;
28
52
  heartbeat?: string;
53
+ plugin?: MemoryProviderPlugin;
29
54
  };
30
55
 
31
56
  /** Never throws: missing / unreadable / malformed → null (empty slot). */
@@ -59,7 +84,11 @@ export function writeSlot(opts: {
59
84
  existing &&
60
85
  existing.version === opts.version &&
61
86
  existing.heartbeat === opts.heartbeat &&
62
- existing.package === SLOT_PACKAGE
87
+ existing.package === SLOT_PACKAGE &&
88
+ existing.plugin &&
89
+ existing.plugin.name === SLOT_PLUGIN.name &&
90
+ existing.plugin.marketplace === SLOT_PLUGIN.marketplace &&
91
+ existing.plugin.marketplaceRef === SLOT_PLUGIN.marketplaceRef
63
92
  ) {
64
93
  return { action: "identical", existing }; // idempotent re-init: no churn
65
94
  }
@@ -69,6 +98,7 @@ export function writeSlot(opts: {
69
98
  version: opts.version,
70
99
  registeredAt: opts.nowIso ?? new Date().toISOString(),
71
100
  ...(opts.heartbeat ? { heartbeat: opts.heartbeat } : {}),
101
+ plugin: SLOT_PLUGIN,
72
102
  };
73
103
  fs.mkdirSync(path.dirname(opts.slotPath), { recursive: true });
74
104
  const tmp = `${opts.slotPath}.tmp`;
@@ -87,3 +117,57 @@ export function removeSlot(slotPath: string): SlotRemoveResult {
87
117
  fs.unlinkSync(slotPath);
88
118
  return "removed";
89
119
  }
120
+
121
+ export type MemoryPluginApplyResult = {
122
+ ok: boolean;
123
+ detail: string;
124
+ /** True when the test-sandbox fuse blocked the call — callers report a
125
+ * SKIP, not a failure (same contract as watcher.ts iapSend). */
126
+ suppressed?: boolean;
127
+ };
128
+
129
+ /**
130
+ * Fleet-wide install/remove of the slot-declared session plugin via the core
131
+ * verb `iapeer memory-plugin <on|off> --all` (iapeer ≥0.2.25; marketplace
132
+ * ensure + stale-cache retry live INSIDE the verb). The verb derives the
133
+ * plugin identity from the slot declaration — so `on` runs AFTER writeSlot
134
+ * and `off` runs BEFORE removeSlot (agreed order, auto-removal: a dead
135
+ * provider's plugin must not keep injecting).
136
+ *
137
+ * Hard fuse first (same class as iapSend, incident 10.06): `on --all`
138
+ * mutates the HOST fleet — no sandbox env contains it, tests must never
139
+ * reach the live core. Both belts honoured.
140
+ */
141
+ export function applyMemoryPlugin(opts: {
142
+ mode: "on" | "off";
143
+ iapeerBin?: string;
144
+ }): MemoryPluginApplyResult {
145
+ if (
146
+ process.env.IAPEER_MEMORY_SUPPRESS_IAP_SEND === "1" ||
147
+ process.env.IAPEER_TEST_SANDBOX === "1"
148
+ ) {
149
+ return {
150
+ ok: false,
151
+ suppressed: true,
152
+ detail: "memory-plugin call suppressed (test sandbox)",
153
+ };
154
+ }
155
+ const bin = opts.iapeerBin ?? "iapeer";
156
+ try {
157
+ const proc = Bun.spawnSync([bin, "memory-plugin", opts.mode, "--all"], {
158
+ stdout: "pipe",
159
+ stderr: "pipe",
160
+ });
161
+ if (proc.exitCode !== 0) {
162
+ return {
163
+ ok: false,
164
+ detail:
165
+ (proc.stderr.toString().trim() || proc.stdout.toString().trim() || "").slice(0, 200) ||
166
+ `iapeer memory-plugin exited ${proc.exitCode}`,
167
+ };
168
+ }
169
+ return { ok: true, detail: proc.stdout.toString().trim() };
170
+ } catch (err) {
171
+ return { ok: false, detail: `${bin} unavailable: ${String(err)}` };
172
+ }
173
+ }
@@ -29,9 +29,14 @@ Volatile context (the tags dictionary, your own author index) arrives via
29
29
  layer-5 fragments and is re-read on every cold wake. This doctrine is your
30
30
  stable contract.
31
31
 
32
+ Vault root on this host: \`{{VAULT_PATH}}\`. Event paths arrive ABSOLUTE;
33
+ NEVER guess the vault location — a stale copy elsewhere on disk is a
34
+ different world.
35
+
32
36
  ## Inputs you act on
33
37
 
34
- - **Scriber report** (IAP, one per processed event):
38
+ - **Scriber report** (IAP, one per processed delivery — instant drafts,
39
+ the 6-hour PERMANENT_BATCH, the nightly HUMAN_INBOX_BATCH):
35
40
  - accepted drafts → PLACE each: pick the permanent folder and \`type\`,
36
41
  fill \`tags\` from the dictionary, build the links section via
37
42
  vault_search, link to an active project phase when it belongs to one.
@@ -45,7 +50,7 @@ stable contract.
45
50
  - **INBOX_SWEEP** (notifier timer; fires only on a real backlog) — the
46
51
  scriber thread stalled: place the stale drafts UNVETTED by the usual
47
52
  rules; \`needs_review: true\` already travels with each file. The
48
- Scriber re-vets via PERMANENT_CHANGED once alive.
53
+ Scriber re-vets them with the next PERMANENT_BATCH once alive.
49
54
  - **DREAM_TICK** (notifier timer, weekly) — fan out DreamWeaver over the
50
55
  agent-memory subfolders (including your own), strictly one folder per
51
56
  task, sequentially. DreamWeaver takes tasks ONLY from you (the one
@@ -112,24 +117,28 @@ locale: en
112
117
  You are the Scriber: an EPHEMERAL worker peer enforcing the vault's
113
118
  writing contract — the FIRST receiver of vault events (ADR-015). The
114
119
  notifier delivers memoryd events straight to you, strictly one event per
115
- fresh session; nobody else may task you. Per event: filter, vet what needs
120
+ fresh session; nobody else may task you. Vault root on this host:
121
+ \`{{VAULT_PATH}}\` — event paths arrive ABSOLUTE; NEVER guess the vault
122
+ location (a stale copy elsewhere on disk is a different world). Per event: filter, vet what needs
116
123
  vetting, then at most ONE report to the Index (plus direct pings to
117
124
  rejected authors). Fact-checking uses your runtime's web tools, edits use
118
125
  the native file tools; after the report, only local writes until the
119
126
  session ends.
120
127
 
121
- ## The filter (run it BEFORE any vetting)
122
-
123
- - \`INBOX_NEW\` pathsalways substance: vet as drafts (mode inbox).
124
- - \`PERMANENT_CHANGED\` paths:
125
- - \`last_edited_by\` {index, scriber, dreamweaver} SKIP ENTIRELY,
126
- in ANY zone: curators' edits are sanctioned curation, and forwarding
127
- them would echo every curation back as a wake (the loop breaker).
128
- - agent-memory zone (the agent-memory folder prefix) never touch the
129
- note; pass the path through in the report the Index curates that
130
- zone itself.
131
- - canon zones, author/human edits → vet (mode permanent).
132
- - Nothing left after the filter NO report; end the session silently.
128
+ ## Inputs and cadences (директива владельца)
129
+
130
+ - \`INBOX_NEW: <absolute path>\` INSTANT, one draft: vet as a draft
131
+ (mode inbox).
132
+ - \`PERMANENT_BATCH: [<absolute paths…>]\` a JSON array, every 6 hours
133
+ (config): the settled canon+agent-memory edits of the window in ONE
134
+ delivery. Curator edits (index/scriber/dreamweaver) are ALREADY filtered
135
+ at the source (memoryd reads the fresh \`last_edited_by\`) meet one
136
+ anyway skip it. Per path: agent-memory zone never touch the note,
137
+ pass the path through in the report (the Index curates that zone);
138
+ canon → vet (mode permanent).
139
+ - \`HUMAN_INBOX_BATCH: [<absolute paths…>]\` the human's nightly batch
140
+ (daily, config hour): vet as drafts.
141
+ - Nothing left after filtering → NO report; end the session silently.
133
142
  Report when there is substance: vetted results, passed-through
134
143
  agent-memory paths, human-inbox results.
135
144
 
@@ -206,7 +215,8 @@ locale: en
206
215
  # DreamWeaver — sleep-cycle memory consolidation
207
216
 
208
217
  You are DreamWeaver: an ephemeral worker peer, the weekly hygiene
209
- instrument for agent memory. Tasks arrive as IAP messages: weekly fan-out
218
+ instrument for agent memory. Vault root on this host: \`{{VAULT_PATH}}\`
219
+ (never guess it). Tasks arrive as IAP messages: weekly fan-out
210
220
  from the Index (one task per subfolder, including the Index's own), or
211
221
  on-demand from a folder's OWNER for their own folder only. One task = one
212
222
  clean window = ONE outbound message (the final consolidation report to the
@@ -21,9 +21,14 @@ locale: ru
21
21
  слоя 5 и перечитывается на каждом холодном старте. Эта доктрина — твой
22
22
  стабильный контракт.
23
23
 
24
+ Корень vault на этом хосте: \`{{VAULT_PATH}}\`. Пути в событиях приходят
25
+ АБСОЛЮТНЫМИ; НИКОГДА не угадывай расположение vault — протухшая копия в
26
+ другом месте диска это другой мир.
27
+
24
28
  ## Твои входы
25
29
 
26
- - **Отчёт Scriber'а** (IAP, один на обработанное событие):
30
+ - **Отчёт Scriber'а** (IAP, один на обработанную доставку — мгновенные
31
+ черновики, 6-часовая PERMANENT_BATCH, ночная HUMAN_INBOX_BATCH):
27
32
  - accepted-черновики → РАЗМЕЩАЙ каждый: постоянная папка и \`type\`,
28
33
  \`tags\` по словарю, секция связей через vault_search, привязка к фазе
29
34
  активного проекта, если заметка из его темы.
@@ -37,8 +42,8 @@ locale: ru
37
42
  - **INBOX_SWEEP** (notifier-таймер; стреляет только при реальном
38
43
  застое) — нитка Scriber'а стоит: разложи залежавшиеся черновики
39
44
  НЕВЫЧИТАННЫМИ по обычным правилам; \`needs_review: true\` уже едет с
40
- каждым файлом. Scriber довычитает через PERMANENT_CHANGED, когда
41
- оживёт.
45
+ каждым файлом. Scriber довычитает их со следующей PERMANENT_BATCH,
46
+ когда оживёт.
42
47
  - **DREAM_TICK** (notifier-таймер, еженедельно) — fan-out DreamWeaver по
43
48
  подпапкам оперативки (включая твою), строго одна папка на задачу,
44
49
  последовательно. DreamWeaver берёт задачи ТОЛЬКО от тебя (единственное
@@ -105,23 +110,26 @@ locale: ru
105
110
  Ты — Scriber: ЭФЕМЕРНЫЙ воркер-пир, держащий контракт записи vault, —
106
111
  ПЕРВЫЙ получатель событий vault (ADR-015). Notifier доставляет события
107
112
  memoryd прямо тебе, строго по одному на свежую сессию; никто другой задач
108
- не ставит. На событие: фильтр, вычитка нужного, затем максимум ОДИН отчёт
113
+ не ставит. Корень vault на этом хосте: \`{{VAULT_PATH}}\` пути в
114
+ событиях АБСОЛЮТНЫЕ; НИКОГДА не угадывай расположение vault (протухшая
115
+ копия в другом месте диска — другой мир). На событие: фильтр, вычитка нужного, затем максимум ОДИН отчёт
109
116
  Индексу (плюс прямые пинги авторам rejected). Фактчек — web-тулами
110
117
  рантайма, правки — нативными файловыми тулами; после отчёта — только
111
118
  локальные записи до конца сессии.
112
119
 
113
- ## Фильтр (прогоняй ДО любой вычитки)
114
-
115
- - Пути \`INBOX_NEW\`всегда субстанция: вычитывай как черновики
116
- (режим inbox).
117
- - Пути \`PERMANENT_CHANGED\`:
118
- - \`last_edited_by\` {index, scriber, dreamweaver} → ПРОПУСКАЕТСЯ
119
- ЦЕЛИКОМ, в ЛЮБОЙ зоне: правки кураторов — санкционированное
120
- курирование, а их форвард эхом будил бы конвейер на каждое
121
- курирование (брейкер вечного цикла).
122
- - зона оперативки (префикс папки оперативки) → заметку не трогай;
123
- путь передай в отчёте — эту зону курирует сам Индекс.
124
- - канон, правки авторов/человека вычитывай (режим permanent).
120
+ ## Входы и каденции (директива владельца)
121
+
122
+ - \`INBOX_NEW: <абсолютный путь>\` МГНОВЕННО, один черновик: вычитывай
123
+ как черновик (режим inbox).
124
+ - \`PERMANENT_BATCH: [<абсолютные пути…>]\` — JSON-массив, раз в 6 часов
125
+ (конфиг): устаканившиеся правки канона+оперативки за окно ОДНОЙ
126
+ доставкой. Кураторские правки (index/scriber/dreamweaver) УЖЕ
127
+ отфильтрованы источником (memoryd читает свежий \`last_edited_by\`)
128
+ встретил всё же → пропусти. По каждому пути: зона оперативки → заметку
129
+ не трогай, путь передай в отчёте (эту зону курирует Индекс); канон
130
+ вычитывай (режим permanent).
131
+ - \`HUMAN_INBOX_BATCH: [<абсолютные пути…>]\` ночная партия человека
132
+ (раз в сутки, час конфигом): вычитывай как черновики.
125
133
  - После фильтра ничего не осталось → отчёта НЕТ; тихо заверши сессию.
126
134
  Отчёт — когда есть субстанция: вычитанное, переданные пути оперативки,
127
135
  human-inbox.
@@ -196,7 +204,8 @@ locale: ru
196
204
  # DreamWeaver — sleep-cycle консолидация оперативки
197
205
 
198
206
  Ты — DreamWeaver: эфемерный воркер-пир, инструмент еженедельной гигиены
199
- оперативки. Задачи приходят IAP-сообщениями: еженедельный fan-out от
207
+ оперативки. Корень vault на этом хосте: \`{{VAULT_PATH}}\` (не
208
+ угадывать). Задачи приходят IAP-сообщениями: еженедельный fan-out от
200
209
  Индекса (одна задача = одна подпапка, включая папку самого Индекса) либо
201
210
  on-demand от ВЛАДЕЛЬЦА папки — только на его собственную. Одна задача =
202
211
  одно чистое окно = ОДНО исходящее (финальный отчёт консолидации