chapterhouse 0.3.26 → 0.4.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/dist/api/server.js +12 -0
- package/dist/api/server.test.js +39 -0
- package/dist/config.js +70 -0
- package/dist/config.test.js +109 -0
- package/dist/copilot/agents.js +27 -4
- package/dist/copilot/agents.test.js +7 -0
- package/dist/copilot/oneshot.js +54 -0
- package/dist/copilot/orchestrator.js +227 -3
- package/dist/copilot/orchestrator.test.js +372 -0
- package/dist/copilot/system-message.js +4 -0
- package/dist/copilot/system-message.test.js +24 -0
- package/dist/copilot/tools.agent.test.js +23 -0
- package/dist/copilot/tools.js +350 -4
- package/dist/copilot/tools.memory.test.js +248 -0
- package/dist/copilot/turn-event-log-env.test.js +19 -0
- package/dist/copilot/turn-event-log.js +22 -23
- package/dist/copilot/turn-event-log.test.js +61 -2
- package/dist/memory/active-scope.js +69 -0
- package/dist/memory/active-scope.test.js +76 -0
- package/dist/memory/checkpoint-prompt.js +71 -0
- package/dist/memory/checkpoint.js +257 -0
- package/dist/memory/checkpoint.test.js +255 -0
- package/dist/memory/decisions.js +53 -0
- package/dist/memory/decisions.test.js +92 -0
- package/dist/memory/entities.js +59 -0
- package/dist/memory/entities.test.js +65 -0
- package/dist/memory/eot.js +219 -0
- package/dist/memory/eot.test.js +263 -0
- package/dist/memory/hot-tier.js +187 -0
- package/dist/memory/hot-tier.test.js +197 -0
- package/dist/memory/housekeeping.js +352 -0
- package/dist/memory/housekeeping.test.js +280 -0
- package/dist/memory/inbox.js +73 -0
- package/dist/memory/index.js +11 -0
- package/dist/memory/observations.js +46 -0
- package/dist/memory/observations.test.js +86 -0
- package/dist/memory/recall.js +197 -0
- package/dist/memory/recall.test.js +196 -0
- package/dist/memory/scopes.js +89 -0
- package/dist/memory/scopes.test.js +201 -0
- package/dist/memory/tiering.js +193 -0
- package/dist/memory/types.js +2 -0
- package/dist/paths.js +7 -1
- package/dist/store/db.js +412 -8
- package/dist/store/db.test.js +83 -0
- package/dist/test/setup-env.js +16 -0
- package/dist/test/setup-env.test.js +4 -0
- package/package.json +1 -1
- package/web/dist/assets/{index-BRPJa1DK.js → index-DmYLALt0.js} +70 -70
- package/web/dist/assets/index-DmYLALt0.js.map +1 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-BRPJa1DK.js.map +0 -1
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
import { getDb } from "../store/db.js";
|
|
3
|
+
import { childLogger } from "../util/logger.js";
|
|
4
|
+
const log = childLogger("memory.tiering");
|
|
5
|
+
const TABLES = {
|
|
6
|
+
observation: "mem_observations",
|
|
7
|
+
decision: "mem_decisions",
|
|
8
|
+
entity: "mem_entities",
|
|
9
|
+
};
|
|
10
|
+
function dbTable(table) {
|
|
11
|
+
return TABLES[table];
|
|
12
|
+
}
|
|
13
|
+
function passSummary(pass, examined = 0, modified = 0, errors = []) {
|
|
14
|
+
return { pass, examined, modified, errors };
|
|
15
|
+
}
|
|
16
|
+
function isRecent(value, days) {
|
|
17
|
+
if (!value) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const parsed = new Date(value.includes("T") ? value : value.replace(" ", "T"));
|
|
21
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return parsed.getTime() >= Date.now() - days * 24 * 60 * 60 * 1000;
|
|
25
|
+
}
|
|
26
|
+
export function inferTierFromSignals(row) {
|
|
27
|
+
if (row.archived_at || row.superseded_by) {
|
|
28
|
+
return "cold";
|
|
29
|
+
}
|
|
30
|
+
if (row.tier === "glacier") {
|
|
31
|
+
return "cold";
|
|
32
|
+
}
|
|
33
|
+
if (row.confidence !== undefined && row.confidence !== null && row.confidence > 0.7) {
|
|
34
|
+
const timestamp = row.created_at ?? row.decided_at ?? row.updated_at;
|
|
35
|
+
if (isRecent(timestamp, 30)) {
|
|
36
|
+
return "hot";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return row.tier === "hot" || row.tier === "cold" ? row.tier : "warm";
|
|
40
|
+
}
|
|
41
|
+
function setTier(table, id, tier, reason, manual) {
|
|
42
|
+
const result = getDb().prepare(`
|
|
43
|
+
UPDATE ${dbTable(table)}
|
|
44
|
+
SET tier = ?, tier_reason = ?, tier_pinned_at = ${manual ? "CURRENT_TIMESTAMP" : "tier_pinned_at"}
|
|
45
|
+
WHERE id = ?
|
|
46
|
+
`).run(tier, reason, id);
|
|
47
|
+
if (result.changes === 0) {
|
|
48
|
+
throw new Error(`Unknown memory ${table} id '${id}'.`);
|
|
49
|
+
}
|
|
50
|
+
log.info({ table, id, tier, reason, manual }, "memory.tier.set");
|
|
51
|
+
}
|
|
52
|
+
export function promoteToHot(table, id, reason) {
|
|
53
|
+
setTier(table, id, "hot", reason, true);
|
|
54
|
+
}
|
|
55
|
+
export function demoteToWarm(table, id, reason) {
|
|
56
|
+
setTier(table, id, "warm", reason, true);
|
|
57
|
+
}
|
|
58
|
+
export function demoteToCold(table, id, reason) {
|
|
59
|
+
setTier(table, id, "cold", reason, true);
|
|
60
|
+
}
|
|
61
|
+
function updateTier(sql, params) {
|
|
62
|
+
return getDb().prepare(sql).run(...params).changes;
|
|
63
|
+
}
|
|
64
|
+
export function tieringPass(scopeId) {
|
|
65
|
+
if (!config.memoryTieringEnabled) {
|
|
66
|
+
return passSummary("tieringPass");
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const db = getDb();
|
|
70
|
+
const counts = db.prepare(`
|
|
71
|
+
SELECT
|
|
72
|
+
(SELECT COUNT(*) FROM mem_observations WHERE scope_id = ?) AS observations,
|
|
73
|
+
(SELECT COUNT(*) FROM mem_decisions WHERE scope_id = ?) AS decisions,
|
|
74
|
+
(SELECT COUNT(*) FROM mem_entities WHERE scope_id = ?) AS entities
|
|
75
|
+
`).get(scopeId, scopeId, scopeId);
|
|
76
|
+
let modified = 0;
|
|
77
|
+
const tx = db.transaction(() => {
|
|
78
|
+
modified += updateTier(`
|
|
79
|
+
UPDATE mem_observations
|
|
80
|
+
SET tier = 'cold', tier_reason = 'archived or superseded'
|
|
81
|
+
WHERE scope_id = ?
|
|
82
|
+
AND tier != 'cold'
|
|
83
|
+
AND tier_pinned_at IS NULL
|
|
84
|
+
AND (archived_at IS NOT NULL OR superseded_by IS NOT NULL)
|
|
85
|
+
`, [scopeId]);
|
|
86
|
+
modified += updateTier(`
|
|
87
|
+
UPDATE mem_decisions
|
|
88
|
+
SET tier = 'cold', tier_reason = 'archived or superseded'
|
|
89
|
+
WHERE scope_id = ?
|
|
90
|
+
AND tier != 'cold'
|
|
91
|
+
AND tier_pinned_at IS NULL
|
|
92
|
+
AND (archived_at IS NOT NULL OR superseded_by IS NOT NULL)
|
|
93
|
+
`, [scopeId]);
|
|
94
|
+
modified += updateTier(`
|
|
95
|
+
UPDATE mem_observations
|
|
96
|
+
SET tier = 'hot', tier_reason = 'referenced by recent entity decision'
|
|
97
|
+
WHERE scope_id = ?
|
|
98
|
+
AND tier != 'hot'
|
|
99
|
+
AND tier_pinned_at IS NULL
|
|
100
|
+
AND archived_at IS NULL
|
|
101
|
+
AND superseded_by IS NULL
|
|
102
|
+
AND entity_id IS NOT NULL
|
|
103
|
+
AND EXISTS (
|
|
104
|
+
SELECT 1
|
|
105
|
+
FROM mem_decisions d
|
|
106
|
+
WHERE d.scope_id = mem_observations.scope_id
|
|
107
|
+
AND d.entity_id = mem_observations.entity_id
|
|
108
|
+
AND d.archived_at IS NULL
|
|
109
|
+
AND d.superseded_by IS NULL
|
|
110
|
+
AND datetime(d.decided_at) >= datetime('now', '-7 days')
|
|
111
|
+
)
|
|
112
|
+
`, [scopeId]);
|
|
113
|
+
modified += updateTier(`
|
|
114
|
+
UPDATE mem_decisions
|
|
115
|
+
SET tier = 'hot', tier_reason = 'recent decision restatement'
|
|
116
|
+
WHERE scope_id = ?
|
|
117
|
+
AND tier != 'hot'
|
|
118
|
+
AND tier_pinned_at IS NULL
|
|
119
|
+
AND archived_at IS NULL
|
|
120
|
+
AND (
|
|
121
|
+
datetime(decided_at) >= datetime('now', '-7 days')
|
|
122
|
+
OR EXISTS (
|
|
123
|
+
SELECT 1
|
|
124
|
+
FROM mem_decisions old
|
|
125
|
+
WHERE old.superseded_by = mem_decisions.id
|
|
126
|
+
AND datetime(mem_decisions.decided_at) >= datetime('now', '-7 days')
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
`, [scopeId]);
|
|
130
|
+
modified += updateTier(`
|
|
131
|
+
UPDATE mem_entities
|
|
132
|
+
SET tier = 'hot', tier_reason = 'referenced by three recent observations'
|
|
133
|
+
WHERE scope_id = ?
|
|
134
|
+
AND tier != 'hot'
|
|
135
|
+
AND tier_pinned_at IS NULL
|
|
136
|
+
AND (
|
|
137
|
+
SELECT COUNT(*)
|
|
138
|
+
FROM mem_observations o
|
|
139
|
+
WHERE o.entity_id = mem_entities.id
|
|
140
|
+
AND o.archived_at IS NULL
|
|
141
|
+
AND o.superseded_by IS NULL
|
|
142
|
+
AND datetime(o.created_at) >= datetime('now', '-7 days')
|
|
143
|
+
) >= 3
|
|
144
|
+
`, [scopeId]);
|
|
145
|
+
modified += updateTier(`
|
|
146
|
+
UPDATE mem_observations
|
|
147
|
+
SET tier = 'warm', tier_reason = 'hot age threshold without recall'
|
|
148
|
+
WHERE scope_id = ?
|
|
149
|
+
AND tier = 'hot'
|
|
150
|
+
AND tier_pinned_at IS NULL
|
|
151
|
+
AND archived_at IS NULL
|
|
152
|
+
AND superseded_by IS NULL
|
|
153
|
+
AND last_recalled_at IS NULL
|
|
154
|
+
AND datetime(created_at) < datetime('now', ?)
|
|
155
|
+
`, [scopeId, `-${config.memoryHotAgeDays} days`]);
|
|
156
|
+
modified += updateTier(`
|
|
157
|
+
UPDATE mem_decisions
|
|
158
|
+
SET tier = 'warm', tier_reason = 'hot age threshold without recall'
|
|
159
|
+
WHERE scope_id = ?
|
|
160
|
+
AND tier = 'hot'
|
|
161
|
+
AND tier_pinned_at IS NULL
|
|
162
|
+
AND archived_at IS NULL
|
|
163
|
+
AND superseded_by IS NULL
|
|
164
|
+
AND last_recalled_at IS NULL
|
|
165
|
+
AND datetime(decided_at) < datetime('now', ?)
|
|
166
|
+
`, [scopeId, `-${config.memoryHotAgeDays} days`]);
|
|
167
|
+
modified += updateTier(`
|
|
168
|
+
UPDATE mem_observations
|
|
169
|
+
SET tier = 'cold', tier_reason = 'stale low confidence'
|
|
170
|
+
WHERE scope_id = ?
|
|
171
|
+
AND tier = 'warm'
|
|
172
|
+
AND tier_pinned_at IS NULL
|
|
173
|
+
AND confidence < 0.3
|
|
174
|
+
AND datetime(created_at) < datetime('now', '-60 days')
|
|
175
|
+
`, [scopeId]);
|
|
176
|
+
modified += updateTier(`
|
|
177
|
+
UPDATE mem_entities
|
|
178
|
+
SET tier = 'cold', tier_reason = 'stale low confidence'
|
|
179
|
+
WHERE scope_id = ?
|
|
180
|
+
AND tier = 'warm'
|
|
181
|
+
AND tier_pinned_at IS NULL
|
|
182
|
+
AND confidence < 0.3
|
|
183
|
+
AND datetime(updated_at) < datetime('now', '-60 days')
|
|
184
|
+
`, [scopeId]);
|
|
185
|
+
});
|
|
186
|
+
tx();
|
|
187
|
+
return passSummary("tieringPass", counts.observations + counts.decisions + counts.entities, modified);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
return passSummary("tieringPass", 0, 0, [error instanceof Error ? error.message : String(error)]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=tiering.js.map
|
package/dist/paths.js
CHANGED
|
@@ -13,8 +13,14 @@ function resolveChapterhouseHome() {
|
|
|
13
13
|
: join(configuredHome, ".chapterhouse");
|
|
14
14
|
}
|
|
15
15
|
export const CHAPTERHOUSE_HOME = resolveChapterhouseHome();
|
|
16
|
+
export function getChapterhouseHome() {
|
|
17
|
+
return resolveChapterhouseHome();
|
|
18
|
+
}
|
|
16
19
|
/** Path to the SQLite database */
|
|
17
20
|
export const DB_PATH = join(CHAPTERHOUSE_HOME, "chapterhouse.db");
|
|
21
|
+
export function getDbPath() {
|
|
22
|
+
return join(resolveChapterhouseHome(), "chapterhouse.db");
|
|
23
|
+
}
|
|
18
24
|
/** Path to the user .env file */
|
|
19
25
|
export const ENV_PATH = join(CHAPTERHOUSE_HOME, ".env");
|
|
20
26
|
/** Path to user-local skills */
|
|
@@ -36,6 +42,6 @@ export function resolveWikiRelativePath(relativePath) {
|
|
|
36
42
|
}
|
|
37
43
|
/** Ensure ~/.chapterhouse/ exists */
|
|
38
44
|
export function ensureChapterhouseHome() {
|
|
39
|
-
mkdirSync(
|
|
45
|
+
mkdirSync(resolveChapterhouseHome(), { recursive: true });
|
|
40
46
|
}
|
|
41
47
|
//# sourceMappingURL=paths.js.map
|