@agfpd/iapeer-memory 0.2.8 → 0.3.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.
- package/package.json +2 -2
- package/src/cli.ts +8 -1
- package/src/commands/archive-stale.ts +88 -0
- package/src/commands/hook.ts +230 -28
- package/src/commands/init.ts +54 -37
- package/src/commands/memoryd.ts +16 -3
- package/src/commands/render.ts +4 -1
- package/src/commands/status.ts +4 -22
- package/src/commands/uninstall.ts +2 -2
- package/src/commands/update.ts +39 -31
- package/src/commands/verify.ts +27 -43
- package/src/paths.ts +3 -3
- package/src/provision.ts +22 -1
- package/src/surfaces/claude.ts +17 -12
- package/src/templates/guide-en.ts +63 -63
- package/src/templates/guide-ru.ts +60 -56
- package/src/templates/roles-en.ts +123 -147
- package/src/templates/roles-ru.ts +107 -134
- package/src/templates/skills.ts +13 -15
- package/src/watcher.ts +7 -74
package/src/commands/status.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* `iapeer-memory status` — read-only diagnostics of the whole chain
|
|
3
3
|
* (ADR-009: the status surface must diagnose a socket without a system).
|
|
4
4
|
* Aggregates: verify checks (NO repair) + slot declaration + a live TCP
|
|
5
|
-
* probe of the MCP endpoint
|
|
5
|
+
* probe of the MCP endpoint. Never mutates anything;
|
|
6
6
|
* exit 1 when something needs attention.
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -26,7 +26,7 @@ import { runVerify } from "./verify.js";
|
|
|
26
26
|
* back must say so here and why.
|
|
27
27
|
*
|
|
28
28
|
* Source of truth ladder: (1) the LIVE pipeline as reported by the running
|
|
29
|
-
* memoryd (a real
|
|
29
|
+
* memoryd (a real memory_search call over MCP — per-component statuses, the
|
|
30
30
|
* same object every search returns); (2) when memoryd is down — the static
|
|
31
31
|
* configuration view + the sqlite runtime probe. P3c live-smoke fact: in
|
|
32
32
|
* the compiled binary the sqlite-vec dylib does not resolve from /$bunfs —
|
|
@@ -50,7 +50,7 @@ export function searchPipelineLine(env: Record<string, string | undefined>): str
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/** Live pipeline from the running memoryd — the same per-component statuses
|
|
53
|
-
* every
|
|
53
|
+
* every memory_search returns. Null when memoryd is unreachable. */
|
|
54
54
|
export async function probeSearchPipeline(
|
|
55
55
|
egress: Egress,
|
|
56
56
|
port: number,
|
|
@@ -68,7 +68,7 @@ export async function probeSearchPipeline(
|
|
|
68
68
|
jsonrpc: "2.0",
|
|
69
69
|
id: 1,
|
|
70
70
|
method: "tools/call",
|
|
71
|
-
params: { name: "
|
|
71
|
+
params: { name: "memory_search", arguments: { query: "status probe" } },
|
|
72
72
|
}),
|
|
73
73
|
signal: AbortSignal.timeout(8000),
|
|
74
74
|
});
|
|
@@ -145,23 +145,5 @@ export async function cmdStatus(argv: string[], egress: Egress): Promise<number>
|
|
|
145
145
|
(livePipeline ?? `${searchPipelineLine(process.env)} (memoryd down — static view)`),
|
|
146
146
|
);
|
|
147
147
|
|
|
148
|
-
const vault = process.env.IAPEER_MEMORY_VAULT_PATH ?? "";
|
|
149
|
-
const localeRaw = process.env.IAPEER_MEMORY_LOCALE || "en";
|
|
150
|
-
if (vault && isLocaleId(localeRaw)) {
|
|
151
|
-
const inboxDir = path.join(vault, getTaxonomy(localeRaw).folders.inbox);
|
|
152
|
-
let count = 0;
|
|
153
|
-
try {
|
|
154
|
-
count = fs.readdirSync(inboxDir).filter((f) => f.endsWith(".md")).length;
|
|
155
|
-
} catch {
|
|
156
|
-
count = -1;
|
|
157
|
-
}
|
|
158
|
-
console.log(
|
|
159
|
-
` ${"inbox".padEnd(width)} ` +
|
|
160
|
-
(count < 0
|
|
161
|
-
? `folder missing (${inboxDir})`
|
|
162
|
-
: `${count} draft(s) awaiting the pipeline (a growing pile = the scriber thread is stuck; the sweep places stale drafts unvetted)`),
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
148
|
return results.some((r) => r.status === "fail") ? 1 : 0;
|
|
167
149
|
}
|
|
@@ -27,7 +27,7 @@ import { sweepUnprovision } from "../surfaces/sweep.js";
|
|
|
27
27
|
import { guardedUnlinkSync } from "@agfpd/iapeer-memory-core";
|
|
28
28
|
import {
|
|
29
29
|
DREAM_TRIGGER_ID,
|
|
30
|
-
|
|
30
|
+
LEGACY_SWEEP_TRIGGER_ID,
|
|
31
31
|
unregisterTimer,
|
|
32
32
|
unregisterWatcher,
|
|
33
33
|
WATCHER_TRIGGER_ID,
|
|
@@ -160,7 +160,7 @@ export function cmdUninstall(argv: string[], egress: Egress): number {
|
|
|
160
160
|
: `unregister not sent (${unreg.detail}) — remove the trigger manually via the watcher peer`
|
|
161
161
|
}`,
|
|
162
162
|
);
|
|
163
|
-
for (const id of [
|
|
163
|
+
for (const id of [LEGACY_SWEEP_TRIGGER_ID, DREAM_TRIGGER_ID]) {
|
|
164
164
|
const t = unregisterTimer(egress, { id, iapeerBin });
|
|
165
165
|
console.log(
|
|
166
166
|
`timer : ${
|
package/src/commands/update.ts
CHANGED
|
@@ -36,6 +36,8 @@ import {
|
|
|
36
36
|
configFromEnv,
|
|
37
37
|
isLocaleId,
|
|
38
38
|
renderDoctrine,
|
|
39
|
+
resolveMode,
|
|
40
|
+
curationPlan,
|
|
39
41
|
writeHostWideGuideFragment,
|
|
40
42
|
type LocaleId,
|
|
41
43
|
} from "@agfpd/iapeer-memory-core";
|
|
@@ -52,13 +54,14 @@ import { guideText, materialiseTemplates } from "../templates/index.js";
|
|
|
52
54
|
import { packageVersion } from "../version.js";
|
|
53
55
|
import {
|
|
54
56
|
DREAM_TARGET,
|
|
57
|
+
DREAM_TRIGGER_ID,
|
|
58
|
+
LEGACY_SWEEP_TRIGGER_ID,
|
|
55
59
|
dreamTimerMessage,
|
|
56
60
|
registerTimer,
|
|
57
61
|
registerWatcher,
|
|
58
|
-
|
|
62
|
+
unregisterTimer,
|
|
59
63
|
writeDreamGateScript,
|
|
60
64
|
writeLauncherScript,
|
|
61
|
-
writeStaleCheckScript,
|
|
62
65
|
} from "../watcher.js";
|
|
63
66
|
import { stopMemorydByPidFile } from "./uninstall.js";
|
|
64
67
|
|
|
@@ -231,44 +234,49 @@ export function cmdUpdate(argv: string[], egress: Egress): number {
|
|
|
231
234
|
// 8. launcher
|
|
232
235
|
step("launcher", writeLauncherScript({ launcherPath: paths.launcherPath, binaryPath: paths.binaryPath }));
|
|
233
236
|
|
|
234
|
-
// 8b. notifier wiring
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
+
// 8b. notifier wiring — RECONCILE to the curation plan (lean §7). The
|
|
238
|
+
// WATCHER always re-registers (it launches memoryd; same-id = replace, and
|
|
239
|
+
// it re-targets old hosts). The legacy inbox-SWEEP timer is unconditionally
|
|
240
|
+
// UNREGISTERED — the inbox pipeline is gone, so a host provisioned before
|
|
241
|
+
// the direct-to-canon migration gets its stale sweep trigger cleaned out
|
|
242
|
+
// (idempotent: not-found is a soft no-op). The DREAM timer is registered
|
|
243
|
+
// when its role is proactive, UNREGISTERED otherwise.
|
|
237
244
|
{
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
writeStaleCheckScript({
|
|
243
|
-
checkScriptPath: paths.checkScriptPath,
|
|
244
|
-
vaultPath: config.vaultPath,
|
|
245
|
-
inboxFolders,
|
|
246
|
-
});
|
|
247
|
-
} catch {
|
|
248
|
-
// unprovisioned env — registrations below still re-target
|
|
249
|
-
}
|
|
250
|
-
writeDreamGateScript({
|
|
251
|
-
dreamGateScriptPath: paths.dreamGateScriptPath,
|
|
252
|
-
binaryPath: paths.binaryPath,
|
|
245
|
+
const plan = curationPlan(resolveMode(process.env).roles);
|
|
246
|
+
const w = registerWatcher(egress, {
|
|
247
|
+
launcherPath: paths.launcherPath,
|
|
248
|
+
target: plan.eventTarget ?? undefined,
|
|
253
249
|
});
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
250
|
+
|
|
251
|
+
// Migration cleanup: drop any stale inbox-sweep timer.
|
|
252
|
+
const s = unregisterTimer(egress, { id: LEGACY_SWEEP_TRIGGER_ID });
|
|
253
|
+
|
|
254
|
+
let d: { ok: boolean; suppressed?: boolean; detail: string };
|
|
255
|
+
if (plan.dream) {
|
|
256
|
+
writeDreamGateScript({
|
|
261
257
|
dreamGateScriptPath: paths.dreamGateScriptPath,
|
|
262
|
-
|
|
263
|
-
|
|
258
|
+
binaryPath: paths.binaryPath,
|
|
259
|
+
});
|
|
260
|
+
d = registerTimer(egress, {
|
|
261
|
+
message: dreamTimerMessage({
|
|
262
|
+
cron: process.env.IAPEER_MEMORY_DREAM_CRON,
|
|
263
|
+
dreamGateScriptPath: paths.dreamGateScriptPath,
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
} else {
|
|
267
|
+
d = unregisterTimer(egress, { id: DREAM_TRIGGER_ID });
|
|
268
|
+
}
|
|
269
|
+
|
|
264
270
|
const sandboxed = w.suppressed && s.suppressed && d.suppressed;
|
|
265
271
|
step(
|
|
266
272
|
"triggers",
|
|
267
273
|
sandboxed
|
|
268
274
|
? "skipped (test sandbox — sends suppressed)"
|
|
269
275
|
: w.ok && s.ok && d.ok
|
|
270
|
-
? `
|
|
271
|
-
|
|
276
|
+
? `reconciled: watcher→${plan.eventTarget ?? "memoryd-only (lean)"}, ` +
|
|
277
|
+
`legacy sweep cleared, ` +
|
|
278
|
+
`dream ${plan.dream ? `→${DREAM_TARGET}` : "unregistered"}; confirm: verify`
|
|
279
|
+
: `watcher: ${w.ok ? "ok" : w.detail}; sweep-cleanup: ${s.ok ? "ok" : s.detail}; dream: ${d.ok ? "ok" : d.detail}`,
|
|
272
280
|
Boolean(sandboxed) || (w.ok && s.ok && d.ok),
|
|
273
281
|
);
|
|
274
282
|
}
|
package/src/commands/verify.ts
CHANGED
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
configFromEnv,
|
|
25
25
|
renderDoctrine,
|
|
26
26
|
renderedVersion,
|
|
27
|
+
resolveMode,
|
|
28
|
+
curationPlan,
|
|
27
29
|
} from "@agfpd/iapeer-memory-core";
|
|
28
30
|
import type { Egress } from "../egress.js";
|
|
29
31
|
import { readFleetMap, writeFleetMap } from "../fleet.js";
|
|
@@ -42,11 +44,8 @@ import {
|
|
|
42
44
|
readWatcherTrigger,
|
|
43
45
|
registerTimer,
|
|
44
46
|
registerWatcher,
|
|
45
|
-
sweepTimerMessage,
|
|
46
|
-
SWEEP_TRIGGER_ID,
|
|
47
47
|
writeDreamGateScript,
|
|
48
48
|
writeLauncherScript,
|
|
49
|
-
writeStaleCheckScript,
|
|
50
49
|
WATCHER_TRIGGER_ID,
|
|
51
50
|
} from "../watcher.js";
|
|
52
51
|
|
|
@@ -307,15 +306,15 @@ export function runVerify(egress: Egress, opts: VerifyOptions = {}): CheckResult
|
|
|
307
306
|
}
|
|
308
307
|
|
|
309
308
|
// 3. notifier wiring (ADR-015). Durable triggers live in the REGISTRANT's
|
|
310
|
-
// peer profile (canonical storage contract): registrant = index
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
309
|
+
// peer profile (canonical storage contract): registrant = index — the
|
|
310
|
+
// EVENT trigger (target=scriber, the curation pipeline) and the weekly
|
|
311
|
+
// dream timer (target=dreamweaver). Re-registration is ASYNC (same id =
|
|
312
|
+
// replace) — repair reports "sent", a re-run confirms.
|
|
314
313
|
{
|
|
315
314
|
const manifest = readRolesManifest(paths.rolesManifestPath);
|
|
316
315
|
const indexEntry = manifest?.roles.find((r) => r.role === "index") ?? null;
|
|
317
316
|
if (!indexEntry) {
|
|
318
|
-
for (const name of ["notifier-watcher", "
|
|
317
|
+
for (const name of ["notifier-watcher", "dream-timer"]) {
|
|
319
318
|
results.push({
|
|
320
319
|
name,
|
|
321
320
|
status: "skip",
|
|
@@ -324,6 +323,12 @@ export function runVerify(egress: Egress, opts: VerifyOptions = {}): CheckResult
|
|
|
324
323
|
}
|
|
325
324
|
} else {
|
|
326
325
|
const registrantCwd = indexEntry.peerCwd;
|
|
326
|
+
// Mode-aware expectation (lean §7): the WATCHER always exists (it launches
|
|
327
|
+
// memoryd) — its target is the §7.1 conditional. The DREAM timer is
|
|
328
|
+
// checked ONLY when its role is proactive; otherwise verify reports a
|
|
329
|
+
// SKIP (it should not exist — update reconciles any stale one).
|
|
330
|
+
const plan = curationPlan(resolveMode(process.env).roles);
|
|
331
|
+
const expectedEventTarget = plan.eventTarget ?? DEFAULT_EVENT_TARGET;
|
|
327
332
|
const checks: Array<{
|
|
328
333
|
name: string;
|
|
329
334
|
id: string;
|
|
@@ -338,8 +343,8 @@ export function runVerify(egress: Egress, opts: VerifyOptions = {}): CheckResult
|
|
|
338
343
|
expect: (t) =>
|
|
339
344
|
t.script !== paths.launcherPath
|
|
340
345
|
? `script is ${t.script}, expected ${paths.launcherPath}`
|
|
341
|
-
: t.target !==
|
|
342
|
-
? `target is ${t.target ?? "?"}, expected ${
|
|
346
|
+
: t.target !== expectedEventTarget
|
|
347
|
+
? `target is ${t.target ?? "?"}, expected ${expectedEventTarget}`
|
|
343
348
|
: null,
|
|
344
349
|
repairSend: () => {
|
|
345
350
|
writeLauncherScript({
|
|
@@ -348,41 +353,14 @@ export function runVerify(egress: Egress, opts: VerifyOptions = {}): CheckResult
|
|
|
348
353
|
});
|
|
349
354
|
return registerWatcher(egress, {
|
|
350
355
|
launcherPath: paths.launcherPath,
|
|
356
|
+
target: plan.eventTarget ?? undefined,
|
|
351
357
|
iapeerBin: opts.iapeerBin,
|
|
352
358
|
});
|
|
353
359
|
},
|
|
354
360
|
},
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
role: "time",
|
|
359
|
-
expect: (t) =>
|
|
360
|
-
(t as { check?: string }).check !== paths.checkScriptPath
|
|
361
|
-
? `check is ${(t as { check?: string }).check ?? "?"}, expected ${paths.checkScriptPath}`
|
|
362
|
-
: t.target !== "index"
|
|
363
|
-
? `target is ${t.target ?? "?"}, expected index`
|
|
364
|
-
: null,
|
|
365
|
-
repairSend: () => {
|
|
366
|
-
try {
|
|
367
|
-
const config = configFromEnv();
|
|
368
|
-
writeStaleCheckScript({
|
|
369
|
-
checkScriptPath: paths.checkScriptPath,
|
|
370
|
-
vaultPath: config.vaultPath,
|
|
371
|
-
inboxFolders: [
|
|
372
|
-
config.taxonomy.folders.inbox,
|
|
373
|
-
config.taxonomy.folders.inboxHuman,
|
|
374
|
-
],
|
|
375
|
-
});
|
|
376
|
-
} catch {
|
|
377
|
-
// unprovisioned env — the registration alone still heals the trigger
|
|
378
|
-
}
|
|
379
|
-
return registerTimer(egress, {
|
|
380
|
-
message: sweepTimerMessage({ checkScriptPath: paths.checkScriptPath }),
|
|
381
|
-
iapeerBin: opts.iapeerBin,
|
|
382
|
-
});
|
|
383
|
-
},
|
|
384
|
-
},
|
|
385
|
-
{
|
|
361
|
+
];
|
|
362
|
+
if (plan.dream) {
|
|
363
|
+
checks.push({
|
|
386
364
|
name: "dream-timer",
|
|
387
365
|
id: DREAM_TRIGGER_ID,
|
|
388
366
|
role: "time",
|
|
@@ -405,8 +383,14 @@ export function runVerify(egress: Egress, opts: VerifyOptions = {}): CheckResult
|
|
|
405
383
|
iapeerBin: opts.iapeerBin,
|
|
406
384
|
});
|
|
407
385
|
},
|
|
408
|
-
}
|
|
409
|
-
|
|
386
|
+
});
|
|
387
|
+
} else {
|
|
388
|
+
results.push({
|
|
389
|
+
name: "dream-timer",
|
|
390
|
+
status: "skip",
|
|
391
|
+
detail: "not expected (mode: dreamweaver not proactive)",
|
|
392
|
+
});
|
|
393
|
+
}
|
|
410
394
|
for (const c of checks) {
|
|
411
395
|
const trigger = readWatcherTrigger({ registrantCwd, id: c.id, role: c.role });
|
|
412
396
|
const problem = trigger
|
package/src/paths.ts
CHANGED
|
@@ -32,6 +32,8 @@ export type MemoryPaths = {
|
|
|
32
32
|
heartbeatPath: string;
|
|
33
33
|
hashStatePath: string;
|
|
34
34
|
tagsMirrorPath: string;
|
|
35
|
+
/** Compact tags-dictionary projection, injected to all peers (lean §3). */
|
|
36
|
+
tagsProjectionPath: string;
|
|
35
37
|
/** Roles manifest (written by init, read by verify/render): role → peerCwd/template. */
|
|
36
38
|
rolesManifestPath: string;
|
|
37
39
|
/** Rendered author indexes (`<agent>-vault-index.md` + `-full` variant). */
|
|
@@ -54,8 +56,6 @@ export type MemoryPaths = {
|
|
|
54
56
|
hooksDir: string;
|
|
55
57
|
/** memoryd launcher — the notifier watcher's script (wraps the stable binary). */
|
|
56
58
|
launcherPath: string;
|
|
57
|
-
/** Sweep check-script — gates the fail-open inbox sweep (ADR-015). */
|
|
58
|
-
checkScriptPath: string;
|
|
59
59
|
/** Dream-tick gate check-script — the notifier `check` for the weekly timer
|
|
60
60
|
* (shells the registry-free `dream-collect --gate`; a dead week wakes no one). */
|
|
61
61
|
dreamGateScriptPath: string;
|
|
@@ -92,6 +92,7 @@ export function memoryPaths(
|
|
|
92
92
|
heartbeatPath: path.join(stateDir, "memoryd.heartbeat"),
|
|
93
93
|
hashStatePath: path.join(stateDir, "memoryd.hashes.json"),
|
|
94
94
|
tagsMirrorPath: path.join(cacheDir, "tags-dictionary.md"),
|
|
95
|
+
tagsProjectionPath: path.join(cacheDir, "tags-projection.md"),
|
|
95
96
|
rolesManifestPath: path.join(stateDir, "roles.json"),
|
|
96
97
|
indexesDir: path.join(stateDir, "indexes"),
|
|
97
98
|
pidPath: path.join(stateDir, "memoryd.pid"),
|
|
@@ -101,7 +102,6 @@ export function memoryPaths(
|
|
|
101
102
|
templatesDir: path.join(path.dirname(configFile), "templates"),
|
|
102
103
|
hooksDir: path.join(path.dirname(configFile), "hooks"),
|
|
103
104
|
launcherPath: path.join(path.dirname(configFile), "memoryd-launcher.sh"),
|
|
104
|
-
checkScriptPath: path.join(path.dirname(configFile), "inbox-stale-check.sh"),
|
|
105
105
|
dreamGateScriptPath: path.join(path.dirname(configFile), "dream-tick-gate.sh"),
|
|
106
106
|
fleetMapPath: path.join(stateDir, "fleet.json"),
|
|
107
107
|
};
|
package/src/provision.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import path from "node:path";
|
|
15
|
-
import type { LocaleId, TaxonomyPreset } from "@agfpd/iapeer-memory-core";
|
|
15
|
+
import type { LocaleId, MemoryMode, TaxonomyPreset } from "@agfpd/iapeer-memory-core";
|
|
16
16
|
import { guardedWriteFileSync } from "@agfpd/iapeer-memory-core";
|
|
17
17
|
|
|
18
18
|
export type ProvisionResult = {
|
|
@@ -140,6 +140,8 @@ export function provisionVault(opts: {
|
|
|
140
140
|
export type ConfigContentOptions = {
|
|
141
141
|
vaultPath: string;
|
|
142
142
|
locale: LocaleId;
|
|
143
|
+
/** Curation mode written at init (lean §7). Default lean for new installs. */
|
|
144
|
+
mode: MemoryMode;
|
|
143
145
|
human?: string | null;
|
|
144
146
|
embeddingEndpoint?: string | null;
|
|
145
147
|
rerankerEndpoint?: string | null;
|
|
@@ -159,6 +161,13 @@ export function defaultConfigContent(opts: ConfigContentOptions): string {
|
|
|
159
161
|
? `IAPEER_MEMORY_HUMAN_NAME=${opts.human}`
|
|
160
162
|
: "# IAPEER_MEMORY_HUMAN_NAME=",
|
|
161
163
|
"",
|
|
164
|
+
"# Curation mode (lean §7): lean = NO proactive curation triggers — the base",
|
|
165
|
+
"# (страж fill + tag-gate + archive + dedup + projection) ALWAYS runs and the",
|
|
166
|
+
"# role peers are always provisioned (callable on-demand); curated = the",
|
|
167
|
+
"# Index/Scriber/DreamWeaver pipeline fires by itself. Independent per-role",
|
|
168
|
+
"# overrides: IAPEER_MEMORY_PROACTIVE_{INDEX,SCRIBER,DREAMWEAVER}=on|off.",
|
|
169
|
+
`IAPEER_MEMORY_MODE=${opts.mode}`,
|
|
170
|
+
"",
|
|
162
171
|
"# MCP endpoint of memoryd (ADR-012). 8766 = iapeer-MCP neighbour.",
|
|
163
172
|
"# IAPEER_MEMORY_MCP_PORT=8766",
|
|
164
173
|
"",
|
|
@@ -176,6 +185,18 @@ export function defaultConfigContent(opts: ConfigContentOptions): string {
|
|
|
176
185
|
"# Curator personalities exempt from needs_review stamping (ADR-006).",
|
|
177
186
|
"# IAPEER_MEMORY_CURATOR_SET=index,scriber,dreamweaver",
|
|
178
187
|
"",
|
|
188
|
+
"# Lean §3: per-tag boundary budget in the injected dictionary projection",
|
|
189
|
+
"# (×whole fleet — keep tight). Boundary text over this many chars is clipped.",
|
|
190
|
+
"# IAPEER_MEMORY_TAGS_BOUNDARY_MAXLEN=160",
|
|
191
|
+
"",
|
|
192
|
+
"# Lean §3a: dedup hint on canon writes — raw cosine similarity threshold",
|
|
193
|
+
"# above which an existing canon note is surfaced as a possible duplicate.",
|
|
194
|
+
"# Needs embeddings (semantic); with embeddings off the hint is silent.",
|
|
195
|
+
"# IAPEER_MEMORY_DEDUP_THRESHOLD=0.78",
|
|
196
|
+
"# Lean §3b: link-hint band lower bound — cosine in [this, DEDUP_THRESHOLD)",
|
|
197
|
+
"# surfaces «semantically close, maybe link [[…]]» (additive to the Index).",
|
|
198
|
+
"# IAPEER_MEMORY_LINK_HINT_THRESHOLD=0.68",
|
|
199
|
+
"",
|
|
179
200
|
"# Weekly dream-tick (deterministic pre-filter → DreamWeaver). Schedule is",
|
|
180
201
|
"# 5-field cron; the window is days BY TIME (not since-last-tick).",
|
|
181
202
|
"# IAPEER_MEMORY_DREAM_CRON=0 4 * * 1",
|
package/src/surfaces/claude.ts
CHANGED
|
@@ -15,11 +15,12 @@
|
|
|
15
15
|
* autoMemoryEnabled — the «last writer rolls back» class is
|
|
16
16
|
* closed by patching ONLY our entries);
|
|
17
17
|
* 2. mcp — `mcpServers["iapeer-memory"]` merged into `<cwd>/.mcp.json`,
|
|
18
|
-
* LITERAL url +
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* LITERAL url + ENV-SUBSTITUTED identity
|
|
19
|
+
* `${PEER_IDENTITY:-claude-<peer>}`: the per-cwd literal was a
|
|
20
|
+
* cwd-landmine (stale on rename/cwd drift), so the identity now
|
|
21
|
+
* follows the live session env (with a per-peer default fallback
|
|
22
|
+
* for envless dev sessions) — matching the codex `env_http_headers`
|
|
23
|
+
* form and the core's own 8765 migration (boris/Артур 15.06);
|
|
23
24
|
* 3. skills — `<cwd>/.claude/skills/iapeer-memory-<name>/SKILL.md`
|
|
24
25
|
* (embedded bodies, bytes-compare; the directory prefix is OUR
|
|
25
26
|
* namespace — unprovision removes every match).
|
|
@@ -128,12 +129,16 @@ export function materialiseShims(hooksDir: string): "written" | "identical" {
|
|
|
128
129
|
|
|
129
130
|
// ── expected forms ───────────────────────────────────────────────────────────
|
|
130
131
|
|
|
131
|
-
/** LITERAL url + identity header
|
|
132
|
-
*
|
|
133
|
-
* `
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* the
|
|
132
|
+
/** LITERAL url + ENV-SUBSTITUTED identity header, with a per-peer DEFAULT
|
|
133
|
+
* fallback: `${PEER_IDENTITY:-claude-<personality>}`. The live session env
|
|
134
|
+
* (`PEER_IDENTITY=claude-<peer>`, same value memoryd's parser strips to the
|
|
135
|
+
* personality) is used when set — closing the cwd-landmine (a bare literal
|
|
136
|
+
* goes stale on rename / cwd drift; the env follows the live session) and
|
|
137
|
+
* matching the core's own 8765 entry. The `:-claude-<personality>` fallback
|
|
138
|
+
* keeps manual dev sessions (no PEER_IDENTITY in env) working — boris/Артур
|
|
139
|
+
* 15.06; env-substitution proven live by iapeer (reverses the D1/D2 literal
|
|
140
|
+
* form back). Port is the host fact baked at provision; drift is repaired by
|
|
141
|
+
* the update/verify sweep. */
|
|
137
142
|
export function expectedMcpEntry(opts: {
|
|
138
143
|
port: number;
|
|
139
144
|
personality: string;
|
|
@@ -142,7 +147,7 @@ export function expectedMcpEntry(opts: {
|
|
|
142
147
|
type: "http",
|
|
143
148
|
url: `http://127.0.0.1:${opts.port}/mcp`,
|
|
144
149
|
headers: {
|
|
145
|
-
"X-IAPeer-Identity":
|
|
150
|
+
"X-IAPeer-Identity": `\${PEER_IDENTITY:-claude-${opts.personality}}`,
|
|
146
151
|
},
|
|
147
152
|
};
|
|
148
153
|
}
|
|
@@ -7,97 +7,97 @@
|
|
|
7
7
|
|
|
8
8
|
export const GUIDE_EN = `# iapeer-memory — the team's shared memory
|
|
9
9
|
|
|
10
|
-
iapeer-memory is the team's shared memory (agents + human): the canon
|
|
11
|
-
|
|
12
|
-
personal memory. You read it and you write it.
|
|
10
|
+
iapeer-memory is the team's shared memory (agents + human): the canon plus
|
|
11
|
+
each agent's personal memory. You read it and you write it.
|
|
13
12
|
|
|
14
13
|
**Search the memory first.** Someone may have already solved your problem
|
|
15
|
-
and written it down.
|
|
16
|
-
\`vault_graph\` for a note's neighborhood, \`vault_map\` for the global map.
|
|
14
|
+
and written it down.
|
|
17
15
|
|
|
18
|
-
**Verify before acting.** A note is a snapshot at write time.
|
|
19
|
-
the function/file/flag still exists — especially before the user acts on
|
|
20
|
-
your recommendation.
|
|
16
|
+
**Verify before acting.** A note is a snapshot at write time.
|
|
21
17
|
|
|
22
18
|
**On a conflict between memory and observation — trust the observation.**
|
|
23
19
|
Update or deprecate the stale note. This is living memory.
|
|
24
20
|
|
|
25
21
|
## Write proactively — you don't exist between sessions
|
|
26
22
|
|
|
27
|
-
A session is ephemeral: when it ends, the context is gone. The vault is
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
a pitfall, feedback, a system nuance) is recorded immediately, by you.
|
|
32
|
-
Canon material → a draft in \`00_Inbox/\`; personal material → your own
|
|
33
|
-
agent-memory folder. Asking the human "should I write this down?" is an
|
|
23
|
+
A session is ephemeral: when it ends, the context is gone. The vault is the
|
|
24
|
+
only thing that survives — what isn't written is lost. You record it
|
|
25
|
+
immediately, yourself, into the canon or your memory. What and where — you
|
|
26
|
+
decide, not the human. Asking the human "should I write this down?" is an
|
|
34
27
|
anti-pattern.
|
|
35
28
|
|
|
36
|
-
**
|
|
29
|
+
- **Session handoff / continuation snapshot** → write at lifecycle
|
|
30
|
+
boundaries (≈80% context, before /new, /compact, before a risky operation).
|
|
31
|
+
- **Something evolving** → consolidate at milestones, don't rewrite it by
|
|
32
|
+
micro-steps.
|
|
33
|
+
|
|
34
|
+
**Write concisely.** Notes are injected into readers' contexts — bloat
|
|
37
35
|
costs tokens for the whole team.
|
|
38
36
|
|
|
39
|
-
**Sweep as you write.** Added or updated a note →
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
**Sweep as you write.** Added or updated a note → check whether an older
|
|
38
|
+
note on the same topic became stale: flip its \`status\` to the final token.
|
|
39
|
+
|
|
40
|
+
## Storage layout
|
|
41
|
+
|
|
42
|
+
| Folder | What it holds | \`type\` | \`status\` |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| \`01_Knowledge/\` | Facts, conceptual descriptions, nuances of external systems, reference tables, recurring patterns, incidents generalized to a pattern | \`knowledge\` | \`current\` → \`outdated\` |
|
|
45
|
+
| \`02_Decisions/\` | Choices among alternatives, with rationale. **Immutable** once decided | \`decision\` | \`accepted\` → \`superseded\` |
|
|
46
|
+
| \`03_Projects/<name>/\` | One subfolder per project: \`Overview <name>.md\`, \`Plan <name>.md\`, \`Phase — <title>.md\` | \`project\` | Overview/Plan: \`active\` / \`paused\` / \`completed\`. Phase (exception): \`planned\` / \`active\` / \`completed\` / \`paused\` / \`cancelled\` |
|
|
47
|
+
| \`04_Ideas/\` | Hypotheses, "might be useful later" thoughts | \`idea\` | \`new\` → \`in_progress\` → \`dropped\` |
|
|
48
|
+
| \`05_Lists/\` | Registries, dictionaries, trackers (append-only); profiles of durable external entities | \`list\` | \`current\` → \`outdated\` |
|
|
49
|
+
| \`06_Agent_Memory/<name>/\` | Each agent's personal memory — feedback, working context, personal references, person profile, pitfalls | \`agent_memory\` | \`current\` → \`outdated\` |
|
|
50
|
+
|
|
51
|
+
## How and where to write
|
|
52
|
+
|
|
53
|
+
### Canon notes
|
|
54
|
+
|
|
55
|
+
**The canon** (knowledge / decisions / ideas / projects / lists) — information useful to the human and the agents, **stable material not tied to a single task**: conceptual descriptions of systems, nuances of external systems, decisions with alternatives and rationale, ideas and hypotheses, project overviews / plans / phases / lists / registries, profiles of durable external entities, stable patterns and incidents generalized to a pattern, infrastructure facts, and so on.
|
|
56
|
+
|
|
57
|
+
**Canon writing style** — idiomatic English, academic tone, self-contained text (no references to the conversation's context, expand abbreviations on first use), no emoji. Mark hypotheses as hypotheses ("Looks like X" / "Assumption: X").
|
|
58
|
+
**The canon's viewpoint is knowledge about a system.** A canon note describes the system objectively: what exists, how it works, what follows. A consequence is stated as a fact, in impersonal third person, as a property of the system. The subjective, procedural "how I act" lives in your memory (\`reference\` / \`pitfall\`). Material that is "objective knowledge + a personal technique" is split by the "Material both for the team and for you" rule below.
|
|
59
|
+
|
|
60
|
+
**Filename = the note's clear title.** Three months on, any reader sees only the title in the index — it must convey what the note is about without opening the file.
|
|
61
|
+
|
|
62
|
+
From you: BODY + ≥1 tag from the dictionary + organic inline \`[[...]]\` links + a self-describing TITLE (= the filename):
|
|
63
|
+
|
|
64
|
+
Write("{{VAULT_PATH}}/01_Knowledge/<Clear title>.md",
|
|
65
|
+
"---\\ntags: [Tag1, Tag2]\\n---\\n<body with inline [[links]]>")
|
|
43
66
|
|
|
44
|
-
|
|
67
|
+
Tags come from the dictionary \`99_System/Tags.md\`, ≥1 per canon note. No fitting tag? Add a new one to the dictionary (explicitly, not on the fly), then use it — extending the dictionary is expected, and proactive when there's a need.
|
|
45
68
|
|
|
46
|
-
|
|
47
|
-
|
|
69
|
+
**Links: inline and the \`## Links\` block.** A \`[[Note]]\` reference comes in two forms — both go into the graph equally:
|
|
70
|
+
- **inline** — right in the text, when the note IS part of what you're saying ("as in [[X]]").
|
|
71
|
+
- **the \`## Links\` block** — a separate link to a related note that isn't in the text but is conceptually nearby. Each line: \`- [[X]] — how it relates\` (related / extends / contradicts / applies). The explanation is mandatory — name the gist of the link in one phrase; it also keeps you from linking blind.
|
|
48
72
|
|
|
49
|
-
|
|
73
|
+
### Your memory → your own folder
|
|
50
74
|
|
|
51
|
-
|
|
52
|
-
(no dialogue references, expand abbreviations on first use), no emoji.
|
|
53
|
-
The canon's viewpoint is objective knowledge about a system — your
|
|
54
|
-
personal "how I do it" belongs in your agent memory instead. Mark
|
|
55
|
-
hypotheses as hypotheses. The filename IS the title readers will see in
|
|
56
|
-
their index — make it understandable without opening the file.
|
|
75
|
+
**Memory** (\`06_Agent_Memory/<name>/\`) — what's personally useful to you, by the 5 subtypes below.
|
|
57
76
|
|
|
58
|
-
|
|
77
|
+
**5 \`subtype\` values:**
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
- \`feedback\` — from colleagues (human or agent).
|
|
80
|
+
- \`context\` — personal context of a project, topic, or task (a handoff).
|
|
81
|
+
- \`reference\` — personal navigation marks, your procedural technique ("X first, then Y"), and the like.
|
|
82
|
+
- \`person_profile\` — facts, goals, preferences about a person.
|
|
83
|
+
- \`pitfall\` — a rule born from one incident (stepped on it once — wrote it down so it won't repeat).
|
|
62
84
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
description: "1–2 sentences on the content"
|
|
66
|
-
---
|
|
67
|
-
<body>
|
|
85
|
+
Write("{{VAULT_PATH}}/06_Agent_Memory/<your name>/<Title>.md",
|
|
86
|
+
"---\\nsubtype: <one of 5>\\ndescription: '1-2 sentence summary'\\n---\\n<body>")
|
|
68
87
|
|
|
69
|
-
|
|
70
|
-
(project/task handoff), \`reference\` (your navigation marks and
|
|
71
|
-
procedures), \`person_profile\` (facts about a person), \`pitfall\`
|
|
72
|
-
(a rule born from one incident — stepped on it once, wrote it down).
|
|
88
|
+
### Material both for the team and for you
|
|
73
89
|
|
|
74
|
-
|
|
75
|
-
\`00_Inbox/\` + a memory note mentioning it inline as \`[[Draft title]]\`.
|
|
90
|
+
Part of it is shared team knowledge, part is your memory → do **both**: into the canon (for the team) + a memory note mentioning it inline as \`[[The clear title]]\`.
|
|
76
91
|
|
|
77
92
|
## Editing rules
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
\`coauthors\`) — reword, update, replace stale text in place; no
|
|
81
|
-
"Update YYYY-MM-DD" journals. Additions to FOREIGN notes go through a new
|
|
82
|
-
inbox draft (the Index merges and adds you to \`coauthors\`).
|
|
94
|
+
**The canon is team knowledge, edit it freely** (your own and others'): reword, replace stale text in place, no "## Update YYYY-MM-DD" journals. **Memory is edited only by its author** (a personal zone).
|
|
83
95
|
|
|
84
|
-
From the frontmatter you change ONLY \`status
|
|
85
|
-
(knowledge/list/memory: current → outdated; idea: new → in_progress /
|
|
86
|
-
dropped; decision: accepted → superseded; project files: active → paused /
|
|
87
|
-
completed; phases: planned → active → completed / paused / cancelled).
|
|
88
|
-
The decision STATEMENT in \`02_Decisions/\` is immutable — supersede it
|
|
89
|
-
with a new draft instead. \`needs_review\` is the Index's field, not yours.
|
|
90
|
-
Never edit the links section, tags, \`type\`, or another agent's memory
|
|
91
|
-
folder.
|
|
96
|
+
From the frontmatter you change ONLY \`status\` (by the type's scale, from the "Storage layout" table above). You don't touch \`type\`, the tag structure, or another agent's memory; \`needs_review\` is set by mechanics and cleared by the Index or the human — not you.
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
connections (\`vault_graph\` first); otherwise flip \`status\` and let the
|
|
95
|
-
Index archive. Renames — same watershed.
|
|
98
|
+
Don't delete notes by hand. Set the final \`status\` — it's archived automatically (moved to \`07_Archive\`, links intact, the note stays in search with the stale de-boost). The final status IS the deletion: knowledge/list/memory → \`outdated\`, decision → \`superseded\`, idea → \`dropped\`, project/phase → \`completed\`/\`cancelled\`.
|
|
96
99
|
|
|
97
100
|
## Projects
|
|
98
101
|
|
|
99
|
-
\`Plan <name>.md\` = the high-level phase list; \`Phase — <title>.md\` =
|
|
100
|
-
that phase's task checklist. Both are append-only: add, don't rewrite
|
|
101
|
-
history. The full Plan text is read with \`Read\` — the path is in your
|
|
102
|
-
index.
|
|
102
|
+
\`Plan <name>.md\` = the high-level phase list; \`Phase — <title>.md\` = that phase's task checklist. Both are append-only — add, don't rewrite history.
|
|
103
103
|
`;
|