chapterhouse 0.13.0 → 0.14.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.
Files changed (118) hide show
  1. package/dist/api/route-coverage.test.js +1 -3
  2. package/dist/api/server.js +0 -2
  3. package/dist/api/server.test.js +0 -281
  4. package/dist/config.js +3 -85
  5. package/dist/config.test.js +5 -123
  6. package/dist/copilot/agents.js +25 -13
  7. package/dist/copilot/agents.test.js +10 -11
  8. package/dist/copilot/memory-coordinator.js +12 -227
  9. package/dist/copilot/memory-coordinator.test.js +31 -250
  10. package/dist/copilot/orchestrator.js +8 -66
  11. package/dist/copilot/orchestrator.test.js +9 -467
  12. package/dist/copilot/skills.js +15 -1
  13. package/dist/copilot/system-message.js +9 -15
  14. package/dist/copilot/system-message.test.js +9 -22
  15. package/dist/copilot/tools/index.js +3 -3
  16. package/dist/copilot/tools-deps.js +1 -1
  17. package/dist/copilot/tools.agent.test.js +6 -0
  18. package/dist/copilot/tools.inventory.test.js +1 -14
  19. package/dist/daemon.js +7 -9
  20. package/dist/memory/assets.js +33 -0
  21. package/dist/memory/domains.js +58 -0
  22. package/dist/memory/domains.test.js +47 -0
  23. package/dist/memory/git.js +66 -0
  24. package/dist/memory/git.test.js +32 -0
  25. package/dist/memory/history.js +19 -0
  26. package/dist/memory/hottier.js +32 -0
  27. package/dist/memory/hottier.test.js +33 -0
  28. package/dist/memory/index.js +5 -13
  29. package/dist/memory/instructions.js +17 -0
  30. package/dist/memory/manager.js +84 -0
  31. package/dist/memory/markdown.js +78 -0
  32. package/dist/memory/markdown.test.js +42 -0
  33. package/dist/memory/mutex.js +18 -0
  34. package/dist/memory/path-guard.js +26 -0
  35. package/dist/memory/path-guard.test.js +27 -0
  36. package/dist/memory/paths.js +12 -0
  37. package/dist/memory/reconcile.js +75 -0
  38. package/dist/memory/reconcile.test.js +50 -0
  39. package/dist/memory/scaffold.js +37 -0
  40. package/dist/memory/scaffold.test.js +52 -0
  41. package/dist/memory/tools/commit-wrapper.js +32 -0
  42. package/dist/memory/tools/domains.js +73 -0
  43. package/dist/memory/tools/domains.test.js +66 -0
  44. package/dist/memory/tools/git.js +52 -0
  45. package/dist/memory/tools/index.js +25 -0
  46. package/dist/memory/tools/read.js +101 -0
  47. package/dist/memory/tools/read.test.js +69 -0
  48. package/dist/memory/tools/search.js +103 -0
  49. package/dist/memory/tools/search.test.js +63 -0
  50. package/dist/memory/tools/sessions.js +45 -0
  51. package/dist/memory/tools/sessions.test.js +74 -0
  52. package/dist/memory/tools/shared.js +7 -0
  53. package/dist/memory/tools/write.js +116 -0
  54. package/dist/memory/tools/write.test.js +107 -0
  55. package/dist/memory/walk.js +39 -0
  56. package/dist/store/repositories/sessions.js +40 -0
  57. package/dist/wiki/consolidation.js +3 -31
  58. package/dist/wiki/consolidation.test.js +0 -19
  59. package/dist/wiki/frontmatter.js +18 -6
  60. package/dist/wiki/frontmatter.test.js +40 -0
  61. package/package.json +1 -1
  62. package/skills/system/evolve/SKILL.md +131 -0
  63. package/skills/system/foresight/SKILL.md +116 -0
  64. package/skills/system/history/SKILL.md +58 -0
  65. package/skills/system/housekeeping/SKILL.md +185 -0
  66. package/skills/system/reflect/SKILL.md +214 -0
  67. package/skills/system/scenario/SKILL.md +198 -0
  68. package/skills/system/setup/SKILL.md +113 -0
  69. package/web/dist/assets/{WikiEdit-CGRxNazp.js → WikiEdit-BTsiBfbC.js} +2 -2
  70. package/web/dist/assets/{WikiEdit-CGRxNazp.js.map → WikiEdit-BTsiBfbC.js.map} +1 -1
  71. package/web/dist/assets/{WikiGraph-eVWNhZS3.js → WikiGraph-COOZbUeH.js} +2 -2
  72. package/web/dist/assets/{WikiGraph-eVWNhZS3.js.map → WikiGraph-COOZbUeH.js.map} +1 -1
  73. package/web/dist/assets/{index-gAvLNEvJ.js → index-aCcfpaLM.js} +101 -101
  74. package/web/dist/assets/index-aCcfpaLM.js.map +1 -0
  75. package/web/dist/index.html +1 -1
  76. package/dist/api/routes/memory.js +0 -475
  77. package/dist/api/routes/memory.test.js +0 -108
  78. package/dist/copilot/tools/memory.js +0 -678
  79. package/dist/copilot/tools.memory.test.js +0 -590
  80. package/dist/memory/action-items.js +0 -100
  81. package/dist/memory/action-items.test.js +0 -83
  82. package/dist/memory/active-scope.js +0 -78
  83. package/dist/memory/active-scope.test.js +0 -80
  84. package/dist/memory/checkpoint-prompt.js +0 -71
  85. package/dist/memory/checkpoint.js +0 -274
  86. package/dist/memory/checkpoint.test.js +0 -275
  87. package/dist/memory/decisions.js +0 -54
  88. package/dist/memory/decisions.test.js +0 -92
  89. package/dist/memory/entities.js +0 -70
  90. package/dist/memory/entities.test.js +0 -65
  91. package/dist/memory/eot.js +0 -459
  92. package/dist/memory/eot.test.js +0 -949
  93. package/dist/memory/hooks.js +0 -149
  94. package/dist/memory/hooks.test.js +0 -325
  95. package/dist/memory/hot-tier.js +0 -283
  96. package/dist/memory/hot-tier.test.js +0 -275
  97. package/dist/memory/housekeeping-scheduler.js +0 -187
  98. package/dist/memory/housekeeping-scheduler.test.js +0 -236
  99. package/dist/memory/housekeeping.js +0 -497
  100. package/dist/memory/housekeeping.test.js +0 -410
  101. package/dist/memory/inbox.js +0 -83
  102. package/dist/memory/inbox.test.js +0 -178
  103. package/dist/memory/migration.js +0 -244
  104. package/dist/memory/migration.test.js +0 -108
  105. package/dist/memory/observations.js +0 -46
  106. package/dist/memory/observations.test.js +0 -86
  107. package/dist/memory/recall.js +0 -269
  108. package/dist/memory/recall.test.js +0 -265
  109. package/dist/memory/reflect.js +0 -273
  110. package/dist/memory/reflect.test.js +0 -256
  111. package/dist/memory/scope-lock.js +0 -26
  112. package/dist/memory/scope-lock.test.js +0 -118
  113. package/dist/memory/scopes.js +0 -89
  114. package/dist/memory/scopes.test.js +0 -176
  115. package/dist/memory/tiering.js +0 -223
  116. package/dist/memory/tiering.test.js +0 -323
  117. package/dist/memory/types.js +0 -2
  118. package/web/dist/assets/index-gAvLNEvJ.js.map +0 -1
@@ -1,257 +1,38 @@
1
1
  import assert from "node:assert/strict";
2
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
2
4
  import test from "node:test";
3
- function makeScope(id, slug, title = slug, description = `${slug} scope`) {
4
- return {
5
- id,
6
- slug,
7
- title,
8
- description,
9
- keywords: [],
10
- active: true,
11
- createdAt: "2026-05-14T00:00:00.000Z",
12
- updatedAt: "2026-05-14T00:00:00.000Z",
13
- };
14
- }
15
- async function loadMemoryCoordinatorModule(t, overrides = {}) {
16
- const chapterhouseScope = makeScope(1, "chapterhouse", "Chapterhouse", "Core Chapterhouse work.");
17
- const wikiScope = makeScope(2, "wiki", "Wiki", "Wiki work.");
18
- const state = {
19
- config: {
20
- copilotModel: "claude-sonnet-4.6",
21
- memoryInjectEnabled: true,
22
- memoryCheckpointEnabled: true,
23
- memoryCheckpointTurns: 2,
24
- memoryCheckpointOnScopeChange: true,
25
- memoryCheckpointMinTurnsForScopeFire: 2,
26
- memoryHousekeepingEnabled: true,
27
- memoryHousekeepingTurns: 2,
28
- },
29
- activeScope: chapterhouseScope,
30
- scopes: new Map([
31
- [chapterhouseScope.slug, chapterhouseScope],
32
- [wikiScope.slug, wikiScope],
33
- ]),
34
- sessionScopes: new Map([["default", chapterhouseScope]]),
35
- hotTierXmlByScope: new Map([[chapterhouseScope.slug, "<memory_context scope=\"chapterhouse\">\n <decision>Ship it</decision>\n</memory_context>\n"]]),
36
- checkpointRuns: [],
37
- checkpointInFlight: false,
38
- checkpointTickCalls: 0,
39
- checkpointMarkFiredCalls: 0,
40
- checkpointMarkScopeChangeFireCalls: 0,
41
- checkpointResetCalls: 0,
42
- housekeepingRuns: [],
43
- housekeepingInFlight: false,
44
- eotCalls: [],
45
- ...overrides,
46
- };
47
- t.mock.module("../config.js", {
48
- namedExports: {
49
- config: state.config,
50
- },
51
- });
52
- t.mock.module("../memory/active-scope.js", {
53
- namedExports: {
54
- getActiveScope: () => state.activeScope,
55
- },
56
- });
57
- t.mock.module("../memory/scopes.js", {
58
- namedExports: {
59
- getScope: (slug) => state.scopes.get(slug) ?? null,
60
- },
61
- });
62
- t.mock.module("../memory/hot-tier.js", {
63
- namedExports: {
64
- getHotTierEntries: (scopeId) => {
65
- const scope = Array.from(state.scopes.values()).find((candidate) => candidate.id === scopeId) ?? null;
66
- return { scope, entities: [], observations: [], decisions: [], actionItems: [] };
67
- },
68
- renderHotTierXML: (entries) => entries.scope ? (state.hotTierXmlByScope.get(entries.scope.slug) ?? "") : "",
69
- renderHotTierForActiveScope: () => state.activeScope ? (state.hotTierXmlByScope.get(state.activeScope.slug) ?? "") : "",
70
- },
71
- });
72
- t.mock.module("../memory/checkpoint.js", {
73
- namedExports: {
74
- CheckpointTracker: class {
75
- turns = 0;
76
- tickOrchestratorTurn() {
77
- state.checkpointTickCalls++;
78
- this.turns++;
79
- }
80
- shouldFire() {
81
- return this.turns >= state.config.memoryCheckpointTurns;
82
- }
83
- turnsSinceLastFire() {
84
- return this.turns;
85
- }
86
- markFired() {
87
- state.checkpointMarkFiredCalls++;
88
- this.turns = 0;
89
- }
90
- markScopeChangeFire() {
91
- state.checkpointMarkScopeChangeFireCalls++;
92
- this.turns = 0;
93
- }
94
- reset() {
95
- state.checkpointResetCalls++;
96
- this.turns = 0;
97
- }
98
- },
99
- isCheckpointInFlight: () => state.checkpointInFlight,
100
- runCheckpointExtraction: async (input) => {
101
- state.checkpointRuns.push(input);
102
- return { written: 0, skipped: 0, errors: [] };
103
- },
104
- },
105
- });
106
- t.mock.module("../memory/housekeeping.js", {
107
- namedExports: {
108
- isHousekeepingInFlight: () => state.housekeepingInFlight,
109
- runHousekeeping: async (input) => {
110
- state.housekeepingRuns.push(input);
111
- return { scopeIds: input.scopeIds ?? [], summaries: [], totalExamined: 0, totalModified: 0, durationMs: 0 };
112
- },
113
- },
114
- });
115
- t.mock.module("../memory/eot.js", {
116
- namedExports: {
117
- runEndOfTaskMemoryHook: async (input) => {
118
- state.eotCalls.push(input);
119
- return {
120
- task_id: String(input.taskId ?? "task"),
121
- proposals_total: 0,
122
- accepted: 0,
123
- rejected: 0,
124
- implicit_extracted: 0,
125
- auto_accept: true,
126
- };
127
- },
128
- },
129
- });
130
- const module = await import(new URL(`./memory-coordinator.js?case=${Date.now()}-${Math.random()}`, import.meta.url).href);
131
- return { module, state };
132
- }
133
- test("buildHotTierContext and buildPerTurnHooks inject trimmed scoped hot-tier memory", async (t) => {
134
- const { module, state } = await loadMemoryCoordinatorModule(t);
135
- const client = { name: "mock-client" };
136
- const coordinator = new module.MemoryCoordinator({
137
- getCopilotClient: () => client,
138
- config: state.config,
139
- resolveScopeForSession: (sessionKey) => state.sessionScopes.get(sessionKey) ?? null,
140
- });
141
- const hotTierContext = await coordinator.buildHotTierContext("default");
142
- const hooks = coordinator.buildPerTurnHooks("default");
143
- const hookResult = await hooks?.onUserPromptSubmitted?.();
144
- assert.equal(hotTierContext, "<memory_context scope=\"chapterhouse\">\n <decision>Ship it</decision>\n</memory_context>");
145
- assert.deepEqual(hookResult, {
146
- additionalContext: "<memory_context scope=\"chapterhouse\">\n <decision>Ship it</decision>\n</memory_context>",
147
- });
5
+ const sandbox = join(process.cwd(), ".test-work", `memory-coordinator-${process.pid}`);
6
+ process.env.CHAPTERHOUSE_HOME = sandbox;
7
+ import { MemoryCoordinator } from "./memory-coordinator.js";
8
+ import { gitAvailable } from "../memory/git.js";
9
+ import { getMemoryManager, resetMemoryManagerForTests } from "../memory/index.js";
10
+ test.beforeEach(() => {
11
+ rmSync(sandbox, { recursive: true, force: true });
12
+ mkdirSync(sandbox, { recursive: true });
13
+ resetMemoryManagerForTests();
148
14
  });
149
- test("onTurnComplete schedules checkpoint and housekeeping at the configured turn boundary", async (t) => {
150
- const { module, state } = await loadMemoryCoordinatorModule(t);
151
- const coordinator = new module.MemoryCoordinator({
152
- getCopilotClient: () => ({ name: "mock-client" }),
153
- config: state.config,
154
- resolveScopeForSession: (sessionKey) => state.sessionScopes.get(sessionKey) ?? null,
155
- });
156
- await coordinator.onTurnComplete("default", "First prompt", "First response", "web");
157
- assert.equal(state.checkpointRuns.length, 0);
158
- assert.equal(state.housekeepingRuns.length, 0);
159
- await coordinator.onTurnComplete("default", "Second prompt", "Second response", "web");
160
- assert.equal(state.checkpointMarkFiredCalls, 1);
161
- assert.equal(state.checkpointRuns.length, 1);
162
- assert.equal(state.housekeepingRuns.length, 1);
163
- assert.deepEqual(state.housekeepingRuns[0]?.scopeIds, [1]);
164
- assert.equal((state.checkpointRuns[0]?.activeScope).slug, "chapterhouse");
165
- assert.deepEqual(state.checkpointRuns[0]?.turns, [
166
- { user: "First prompt", assistant: "First response" },
167
- { user: "Second prompt", assistant: "Second response" },
168
- ]);
169
- await coordinator.onTurnComplete("default", "Background prompt", "Background response", "background");
170
- assert.equal(state.checkpointRuns.length, 1);
171
- assert.equal(state.housekeepingRuns.length, 1);
15
+ test.after(() => rmSync(sandbox, { recursive: true, force: true }));
16
+ test("buildPerTurnHooks returns an onUserPromptSubmitted hook", () => {
17
+ const hooks = new MemoryCoordinator().buildPerTurnHooks();
18
+ assert.ok(hooks);
19
+ assert.equal(typeof hooks.onUserPromptSubmitted, "function");
172
20
  });
173
- test("onScopeChange triggers checkpoint extraction for the previous scope", async (t) => {
174
- const { module, state } = await loadMemoryCoordinatorModule(t, {
175
- config: {
176
- copilotModel: "claude-sonnet-4.6",
177
- memoryInjectEnabled: true,
178
- memoryCheckpointEnabled: true,
179
- memoryCheckpointTurns: 5,
180
- memoryCheckpointOnScopeChange: true,
181
- memoryCheckpointMinTurnsForScopeFire: 2,
182
- memoryHousekeepingEnabled: true,
183
- memoryHousekeepingTurns: 2,
184
- },
185
- });
186
- const coordinator = new module.MemoryCoordinator({
187
- getCopilotClient: () => ({ name: "mock-client" }),
188
- config: state.config,
189
- resolveScopeForSession: (sessionKey) => state.sessionScopes.get(sessionKey) ?? null,
190
- });
191
- await coordinator.onTurnComplete("default", "Turn one", "Reply one", "web");
192
- await coordinator.onTurnComplete("default", "Turn two", "Reply two", "web");
193
- state.checkpointRuns.length = 0;
194
- await coordinator.onScopeChange("default", "chapterhouse", "wiki");
195
- assert.equal(state.checkpointMarkScopeChangeFireCalls, 1);
196
- assert.equal(state.checkpointRuns.length, 1);
197
- assert.equal((state.checkpointRuns[0]?.activeScope).slug, "chapterhouse");
198
- assert.equal(state.checkpointRuns[0]?.trigger, "scope_change");
199
- assert.deepEqual(state.checkpointRuns[0]?.scopeChangeContext, { from: "chapterhouse", to: "wiki" });
21
+ test("the hook returns undefined when memory has no hot-tier content", async () => {
22
+ const hooks = new MemoryCoordinator().buildPerTurnHooks();
23
+ const result = await hooks.onUserPromptSubmitted();
24
+ assert.equal(result, undefined);
200
25
  });
201
- test("onAgentTaskComplete runs the end-of-task memory hook with the mock client", async (t) => {
202
- const { module, state } = await loadMemoryCoordinatorModule(t);
203
- const client = { name: "mock-client" };
204
- const coordinator = new module.MemoryCoordinator({
205
- getCopilotClient: () => client,
206
- config: state.config,
207
- resolveScopeForSession: (sessionKey) => state.sessionScopes.get(sessionKey) ?? null,
208
- });
209
- await coordinator.onAgentTaskComplete("task-42", "Final delegated result");
210
- assert.equal(state.eotCalls.length, 1);
211
- assert.equal(state.eotCalls[0]?.taskId, "task-42");
212
- assert.equal(state.eotCalls[0]?.finalResult, "Final delegated result");
213
- assert.equal(state.eotCalls[0]?.copilotClient, client);
214
- });
215
- test("onAgentTaskComplete is a no-op for a duplicate task ID (double-fire guard)", async (t) => {
216
- const { module, state } = await loadMemoryCoordinatorModule(t);
217
- const client = { name: "mock-client" };
218
- const coordinator = new module.MemoryCoordinator({
219
- getCopilotClient: () => client,
220
- config: state.config,
221
- resolveScopeForSession: (sessionKey) => state.sessionScopes.get(sessionKey) ?? null,
222
- });
223
- await coordinator.onAgentTaskComplete("task-99", "result one");
224
- await coordinator.onAgentTaskComplete("task-99", "result two");
225
- assert.equal(state.eotCalls.length, 1);
226
- assert.equal(state.eotCalls[0]?.taskId, "task-99");
227
- assert.equal(state.eotCalls[0]?.finalResult, "result one");
228
- });
229
- test("reset clears turn buffers and tracker state for a session", async (t) => {
230
- const { module, state } = await loadMemoryCoordinatorModule(t);
231
- const coordinator = new module.MemoryCoordinator({
232
- getCopilotClient: () => ({ name: "mock-client" }),
233
- config: state.config,
234
- resolveScopeForSession: (sessionKey) => state.sessionScopes.get(sessionKey) ?? null,
235
- });
236
- await coordinator.onTurnComplete("default", "Turn one", "Reply one", "web");
237
- coordinator.reset("default");
238
- await coordinator.onTurnComplete("default", "Turn two", "Reply two", "web");
239
- await coordinator.onScopeChange("default", "chapterhouse", "wiki");
240
- assert.equal(state.checkpointResetCalls, 1);
241
- assert.equal(state.checkpointRuns.length, 0);
242
- assert.equal(state.housekeepingRuns.length, 0);
243
- });
244
- test("shutdown clears session state across all tracked maps", async (t) => {
245
- const { module, state } = await loadMemoryCoordinatorModule(t);
246
- const coordinator = new module.MemoryCoordinator({
247
- getCopilotClient: () => ({ name: "mock-client" }),
248
- config: state.config,
249
- resolveScopeForSession: (sessionKey) => state.sessionScopes.get(sessionKey) ?? null,
250
- });
251
- await coordinator.onTurnComplete("default", "Turn one", "Reply one", "web");
252
- coordinator.shutdown();
253
- await coordinator.onTurnComplete("default", "Turn two", "Reply two", "web");
254
- assert.equal(state.checkpointRuns.length, 0);
255
- assert.equal(state.housekeepingRuns.length, 0);
26
+ test("the hook injects the hot-tier block once memory is scaffolded", async () => {
27
+ if (!gitAvailable())
28
+ return;
29
+ const manager = getMemoryManager();
30
+ await manager.ensureReady();
31
+ writeFileSync(join(manager.paths.memoryRoot, "hot-memory.md"), "<!-- L0: hot -->\ntop of mind");
32
+ const hooks = new MemoryCoordinator().buildPerTurnHooks();
33
+ const result = await hooks.onUserPromptSubmitted();
34
+ assert.ok(result && typeof result === "object");
35
+ assert.match(result.additionalContext, /top of mind/);
36
+ assert.match(result.additionalContext, /<chapterhouse-memory>/);
256
37
  });
257
38
  //# sourceMappingURL=memory-coordinator.test.js.map
@@ -3,8 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { approveAll } from "@github/copilot-sdk";
4
4
  import { createTools } from "./tools.js";
5
5
  import { getOrchestratorSystemMessage } from "./system-message.js";
6
- import { getActiveScope, withActiveScope } from "../memory/active-scope.js";
7
- import { getScope } from "../memory/scopes.js";
6
+ import { getMemoryManager } from "../memory/index.js";
8
7
  import { MemoryCoordinator } from "./memory-coordinator.js";
9
8
  import { CHAPTERHOUSE_VERSION } from "../version.js";
10
9
  import { config, DEFAULT_MODEL } from "../config.js";
@@ -39,18 +38,6 @@ const AGENT_REPLY_CHUNK_THRESHOLD = 8 * 1024;
39
38
  const ORCHESTRATOR_SESSION_KEY = "orchestrator_session_id";
40
39
  const LAST_AUTHENTICATED_USER_KEY = "last_authenticated_user";
41
40
  let taskEventLogCleanup;
42
- function getWikiSummary() {
43
- try {
44
- const db = getDb();
45
- const pages = db.prepare(`SELECT title, summary, last_updated FROM wiki_pages ORDER BY last_updated DESC LIMIT 50`).all();
46
- if (pages.length === 0)
47
- return "";
48
- return pages.map((page) => `${page.title}: ${page.summary || ""}`.trim()).join("\n");
49
- }
50
- catch {
51
- return "";
52
- }
53
- }
54
41
  let logMessage = () => { };
55
42
  export function setMessageLogger(fn) {
56
43
  logMessage = fn;
@@ -84,14 +71,6 @@ let memoryCoordinator;
84
71
  export function getLastRouteResult() {
85
72
  return lastRouteResult;
86
73
  }
87
- export function resetCheckpointSessionState(sessionKey) {
88
- memoryCoordinator?.reset(sessionKey);
89
- }
90
- export function maybeScheduleScopeChangeCheckpoint(sessionKey, previousScope, nextScope) {
91
- void memoryCoordinator?.onScopeChange(sessionKey, previousScope?.slug ?? "", nextScope?.slug ?? "").catch((error) => {
92
- log.error({ err: error, sessionKey }, "memory.checkpoint.error");
93
- });
94
- }
95
74
  export function subscribeTaskEvents(taskId, listener) {
96
75
  return agentEventBus.subscribe("session:tool_call", (event) => {
97
76
  if (event.sessionId !== taskId)
@@ -176,6 +155,7 @@ export function getCurrentAuthorizationHeader() {
176
155
  function getSessionConfig() {
177
156
  const baseTools = createTools({
178
157
  client: copilotClient,
158
+ memoryManager: getMemoryManager(),
179
159
  onAgentTaskComplete: feedAgentResult,
180
160
  });
181
161
  const tools = agentsModule.bindToolsToAgent?.("chapterhouse", baseTools) ?? baseTools;
@@ -200,18 +180,9 @@ function getPersistentAgentForSessionKey(sessionKey) {
200
180
  const agent = getAgent(slug);
201
181
  return agent?.persistent ? agent : undefined;
202
182
  }
203
- function getMemoryScopeForSession(sessionKey) {
204
- const persistentAgent = getPersistentAgentForSessionKey(sessionKey);
205
- if (persistentAgent?.scope) {
206
- return getScope(persistentAgent.scope) ?? null;
207
- }
208
- return getActiveScope();
209
- }
210
- function getSystemMessageOptions(memorySummary, hotTierXml) {
183
+ function getSystemMessageOptions() {
211
184
  return {
212
185
  selfEditEnabled: config.selfEditEnabled,
213
- memorySummary: memorySummary || undefined,
214
- hotTierXml,
215
186
  agentRoster: buildAgentRoster(),
216
187
  userContext: currentUserContext,
217
188
  };
@@ -242,9 +213,6 @@ function recordLastAuthenticatedUser(source) {
242
213
  }
243
214
  }
244
215
  export function feedAgentResult(taskId, agentSlug, result) {
245
- void memoryCoordinator?.onAgentTaskComplete(taskId, result).catch((error) => {
246
- log.error({ err: error, taskId }, "memory.eot.error");
247
- });
248
216
  const sessionKey = getTaskSessionKey(taskId) || "default";
249
217
  const agentTurnId = randomUUID();
250
218
  const agentDisplayName = getAgentRegistry().find((agent) => agent.slug === agentSlug)?.name ?? agentSlug;
@@ -346,7 +314,7 @@ async function createOrResumeSession(sessionKey, projectRoot) {
346
314
  backgroundCompactionThreshold: 0.80,
347
315
  bufferExhaustionThreshold: 0.95,
348
316
  };
349
- const memoryHooks = memoryCoordinator?.buildPerTurnHooks(sessionKey);
317
+ const memoryHooks = memoryCoordinator?.buildPerTurnHooks();
350
318
  let model = config.copilotModel;
351
319
  let systemMessageContent;
352
320
  let sessionMode = isProjectSession ? "project" : "default";
@@ -354,23 +322,20 @@ async function createOrResumeSession(sessionKey, projectRoot) {
354
322
  model = persistentAgent.model === "auto" ? config.copilotModel : persistentAgent.model;
355
323
  tools = agentsModule.bindToolsToAgent(persistentAgent.slug, filterToolsForAgent(persistentAgent, createTools({
356
324
  client: copilotClient,
325
+ memoryManager: getMemoryManager(),
357
326
  onAgentTaskComplete: feedAgentResult,
358
327
  })));
359
328
  mcpServers = filterMcpServersForAgent(persistentAgent, mcpServers);
360
- const scopedHotTier = (await memoryCoordinator?.buildHotTierContext(sessionKey)) || undefined;
361
- const channelNote = `You are in your persistent Chapterhouse channel (${sessionKey}). Your memory scope is ${persistentAgent.scope}.`;
329
+ const channelNote = `You are in your persistent Chapterhouse channel (${sessionKey}).`;
362
330
  systemMessageContent = [
363
331
  composeAgentSystemMessage(persistentAgent),
364
332
  channelNote,
365
- scopedHotTier,
366
333
  ].filter(Boolean).join("\n\n");
367
334
  sessionMode = "agent";
368
335
  }
369
336
  else {
370
- const memorySummary = getWikiSummary();
371
- const hotTierXml = (await memoryCoordinator?.buildHotTierContext(sessionKey)) || undefined;
372
337
  systemMessageContent = getOrchestratorSystemMessage({
373
- ...getSystemMessageOptions(memorySummary, hotTierXml),
338
+ ...getSystemMessageOptions(),
374
339
  version: CHAPTERHOUSE_VERSION,
375
340
  });
376
341
  }
@@ -393,7 +358,6 @@ async function createOrResumeSession(sessionKey, projectRoot) {
393
358
  infiniteSessions,
394
359
  });
395
360
  log.info({ sessionKey }, "Session resumed successfully");
396
- memoryCoordinator?.reset(sessionKey);
397
361
  upsertCopilotSession(sessionKey, sessionMode, session.sessionId, projectRoot, model);
398
362
  const mgr = registry?.get(sessionKey);
399
363
  if (mgr)
@@ -422,7 +386,6 @@ async function createOrResumeSession(sessionKey, projectRoot) {
422
386
  infiniteSessions,
423
387
  });
424
388
  log.info({ sessionKey, sessionId: session.sessionId.slice(0, 8) }, "Session created");
425
- memoryCoordinator?.reset(sessionKey);
426
389
  upsertCopilotSession(sessionKey, sessionMode, session.sessionId, projectRoot, model);
427
390
  if (sessionKey === "default")
428
391
  setState(ORCHESTRATOR_SESSION_KEY, session.sessionId);
@@ -433,12 +396,7 @@ async function createOrResumeSession(sessionKey, projectRoot) {
433
396
  }
434
397
  export async function initOrchestrator(client) {
435
398
  copilotClient = client;
436
- memoryCoordinator?.shutdown();
437
- memoryCoordinator = new MemoryCoordinator({
438
- getCopilotClient: () => copilotClient,
439
- config,
440
- resolveScopeForSession: getMemoryScopeForSession,
441
- });
399
+ memoryCoordinator = new MemoryCoordinator();
442
400
  // Initialize per-task ring buffer — subscribes to agentEventBus for session:tool_call events.
443
401
  taskEventLogCleanup?.();
444
402
  taskEventLogCleanup = initTaskEventLog();
@@ -738,11 +696,6 @@ async function executeOnSession(manager, item) {
738
696
  log.warn({ taskId, length: finalResult.length, limit: 10_000 }, "subagent result truncated before persistence");
739
697
  }
740
698
  db.prepare(`UPDATE agent_tasks SET status = 'completed', result = ?, completed_at = CURRENT_TIMESTAMP WHERE task_id = ?`).run(finalResult?.slice(0, 10000) ?? null, taskId);
741
- if (finalResult) {
742
- void memoryCoordinator?.onAgentTaskComplete(taskId, finalResult).catch((error) => {
743
- log.error({ err: error, taskId }, "memory.eot.error");
744
- });
745
- }
746
699
  const taskRow = db.prepare(`SELECT agent_slug FROM agent_tasks WHERE task_id = ?`).get(taskId);
747
700
  void agentEventBus.emit({
748
701
  type: "session:destroyed",
@@ -902,13 +855,9 @@ async function executeOnSession(manager, item) {
902
855
  unsubTurnReasoning();
903
856
  }
904
857
  });
905
- const persistentAgent = getPersistentAgentForSessionKey(sessionKey);
906
858
  const scopedRunTurn = () => item.taskId
907
859
  ? withToolTaskContext(item.taskId, runTurn)
908
860
  : runTurn();
909
- if (persistentAgent?.scope) {
910
- return withActiveScope(persistentAgent.scope, scopedRunTurn);
911
- }
912
861
  return scopedRunTurn();
913
862
  }
914
863
  /**
@@ -1087,9 +1036,6 @@ export function sendToOrchestrator(prompt, source, callback, attachments, onActi
1087
1036
  logConversation("assistant", finalContent, logSource, sessionKey, { turnId });
1088
1037
  }
1089
1038
  catch { /* best-effort */ }
1090
- void memoryCoordinator?.onTurnComplete(sessionKey, prompt, finalContent, source.type).catch((error) => {
1091
- log.error({ err: error, sessionKey }, "memory.turn_complete.error");
1092
- });
1093
1039
  if (copilotClient) {
1094
1040
  maybeWriteEpisode(copilotClient).catch((err) => {
1095
1041
  log.error({ err: err instanceof Error ? err.message : err }, "Episode write failed (non-fatal)");
@@ -1194,9 +1140,6 @@ export async function interruptCurrentTurn(sessionKey, newPrompt, source, callba
1194
1140
  logConversation("assistant", finalContent, sourceLabel, sessionKey, { turnId });
1195
1141
  }
1196
1142
  catch { /* best-effort */ }
1197
- void memoryCoordinator?.onTurnComplete(sessionKey, newPrompt, finalContent, source.type).catch((error) => {
1198
- log.error({ err: error, sessionKey }, "memory.turn_complete.error");
1199
- });
1200
1143
  if (copilotClient) {
1201
1144
  maybeWriteEpisode(copilotClient).catch((err) => {
1202
1145
  log.error({ err: err instanceof Error ? err.message : err }, "Episode write failed (non-fatal)");
@@ -1373,7 +1316,6 @@ export function getAgentInfo() {
1373
1316
  /** Clean up on shutdown/restart. */
1374
1317
  export async function shutdownAgents() {
1375
1318
  stopClassifier();
1376
- memoryCoordinator?.shutdown();
1377
1319
  memoryCoordinator = undefined;
1378
1320
  if (!registry) {
1379
1321
  await clearActiveTasks();