chapterhouse 0.7.0 → 0.8.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.
Files changed (81) hide show
  1. package/agents/korg.agent.md +65 -0
  2. package/dist/api/korg.js +34 -0
  3. package/dist/api/korg.test.js +42 -0
  4. package/dist/api/server.js +238 -2
  5. package/dist/api/server.test.js +199 -0
  6. package/dist/config.js +28 -0
  7. package/dist/config.test.js +20 -0
  8. package/dist/copilot/agents.js +3 -4
  9. package/dist/copilot/agents.test.js +12 -1
  10. package/dist/copilot/orchestrator.js +12 -1
  11. package/dist/copilot/orchestrator.test.js +3 -7
  12. package/dist/copilot/system-message.js +12 -10
  13. package/dist/copilot/system-message.test.js +6 -1
  14. package/dist/copilot/tools.js +193 -375
  15. package/dist/copilot/tools.memory.test.js +32 -0
  16. package/dist/copilot/tools.wiki.test.js +80 -59
  17. package/dist/copilot/turn-event-log-env.test.js +11 -15
  18. package/dist/daemon.js +19 -0
  19. package/dist/memory/decisions.js +6 -5
  20. package/dist/memory/entities.js +20 -9
  21. package/dist/memory/eot.js +30 -8
  22. package/dist/memory/eot.test.js +220 -6
  23. package/dist/memory/hooks.js +151 -0
  24. package/dist/memory/hooks.test.js +325 -0
  25. package/dist/memory/hot-tier.js +37 -0
  26. package/dist/memory/hot-tier.test.js +30 -0
  27. package/dist/memory/housekeeping-scheduler.js +35 -0
  28. package/dist/memory/housekeeping-scheduler.test.js +50 -0
  29. package/dist/memory/inbox.js +10 -0
  30. package/dist/memory/index.js +3 -1
  31. package/dist/memory/migration.js +244 -0
  32. package/dist/memory/migration.test.js +108 -0
  33. package/dist/memory/reflect.js +273 -0
  34. package/dist/memory/reflect.test.js +254 -0
  35. package/dist/paths.js +31 -11
  36. package/dist/store/db.js +187 -4
  37. package/dist/store/db.test.js +66 -2
  38. package/dist/test/helpers/reset-singletons.js +8 -0
  39. package/dist/test/helpers/reset-singletons.test.js +37 -0
  40. package/dist/test/setup-env.js +9 -1
  41. package/dist/wiki/consolidation.js +641 -0
  42. package/dist/wiki/consolidation.test.js +143 -0
  43. package/dist/wiki/frontmatter.js +48 -0
  44. package/dist/wiki/frontmatter.test.js +42 -0
  45. package/dist/wiki/fs.js +22 -13
  46. package/dist/wiki/index-manager.js +305 -330
  47. package/dist/wiki/index-manager.test.js +265 -144
  48. package/dist/wiki/ingest.js +347 -0
  49. package/dist/wiki/ingest.test.js +111 -0
  50. package/dist/wiki/links.js +151 -0
  51. package/dist/wiki/links.test.js +176 -0
  52. package/dist/wiki/log-manager.js +8 -5
  53. package/dist/wiki/log-manager.test.js +4 -0
  54. package/dist/wiki/migrate-topics.test.js +16 -6
  55. package/dist/wiki/scheduler.js +118 -0
  56. package/dist/wiki/scheduler.test.js +64 -0
  57. package/dist/wiki/timeline.js +51 -0
  58. package/dist/wiki/timeline.test.js +65 -0
  59. package/dist/wiki/topic-structure.js +1 -1
  60. package/package.json +1 -1
  61. package/skills/pkb-ideas/SKILL.md +78 -0
  62. package/skills/pkb-ideas/_meta.json +4 -0
  63. package/skills/pkb-org/SKILL.md +82 -0
  64. package/skills/pkb-org/_meta.json +4 -0
  65. package/skills/pkb-people/SKILL.md +74 -0
  66. package/skills/pkb-people/_meta.json +4 -0
  67. package/skills/pkb-research/SKILL.md +83 -0
  68. package/skills/pkb-research/_meta.json +4 -0
  69. package/skills/pkb-source/SKILL.md +38 -0
  70. package/skills/pkb-source/_meta.json +4 -0
  71. package/skills/wiki-conventions/SKILL.md +5 -5
  72. package/web/dist/assets/{index-DuKYxMIR.css → index-5kz9aRU9.css} +1 -1
  73. package/web/dist/assets/{index-DytB69KC.js → index-BbX9RKf3.js} +91 -89
  74. package/web/dist/assets/index-BbX9RKf3.js.map +1 -0
  75. package/web/dist/index.html +2 -2
  76. package/dist/wiki/context.js +0 -138
  77. package/dist/wiki/fix.js +0 -335
  78. package/dist/wiki/fix.test.js +0 -350
  79. package/dist/wiki/lint.js +0 -451
  80. package/dist/wiki/lint.test.js +0 -329
  81. package/web/dist/assets/index-DytB69KC.js.map +0 -1
package/dist/config.js CHANGED
@@ -58,6 +58,7 @@ const configSchema = z.object({
58
58
  CHAPTERHOUSE_MEMORY_INJECT: z.string().optional(),
59
59
  CHAPTERHOUSE_MEMORY_AUTO_ACCEPT: z.string().optional(),
60
60
  CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED: z.string().optional(),
61
+ CHAPTERHOUSE_MEMORY_HOOKS_ENABLED: z.string().optional(),
61
62
  CHAPTERHOUSE_MEMORY_HOUSEKEEPING_ENABLED: z.string().optional(),
62
63
  CHAPTERHOUSE_MEMORY_HOUSEKEEPING_TURNS: z.string().optional(),
63
64
  CHAPTERHOUSE_MEMORY_DECAY_DAYS: z.string().optional(),
@@ -65,6 +66,8 @@ const configSchema = z.object({
65
66
  CHAPTERHOUSE_MEMORY_TIERING_ENABLED: z.string().optional(),
66
67
  CHAPTERHOUSE_MEMORY_HOT_RECALL_BOOST: z.string().optional(),
67
68
  CHAPTERHOUSE_MEMORY_HOT_AGE_DAYS: z.string().optional(),
69
+ CHAPTERHOUSE_PKB_CONSOLIDATION_ENABLED: z.string().optional(),
70
+ CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR: z.string().optional(),
68
71
  });
69
72
  export const DEFAULT_MODEL = "claude-sonnet-4.6";
70
73
  export const DEFAULT_TEAM_WIKI_CACHE_TTL_MINUTES = 60;
@@ -140,6 +143,17 @@ function parsePositiveNumberEnv(name, rawValue, defaultValue) {
140
143
  }
141
144
  return parsed;
142
145
  }
146
+ function parseHourEnv(name, rawValue, defaultValue) {
147
+ const normalized = rawValue?.trim();
148
+ if (!normalized) {
149
+ return defaultValue;
150
+ }
151
+ const parsed = Number(normalized);
152
+ if (!Number.isInteger(parsed) || parsed < 0 || parsed > 23) {
153
+ throw new Error(`${name} must be an integer between 0 and 23, got: "${rawValue}"`);
154
+ }
155
+ return parsed;
156
+ }
143
157
  function parseCorsAllowedOrigins(rawValue) {
144
158
  const normalized = rawValue?.trim();
145
159
  if (!normalized) {
@@ -298,6 +312,8 @@ export function parseRuntimeConfig(env, options = {}) {
298
312
  const memoryInboxRetentionDays = parsePositiveIntegerEnv("CHAPTERHOUSE_MEMORY_INBOX_RETENTION_DAYS", raw.CHAPTERHOUSE_MEMORY_INBOX_RETENTION_DAYS, 7);
299
313
  const memoryHotRecallBoost = parsePositiveNumberEnv("CHAPTERHOUSE_MEMORY_HOT_RECALL_BOOST", raw.CHAPTERHOUSE_MEMORY_HOT_RECALL_BOOST, 1.5);
300
314
  const memoryHotAgeDays = parsePositiveIntegerEnv("CHAPTERHOUSE_MEMORY_HOT_AGE_DAYS", raw.CHAPTERHOUSE_MEMORY_HOT_AGE_DAYS, 30);
315
+ const pkbConsolidationEnabled = parseBooleanEnv("CHAPTERHOUSE_PKB_CONSOLIDATION_ENABLED", raw.CHAPTERHOUSE_PKB_CONSOLIDATION_ENABLED, true);
316
+ const pkbConsolidationHour = parseHourEnv("CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR", raw.CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR, 3);
301
317
  if (effectiveEntraAuthEnabled && (!effectiveEntraTenantId || !effectiveEntraClientId)) {
302
318
  throw new Error("ENTRA_AUTH_ENABLED=true requires ENTRA_TENANT_ID and ENTRA_CLIENT_ID");
303
319
  }
@@ -348,6 +364,7 @@ export function parseRuntimeConfig(env, options = {}) {
348
364
  memoryInjectEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_INJECT", raw.CHAPTERHOUSE_MEMORY_INJECT, true),
349
365
  memoryAutoAcceptEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_AUTO_ACCEPT", raw.CHAPTERHOUSE_MEMORY_AUTO_ACCEPT, true),
350
366
  memoryEndOfTaskHookEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED", raw.CHAPTERHOUSE_MEMORY_EOT_HOOK_ENABLED, true),
367
+ memoryHooksEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_HOOKS_ENABLED", raw.CHAPTERHOUSE_MEMORY_HOOKS_ENABLED, true),
351
368
  memoryHousekeepingEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_HOUSEKEEPING_ENABLED", raw.CHAPTERHOUSE_MEMORY_HOUSEKEEPING_ENABLED, true),
352
369
  memoryHousekeepingTurns,
353
370
  memoryDecayDays,
@@ -355,6 +372,8 @@ export function parseRuntimeConfig(env, options = {}) {
355
372
  memoryTieringEnabled: parseZeroOneEnv("CHAPTERHOUSE_MEMORY_TIERING_ENABLED", raw.CHAPTERHOUSE_MEMORY_TIERING_ENABLED, true),
356
373
  memoryHotRecallBoost,
357
374
  memoryHotAgeDays,
375
+ pkbConsolidationEnabled,
376
+ pkbConsolidationHour,
358
377
  modeCompatibilityWarnings,
359
378
  };
360
379
  }
@@ -406,6 +425,7 @@ export const config = {
406
425
  memoryInjectEnabled: runtimeConfig.memoryInjectEnabled,
407
426
  memoryAutoAcceptEnabled: runtimeConfig.memoryAutoAcceptEnabled,
408
427
  memoryEndOfTaskHookEnabled: runtimeConfig.memoryEndOfTaskHookEnabled,
428
+ memoryHooksEnabled: runtimeConfig.memoryHooksEnabled,
409
429
  memoryHousekeepingEnabled: runtimeConfig.memoryHousekeepingEnabled,
410
430
  memoryHousekeepingTurns: runtimeConfig.memoryHousekeepingTurns,
411
431
  memoryDecayDays: runtimeConfig.memoryDecayDays,
@@ -413,6 +433,14 @@ export const config = {
413
433
  memoryTieringEnabled: runtimeConfig.memoryTieringEnabled,
414
434
  memoryHotRecallBoost: runtimeConfig.memoryHotRecallBoost,
415
435
  memoryHotAgeDays: runtimeConfig.memoryHotAgeDays,
436
+ pkbConsolidationEnabled: runtimeConfig.pkbConsolidationEnabled,
437
+ pkbConsolidationHour: runtimeConfig.pkbConsolidationHour,
438
+ get PKB_CONSOLIDATION_ENABLED() {
439
+ return runtimeConfig.pkbConsolidationEnabled;
440
+ },
441
+ get PKB_CONSOLIDATION_HOUR() {
442
+ return runtimeConfig.pkbConsolidationHour;
443
+ },
416
444
  modeCompatibilityWarnings: runtimeConfig.modeCompatibilityWarnings,
417
445
  copilotAuthToken: runtimeConfig.copilotAuthToken,
418
446
  get copilotModel() {
@@ -126,6 +126,26 @@ test("defaults memory checkpoint turns to 5 and parses integer overrides", async
126
126
  assert.equal(parsedThree.memoryCheckpointTurns, 3);
127
127
  assert.equal(parsedTen.memoryCheckpointTurns, 10);
128
128
  });
129
+ test("parses PKB consolidation defaults and explicit hour overrides", async () => {
130
+ const configModule = await import("./config.js");
131
+ assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
132
+ const parsedDefault = configModule.parseRuntimeConfig({});
133
+ const parsedExplicit = configModule.parseRuntimeConfig({
134
+ CHAPTERHOUSE_PKB_CONSOLIDATION_ENABLED: "false",
135
+ CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR: "5",
136
+ });
137
+ assert.equal(parsedDefault.pkbConsolidationEnabled, true);
138
+ assert.equal(parsedDefault.pkbConsolidationHour, 3);
139
+ assert.equal(parsedExplicit.pkbConsolidationEnabled, false);
140
+ assert.equal(parsedExplicit.pkbConsolidationHour, 5);
141
+ });
142
+ test("rejects invalid PKB consolidation hours outside 0-23", async () => {
143
+ const configModule = await import("./config.js");
144
+ assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
145
+ assert.throws(() => configModule.parseRuntimeConfig({
146
+ CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR: "24",
147
+ }), /CHAPTERHOUSE_PKB_CONSOLIDATION_HOUR must be an integer between 0 and 23/);
148
+ });
129
149
  test("defaults memory injection on and still honors explicit CHAPTERHOUSE_MEMORY_INJECT overrides", async () => {
130
150
  const configModule = await import("./config.js");
131
151
  assert.equal(typeof configModule.parseRuntimeConfig, "function", "parseRuntimeConfig should be exported");
@@ -299,7 +299,7 @@ Chapterhouse agent memory follows a three-tier memory model: **read** with \`mem
299
299
  ### Shared Wiki
300
300
  All agents share a wiki knowledge base for persistent memory. Use \`wiki_read\` and \`wiki_search\` to find existing knowledge, and \`wiki_update\` to save important findings.
301
301
 
302
- Invoke \`wiki-conventions\` before wiki writes or restructuring work. Treat \`wiki_update\`, \`remember\`, \`forget\`, \`wiki_ingest\`, \`wiki_lint\`, and \`wiki_rebuild_index\` as write-sensitive workflows. Before using them, read \`pages/index.md\`, scan the last 20-30 entries of \`pages/_meta/log.md\`, and run \`wiki_search\` for the topic when the wiki is large or the topic is ambiguous.
302
+ Invoke \`wiki-conventions\` before wiki writes or restructuring work. Treat \`wiki_update\` and \`wiki_ingest_source\` as write-sensitive workflows, and use \`memory_propose\` for agent-memory handoff. Before using wiki write tools, read \`pages/index.md\`, scan the last 20-30 entries of \`pages/_meta/log.md\`, and run \`wiki_search\` for the topic when the wiki is large or the topic is ambiguous.
303
303
 
304
304
  ### Communication
305
305
  - You receive tasks from @chapterhouse (the orchestrator) or directly from the user
@@ -340,8 +340,7 @@ export function buildAgentRoster() {
340
340
  }
341
341
  // The wiki tools that every agent gets regardless of tool config
342
342
  const WIKI_TOOL_NAMES = new Set([
343
- "wiki_search", "wiki_read", "wiki_update", "remember", "recall", "forget",
344
- "wiki_ingest", "wiki_lint", "wiki_rebuild_index",
343
+ "wiki_search", "wiki_read", "wiki_update", "wiki_reindex", "wiki_append_timeline", "wiki_ingest_source",
345
344
  "memory_recall", "memory_propose", "memory_list_action_items",
346
345
  ]);
347
346
  // Management tools that only @chapterhouse should have
@@ -351,7 +350,7 @@ const MANAGEMENT_TOOL_NAMES = new Set([
351
350
  "switch_model", "toggle_auto", "list_models",
352
351
  "restart_chapterhouse", "list_skills", "learn_skill", "uninstall_skill",
353
352
  "list_machine_sessions", "attach_machine_session",
354
- "memory_remember", "memory_set_scope", "memory_housekeep", "memory_promote", "memory_demote",
353
+ "memory_remember", "memory_set_scope", "memory_housekeep", "memory_reflect", "memory_promote", "memory_demote",
355
354
  "memory_add_action_item", "memory_complete_action_item", "memory_drop_action_item", "memory_snooze_action_item",
356
355
  ]);
357
356
  export function getCurrentToolAgentSlug() {
@@ -1,4 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
2
4
  import test from "node:test";
3
5
  import { composeAgentSystemMessage, filterToolsForAgent, bindToolsToAgent, getCurrentToolTaskId, getAgentRegistry, parseAgentMd, withToolTaskContext, } from "./agents.js";
4
6
  function makeAgent(slug) {
@@ -48,7 +50,8 @@ test("composeAgentSystemMessage steers wiki-capable agents to wiki-conventions",
48
50
  for (const slug of ["coder", "general-purpose"]) {
49
51
  const message = composeAgentSystemMessage(makeAgent(slug));
50
52
  assert.match(message, /invoke `wiki-conventions` before wiki writes/i);
51
- assert.match(message, /wiki_update[\s\S]{0,80}remember[\s\S]{0,80}forget[\s\S]{0,80}wiki_ingest[\s\S]{0,80}wiki_lint[\s\S]{0,80}wiki_rebuild_index/i);
53
+ assert.match(message, /wiki_update[\s\S]{0,120}wiki_ingest_source[\s\S]{0,120}memory_propose/i);
54
+ assert.doesNotMatch(message, /`remember`|`forget`|`wiki_ingest`(?!_source)|`wiki_lint`|`wiki_rebuild_index`/);
52
55
  assert.match(message, /read `pages\/index\.md`/i);
53
56
  assert.match(message, /scan the last 20-30 entries of `pages\/_meta\/log\.md`/i);
54
57
  }
@@ -92,6 +95,14 @@ test("parseAgentMd preserves block-style skills arrays from charter frontmatter"
92
95
  assert.ok(agent, "agent charter should parse");
93
96
  assert.deepEqual(agent.skills, ["frontend-design", "accessibility-review"]);
94
97
  });
98
+ test("korg charter is persistent and standardizes on the Summary heading", () => {
99
+ const charter = readFileSync(join(process.cwd(), "agents", "korg.agent.md"), "utf8");
100
+ const agent = parseAgentMd(charter, "korg");
101
+ assert.ok(agent, "korg charter should parse");
102
+ assert.equal(agent.persistent, true);
103
+ assert.match(charter, /## Summary/);
104
+ assert.doesNotMatch(charter, /## Compiled Truth/);
105
+ });
95
106
  test("persistent agents cannot receive scope-changing management tools", () => {
96
107
  const agent = {
97
108
  ...makeAgent("bellonda"),
@@ -13,7 +13,6 @@ import { getSkillDirectories } from "./skills.js";
13
13
  import { resetClient } from "./client.js";
14
14
  import { logConversation, getState, setState, deleteState, getCopilotSession, upsertCopilotSession, getTaskSessionKey, getDb, appendTaskEvent } from "../store/db.js";
15
15
  import { maybeWriteEpisode } from "./episode-writer.js";
16
- import { getWikiSummary } from "../wiki/context.js";
17
16
  import { SESSIONS_DIR } from "../paths.js";
18
17
  import { resolveModel } from "./router.js";
19
18
  import { loadAgents, ensureDefaultAgents, clearActiveTasks, getAgentRegistry, setActiveAgent, parseAtMention, buildAgentRoster, getActiveTasks, getAgent, composeAgentSystemMessage, filterMcpServersForAgent, filterToolsForAgent, withToolTaskContext, } from "./agents.js";
@@ -36,6 +35,18 @@ const AGENT_REPLY_CHUNK_SIZE = 500;
36
35
  const AGENT_REPLY_CHUNK_THRESHOLD = 8 * 1024;
37
36
  const ORCHESTRATOR_SESSION_KEY = "orchestrator_session_id";
38
37
  const LAST_AUTHENTICATED_USER_KEY = "last_authenticated_user";
38
+ function getWikiSummary() {
39
+ try {
40
+ const db = getDb();
41
+ const pages = db.prepare(`SELECT title, summary, last_updated FROM wiki_pages ORDER BY last_updated DESC LIMIT 50`).all();
42
+ if (pages.length === 0)
43
+ return "";
44
+ return pages.map((page) => `${page.title}: ${page.summary || ""}`.trim()).join("\n");
45
+ }
46
+ catch {
47
+ return "";
48
+ }
49
+ }
39
50
  let logMessage = () => { };
40
51
  export function setMessageLogger(fn) {
41
52
  logMessage = fn;
@@ -130,6 +130,7 @@ async function loadOrchestratorModule(t, overrides = {}) {
130
130
  housekeepingRuns: [],
131
131
  housekeepingInFlight: false,
132
132
  activeScope: makeScope(1, "chapterhouse", "Chapterhouse", "Core Chapterhouse work."),
133
+ wikiPages: [{ title: "Chapterhouse", summary: "wiki summary", last_updated: "2026-05-13" }],
133
134
  ...overrides,
134
135
  };
135
136
  const client = createFakeClient(state);
@@ -426,7 +427,7 @@ async function loadOrchestratorModule(t, overrides = {}) {
426
427
  return {};
427
428
  },
428
429
  get: () => undefined,
429
- all: () => [],
430
+ all: () => (sql.includes("FROM wiki_pages") ? (state.wikiPages ?? []) : []),
430
431
  }),
431
432
  transaction: (fn) => fn,
432
433
  }),
@@ -447,11 +448,6 @@ async function loadOrchestratorModule(t, overrides = {}) {
447
448
  },
448
449
  },
449
450
  });
450
- t.mock.module("../wiki/context.js", {
451
- namedExports: {
452
- getWikiSummary: () => "wiki summary",
453
- },
454
- });
455
451
  t.mock.module("../wiki/project-registry.js", {
456
452
  namedExports: {
457
453
  loadRegistry: () => state.projectRegistry,
@@ -637,7 +633,7 @@ test("initOrchestrator falls back to an available model and eagerly creates a se
637
633
  assert.equal(state.loadAgentsCalls, 1);
638
634
  assert.equal(state.createSessionCalls.length, 1);
639
635
  assert.equal(state.healthCheckIntervalMs, 30_000);
640
- assert.equal(state.systemOptions?.memorySummary, "wiki summary");
636
+ assert.equal(state.systemOptions?.memorySummary, "Chapterhouse: wiki summary");
641
637
  assert.equal(state.store.get("orchestrator_session_id"), "session-123");
642
638
  });
643
639
  test("initOrchestrator passes hot-tier XML into the orchestrator system prompt when injection is enabled", async (t) => {
@@ -4,7 +4,7 @@ export function getOrchestratorSystemMessage(opts) {
4
4
  const versionBanner = opts?.version ? `\nYou are running inside chapterhouse v${opts.version}.\n` : "";
5
5
  const memoryBlock = opts?.memorySummary
6
6
  ? `\n## Memory\nYou have a persistent memory store. Here's what you currently remember:\n\n${opts.memorySummary}\n`
7
- : "\n## Memory\nYou have a persistent memory store. It's currently empty — use `remember` to start building it!\n";
7
+ : "\n## Memory\nYou have a persistent memory store. It's currently empty — use `memory_remember` for agent memory or `wiki_update` for wiki knowledge.\n";
8
8
  const selfEditBlock = opts?.selfEditEnabled
9
9
  ? ""
10
10
  : `\n## Self-Edit Protection
@@ -115,22 +115,24 @@ You can delegate **multiple tasks simultaneously**. Different agents can work in
115
115
  - \`restart_chapterhouse\`: Restart the Chapterhouse daemon.
116
116
 
117
117
  ### Memory
118
- - \`remember\`: Save something to memory.
119
- - \`recall\`: Search your memory for stored facts, preferences, or information.
120
- - \`forget\`: Remove content from the wiki.
118
+ - \`memory_remember\`: Save something durable to scoped agent memory.
119
+ - \`memory_recall\`: Search scoped agent memory for stored facts, decisions, and observations.
120
+ - \`memory_reflect\`: Synthesize durable patterns from repeated observations in the scoped memory store.
121
+ - \`wiki_update\`: Create or update wiki pages when knowledge belongs in the shared wiki.
122
+ - \`wiki_reindex\`: Force a filesystem-to-SQLite wiki reindex if existing pages are missing from search.
121
123
 
122
124
  Subagent proposals from \`memory_propose\` are processed automatically at end-of-task, so you do not need to manually review them mid-conversation.
123
125
 
124
- **Past conversations**: Daily conversation summaries are auto-written to \`pages/conversations/YYYY-MM-DD.md\`. When the user references something from earlier ("what did we decide about X", "remember when we…", "the thing we discussed yesterday"), call \`wiki_search\` (or \`recall\`) — don't guess from your own context, since older turns may have been compacted out.
126
+ **Past conversations**: Daily conversation summaries are auto-written to \`pages/conversations/YYYY-MM-DD.md\`. When the user references something from earlier ("what did we decide about X", "remember when we…", "the thing we discussed yesterday"), call \`wiki_search\` or \`memory_recall\` — don't guess from your own context, since older turns may have been compacted out.
125
127
 
126
128
  **Wiki structure** — the wiki enforces a topic layout, so put things in the right place:
127
129
  - **Entity categories** (\`projects\`, \`people\`, \`orgs\`, \`tools\`, \`topics\`, \`areas\`): every named thing gets its own directory \`pages/<category>/<topic-slug>/\`. The topic's overview lives at \`pages/<category>/<topic-slug>/index.md\`; related sub-pages ("facets") go alongside it, e.g. \`pages/projects/chapterhouse/decisions.md\`, \`pages/projects/chapterhouse/feature-ideas.md\`. Exactly one topic level deep; lowercase-slug names only.
128
130
  - **Flat categories** (\`preferences\`, \`facts\`, \`routines\`): a single file each, e.g. \`pages/preferences.md\`.
129
- - **Decisions** are always recorded against an entity, never as a standalone page: a decision about a project goes to \`pages/projects/<topic>/decisions.md\`. With \`remember\`, pass \`category: "decision"\` plus \`about\` (which entity category) and \`entity\`.
130
- - With \`remember\`, pass \`entity\` for entity categories (and \`facet\` to target a sub-page). With \`wiki_update\`, give the full canonical path — bad paths are rejected with a suggested correction; just retry with the suggestion.
131
- - If the structure ever looks wrong, call \`wiki_rebuild_index\` to regenerate the index from disk.
131
+ - **Decisions** are always recorded against an entity, never as a standalone page: a decision about a project goes to \`pages/projects/<topic>/decisions.md\`. Use \`wiki_update\` for shared wiki records or \`memory_remember\` for scoped agent memory.
132
+ - For entity pages and facet pages, use \`wiki_update\` with the full canonical path — bad paths are rejected with a suggested correction; just retry with the suggestion.
133
+ - Source material that should be preserved before synthesis belongs in \`wiki_ingest_source\`.
132
134
 
133
- **Wiki writes and restructuring**: Before writing or restructuring wiki content, invoke the \`wiki-conventions\` skill. Treat \`wiki_update\`, \`remember\`, \`forget\`, \`wiki_ingest\`, \`wiki_lint\`, and \`wiki_rebuild_index\` as write-sensitive workflows. Before using them, read \`pages/index.md\`, scan the last 20-30 entries of \`pages/_meta/log.md\`, and run \`wiki_search\` for the topic when the wiki is large or the topic is ambiguous.
135
+ **Wiki writes and restructuring**: Before writing or restructuring wiki content, invoke the \`wiki-conventions\` skill. Treat \`wiki_update\` and \`wiki_ingest_source\` as write-sensitive workflows, and use \`memory_remember\` / \`memory_recall\` for scoped agent memory. Before using wiki write tools, read \`pages/index.md\`, scan the last 20-30 entries of \`pages/_meta/log.md\`, and run \`wiki_search\` for the topic when the wiki is large or the topic is ambiguous.
134
136
 
135
137
  **Learning workflow**: When the user asks you to do something you don't have a skill for:
136
138
  1. **Search skills.sh first**: Use the find-skills skill to search for existing community skills.
@@ -154,7 +156,7 @@ Subagent proposals from \`memory_propose\` are processed automatically at end-of
154
156
  10. Expand shorthand paths: "${getExampleProjectPath()}" is the expanded form of the user's home-directory project path.
155
157
  11. Be conversational and human. You're Chapterhouse.
156
158
  12. When using skills, follow the skill's instructions precisely.
157
- 13. **Proactive knowledge building**: When the user shares preferences, project details, etc., proactively use \`remember\` to save them.
159
+ 13. **Proactive knowledge building**: When the user shares preferences, project details, etc., proactively use \`memory_remember\` for scoped agent memory or \`wiki_update\` for shared wiki knowledge.
158
160
  14. When a user mentions completing work or shipping something, proactively suggest logging it as OKR progress with \`log_okr_progress\`.
159
161
  ${selfEditBlock}${memoryBlock}`;
160
162
  }
@@ -86,7 +86,8 @@ test("orchestrator prompt omits memory_context when hot-tier XML is not provided
86
86
  });
87
87
  test("orchestrator prompt requires wiki-conventions before write-sensitive wiki work", () => {
88
88
  const message = getOrchestratorSystemMessage();
89
- assert.match(message, /wiki-conventions[\s\S]{0,500}wiki_update[\s\S]{0,200}remember[\s\S]{0,200}forget[\s\S]{0,200}wiki_ingest[\s\S]{0,200}wiki_lint[\s\S]{0,200}wiki_rebuild_index/i);
89
+ assert.match(message, /wiki-conventions[\s\S]{0,500}wiki_update[\s\S]{0,200}wiki_ingest_source[\s\S]{0,200}memory_remember[\s\S]{0,200}memory_recall/i);
90
+ assert.doesNotMatch(message, /`remember`|`recall`|`forget`|`wiki_ingest`(?!_source)|`wiki_lint`|`wiki_rebuild_index`|`wiki_fix`/);
90
91
  assert.match(message, /before writing or restructuring wiki content/i);
91
92
  });
92
93
  test("orchestrator prompt describes the wiki orientation ritual", () => {
@@ -101,4 +102,8 @@ test("orchestrator prompt explains that subagent memory proposals are processed
101
102
  assert.match(message, /processed automatically at end-of-task|processed automatically at the end of the task/i);
102
103
  assert.match(message, /do not need to manually review them mid-conversation|don't need to manually review them mid-conversation/i);
103
104
  });
105
+ test("orchestrator prompt includes the memory_reflect tool in memory guidance", () => {
106
+ const message = getOrchestratorSystemMessage();
107
+ assert.match(message, /memory_reflect/);
108
+ });
104
109
  //# sourceMappingURL=system-message.test.js.map