chapterhouse 0.7.0 → 0.8.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/agents/korg.agent.md +65 -0
- package/dist/api/korg.js +34 -0
- package/dist/api/korg.test.js +42 -0
- package/dist/api/server.js +238 -2
- package/dist/api/server.test.js +199 -0
- package/dist/config.js +28 -0
- package/dist/config.test.js +20 -0
- package/dist/copilot/agents.js +3 -4
- package/dist/copilot/agents.test.js +12 -1
- package/dist/copilot/orchestrator.js +12 -1
- package/dist/copilot/orchestrator.test.js +3 -7
- package/dist/copilot/system-message.js +11 -10
- package/dist/copilot/system-message.test.js +6 -1
- package/dist/copilot/tools.js +184 -376
- package/dist/copilot/tools.memory.test.js +32 -0
- package/dist/copilot/tools.wiki.test.js +53 -59
- package/dist/daemon.js +9 -0
- package/dist/memory/decisions.js +6 -5
- package/dist/memory/entities.js +20 -9
- package/dist/memory/hooks.js +151 -0
- package/dist/memory/hooks.test.js +325 -0
- package/dist/memory/hot-tier.js +37 -0
- package/dist/memory/hot-tier.test.js +30 -0
- package/dist/memory/housekeeping-scheduler.js +35 -0
- package/dist/memory/housekeeping-scheduler.test.js +50 -0
- package/dist/memory/inbox.js +10 -0
- package/dist/memory/index.js +3 -1
- package/dist/memory/migration.js +244 -0
- package/dist/memory/migration.test.js +100 -0
- package/dist/memory/reflect.js +273 -0
- package/dist/memory/reflect.test.js +254 -0
- package/dist/store/db.js +119 -4
- package/dist/store/db.test.js +19 -1
- package/dist/test/setup-env.js +1 -0
- package/dist/wiki/consolidation.js +641 -0
- package/dist/wiki/consolidation.test.js +140 -0
- package/dist/wiki/frontmatter.js +48 -0
- package/dist/wiki/frontmatter.test.js +42 -0
- package/dist/wiki/index-manager.js +246 -330
- package/dist/wiki/index-manager.test.js +138 -145
- package/dist/wiki/ingest.js +347 -0
- package/dist/wiki/ingest.test.js +111 -0
- package/dist/wiki/links.js +151 -0
- package/dist/wiki/links.test.js +176 -0
- package/dist/wiki/migrate-topics.test.js +16 -6
- package/dist/wiki/scheduler.js +118 -0
- package/dist/wiki/scheduler.test.js +64 -0
- package/dist/wiki/timeline.js +51 -0
- package/dist/wiki/timeline.test.js +65 -0
- package/dist/wiki/topic-structure.js +1 -1
- package/package.json +1 -1
- package/skills/pkb-ideas/SKILL.md +78 -0
- package/skills/pkb-ideas/_meta.json +4 -0
- package/skills/pkb-org/SKILL.md +82 -0
- package/skills/pkb-org/_meta.json +4 -0
- package/skills/pkb-people/SKILL.md +74 -0
- package/skills/pkb-people/_meta.json +4 -0
- package/skills/pkb-research/SKILL.md +83 -0
- package/skills/pkb-research/_meta.json +4 -0
- package/skills/pkb-source/SKILL.md +38 -0
- package/skills/pkb-source/_meta.json +4 -0
- package/skills/wiki-conventions/SKILL.md +5 -5
- package/web/dist/assets/{index-DuKYxMIR.css → index-5kz9aRU9.css} +1 -1
- package/web/dist/assets/{index-DytB69KC.js → index-BbX9RKf3.js} +91 -89
- package/web/dist/assets/index-BbX9RKf3.js.map +1 -0
- package/web/dist/index.html +2 -2
- package/dist/wiki/context.js +0 -138
- package/dist/wiki/fix.js +0 -335
- package/dist/wiki/fix.test.js +0 -350
- package/dist/wiki/lint.js +0 -451
- package/dist/wiki/lint.test.js +0 -329
- 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() {
|
package/dist/config.test.js
CHANGED
|
@@ -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");
|
package/dist/copilot/agents.js
CHANGED
|
@@ -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
|
|
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", "
|
|
344
|
-
"wiki_ingest", "wiki_lint", "wiki_rebuild_index",
|
|
343
|
+
"wiki_search", "wiki_read", "wiki_update", "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,
|
|
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 `
|
|
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,23 @@ 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
|
-
- \`
|
|
119
|
-
- \`
|
|
120
|
-
- \`
|
|
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.
|
|
121
122
|
|
|
122
123
|
Subagent proposals from \`memory_propose\` are processed automatically at end-of-task, so you do not need to manually review them mid-conversation.
|
|
123
124
|
|
|
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\`
|
|
125
|
+
**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
126
|
|
|
126
127
|
**Wiki structure** — the wiki enforces a topic layout, so put things in the right place:
|
|
127
128
|
- **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
129
|
- **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\`.
|
|
130
|
-
-
|
|
131
|
-
-
|
|
130
|
+
- **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.
|
|
131
|
+
- 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.
|
|
132
|
+
- Source material that should be preserved before synthesis belongs in \`wiki_ingest_source\`.
|
|
132
133
|
|
|
133
|
-
**Wiki writes and restructuring**: Before writing or restructuring wiki content, invoke the \`wiki-conventions\` skill. Treat \`wiki_update
|
|
134
|
+
**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
135
|
|
|
135
136
|
**Learning workflow**: When the user asks you to do something you don't have a skill for:
|
|
136
137
|
1. **Search skills.sh first**: Use the find-skills skill to search for existing community skills.
|
|
@@ -154,7 +155,7 @@ Subagent proposals from \`memory_propose\` are processed automatically at end-of
|
|
|
154
155
|
10. Expand shorthand paths: "${getExampleProjectPath()}" is the expanded form of the user's home-directory project path.
|
|
155
156
|
11. Be conversational and human. You're Chapterhouse.
|
|
156
157
|
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 \`
|
|
158
|
+
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
159
|
14. When a user mentions completing work or shipping something, proactively suggest logging it as OKR progress with \`log_okr_progress\`.
|
|
159
160
|
${selfEditBlock}${memoryBlock}`;
|
|
160
161
|
}
|
|
@@ -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}
|
|
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
|