@agfpd/iapeer-memory 0.2.9 → 0.3.1

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.
@@ -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
- sweepTimerMessage,
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 (ADR-015): same-id re-send REPLACES the trigger
235
- // the idempotent re-target path (old hosts with target=index migrate to
236
- // target=scriber by this very step).
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
- let inboxFolders: string[] | null = null;
239
- try {
240
- const config = configFromEnv();
241
- inboxFolders = [config.taxonomy.folders.inbox, config.taxonomy.folders.inboxHuman];
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
- const w = registerWatcher(egress, { launcherPath: paths.launcherPath });
255
- const s = registerTimer(egress, {
256
- message: sweepTimerMessage({ checkScriptPath: paths.checkScriptPath }),
257
- });
258
- const d = registerTimer(egress, {
259
- message: dreamTimerMessage({
260
- cron: process.env.IAPEER_MEMORY_DREAM_CRON,
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
- ? `re-sent: event→scriber, sweep→index, dream→${DREAM_TARGET} (gated; same id = replace); confirm: verify`
271
- : `event: ${w.ok ? "sent" : w.detail}; sweep: ${s.ok ? "sent" : s.detail}; dream: ${d.ok ? "sent" : d.detail}`,
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
  }
@@ -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 for all
311
- // three — the EVENT trigger (target=scriber, inverted pipeline), the
312
- // sweep timer and the dream timer (both target=index). Re-registration is
313
- // ASYNC (same id = replace) — repair reports "sent", a re-run confirms.
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", "sweep-timer", "dream-timer"]) {
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 !== DEFAULT_EVENT_TARGET
342
- ? `target is ${t.target ?? "?"}, expected ${DEFAULT_EVENT_TARGET} (inverted pipeline)`
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
- name: "sweep-timer",
357
- id: SWEEP_TRIGGER_ID,
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
@@ -56,8 +56,6 @@ export type MemoryPaths = {
56
56
  hooksDir: string;
57
57
  /** memoryd launcher — the notifier watcher's script (wraps the stable binary). */
58
58
  launcherPath: string;
59
- /** Sweep check-script — gates the fail-open inbox sweep (ADR-015). */
60
- checkScriptPath: string;
61
59
  /** Dream-tick gate check-script — the notifier `check` for the weekly timer
62
60
  * (shells the registry-free `dream-collect --gate`; a dead week wakes no one). */
63
61
  dreamGateScriptPath: string;
@@ -104,7 +102,6 @@ export function memoryPaths(
104
102
  templatesDir: path.join(path.dirname(configFile), "templates"),
105
103
  hooksDir: path.join(path.dirname(configFile), "hooks"),
106
104
  launcherPath: path.join(path.dirname(configFile), "memoryd-launcher.sh"),
107
- checkScriptPath: path.join(path.dirname(configFile), "inbox-stale-check.sh"),
108
105
  dreamGateScriptPath: path.join(path.dirname(configFile), "dream-tick-gate.sh"),
109
106
  fleetMapPath: path.join(stateDir, "fleet.json"),
110
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
  "",
@@ -183,7 +192,10 @@ export function defaultConfigContent(opts: ConfigContentOptions): string {
183
192
  "# Lean §3a: dedup hint on canon writes — raw cosine similarity threshold",
184
193
  "# above which an existing canon note is surfaced as a possible duplicate.",
185
194
  "# Needs embeddings (semantic); with embeddings off the hint is silent.",
186
- "# IAPEER_MEMORY_DEDUP_THRESHOLD=0.82",
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",
187
199
  "",
188
200
  "# Weekly dream-tick (deterministic pre-filter → DreamWeaver). Schedule is",
189
201
  "# 5-field cron; the window is days BY TIME (not since-last-tick).",
@@ -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 + identity header (the BATTLE-TESTED direct form:
19
- * the core's writeClaudeMcpConfig writes the same shape for its
20
- * own 8765 server on the whole fleet; the plugin's env-
21
- * substitution form has NO live precedent in the direct project
22
- * scope D1's env-form decision was reversed on this fact);
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 byte-isomorphic to the core's own battle
132
- * form (writeClaudeMcpConfig: every fleet peer carries
133
- * `"X-IAPeer-Identity": "claude-<personality>"` against the 8765 daemon,
134
- * proven live at cold-start 08.06). Port and personality are HOST FACTS
135
- * baked at provision time; drift (port change, peer rename) is repaired by
136
- * the update/verify sweep against fleet.json (требование №2). */
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": `claude-${opts.personality}`,
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
- (knowledge / decisions / ideas / projects / lists) plus each agent's
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. Use \`vault_search\` (then \`Read\` the note),
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. Check that
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
- the ONLY thing that survives. Not written down lost forever.
29
-
30
- Write without asking permission. A meaningful result (a fix, a decision,
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
- **Write concisely.** Notes are injected into readers' contexts; bloat
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 → immediately check
40
- whether an older note on the same topic became stale: flip its \`status\`
41
- to the final token, or delete it (your own memory notes only, and only
42
- with zero graph connections — check with \`vault_graph\` first).
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
- ## Canon drafts \`00_Inbox/\`
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
- Write the BARE BODY only no frontmatter, no links section (the post-write
47
- hook stamps the 4 draft fields; the links section belongs to the Index):
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
- Write("{{VAULT_PATH}}/00_Inbox/<Meaningful title>.md", "<body>")
73
+ ### Your memory → your own folder
50
74
 
51
- Canon style: idiomatic vault language, academic tone, self-contained text
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
- ## Your agent memory → \`06_Agent_Memory/<your name>/\`
77
+ **5 \`subtype\` values:**
59
78
 
60
- Personal notes useful to you, written directly (no inbox). Frontmatter:
61
- only \`subtype\` + \`description\`, the hook fills the rest:
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
- subtype: <one of below>
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
- Five \`subtype\` values: \`feedback\` (from colleagues), \`context\`
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
- Material that is BOTH team knowledge and personal → do both: a draft in
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
- Edit the BODY of your own notes freely (you are \`author\` or in
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\`, by your note type's scale
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
- Deletion (\`rm\`)only in YOUR memory folder and only at zero
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
  `;