zouroboros-memory 3.0.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.
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * context-budget.ts — Token Budget Tracking for Swarm Orchestration
4
+ * MEM-001: Context Budget Awareness
5
+ *
6
+ * Implements MemGPT-style context budget awareness:
7
+ * - Tracks current token usage vs. model limits
8
+ * - Warning/critical thresholds trigger proactive checkpointing
9
+ * - Compresses or paginates retrieved facts before context overflow
10
+ */
11
+ import { Database } from "bun:sqlite";
12
+ export interface ContextBudget {
13
+ maxTokens: number;
14
+ warningThreshold: number;
15
+ criticalThreshold: number;
16
+ currentUsage: number;
17
+ warningFired: boolean;
18
+ criticalFired: boolean;
19
+ lastUpdated: number;
20
+ }
21
+ export interface BudgetCheckpoint {
22
+ id: string;
23
+ swarmId: string;
24
+ summary: string;
25
+ facts: CompressedFact[];
26
+ openLoops: string[];
27
+ pendingEpisodes: string[];
28
+ tokenEstimate: number;
29
+ compressedFrom: number;
30
+ compressionRatio: number;
31
+ createdAt: number;
32
+ }
33
+ export interface CompressedFact {
34
+ entity: string;
35
+ key: string | null;
36
+ value: string;
37
+ isSummary: boolean;
38
+ originalIds?: string[];
39
+ }
40
+ export interface BudgetMetrics {
41
+ checkpointsCreated: number;
42
+ compressionsTriggered: number;
43
+ warningFires: number;
44
+ criticalFires: number;
45
+ totalTokensTracked: number;
46
+ avgTokensPerOperation: number;
47
+ }
48
+ export interface CompressionPlan {
49
+ includeFacts: CompressedFact[];
50
+ excludeCount: number;
51
+ estimatedTokens: number;
52
+ withinBudget: boolean;
53
+ }
54
+ export interface RetrievalBudgetResult {
55
+ includeFacts: CompressedFact[];
56
+ checkpointRecommended: boolean;
57
+ checkpoint?: BudgetCheckpoint;
58
+ withinBudget: boolean;
59
+ estimatedTokens: number;
60
+ compressed: boolean;
61
+ excludedCount: number;
62
+ }
63
+ export declare function estimateTokens(text: string): number;
64
+ export declare function estimateFactTokens(fact: {
65
+ entity: string;
66
+ key: string | null;
67
+ value: string;
68
+ }): number;
69
+ export declare function getBudget(db?: Database): ContextBudget;
70
+ export declare function updateBudget(currentUsage: number, db?: Database): {
71
+ level: "ok" | "warning" | "critical";
72
+ budget: ContextBudget;
73
+ };
74
+ export declare function resetBudget(db?: Database): void;
75
+ export declare function initBudget(maxTokens: number, warningThreshold: number, criticalThreshold: number, db?: Database): void;
76
+ export declare function planCompression(facts: Array<{
77
+ id: string;
78
+ entity: string;
79
+ key: string | null;
80
+ value: string;
81
+ importance?: number;
82
+ confidence?: number;
83
+ }>, availableTokens: number): CompressionPlan;
84
+ export declare function createCheckpoint(swarmId: string, summary: string, facts: CompressedFact[], openLoopIds: string[], pendingEpisodeIds: string[]): BudgetCheckpoint;
85
+ export declare function loadCheckpoint(checkpointId: string): BudgetCheckpoint | null;
86
+ export declare function listCheckpoints(swarmId?: string, limit?: number): BudgetCheckpoint[];
87
+ export declare function retrievalWithBudget(facts: Array<{
88
+ id: string;
89
+ entity: string;
90
+ key: string | null;
91
+ value: string;
92
+ importance?: number;
93
+ confidence?: number;
94
+ }>, swarmId: string, operationSummary: string, maxTokens?: number): RetrievalBudgetResult;
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * context-budget.ts — Token Budget Tracking for Swarm Orchestration
4
+ * MEM-001: Context Budget Awareness
5
+ *
6
+ * Implements MemGPT-style context budget awareness:
7
+ * - Tracks current token usage vs. model limits
8
+ * - Warning/critical thresholds trigger proactive checkpointing
9
+ * - Compresses or paginates retrieved facts before context overflow
10
+ */
11
+ import { Database } from "bun:sqlite";
12
+ import { randomUUID } from "crypto";
13
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
14
+ import { join } from "path";
15
+ const DB_PATH = process.env.ZO_MEMORY_DB || "/home/workspace/.zo/memory/shared-facts.db";
16
+ const CHECKPOINT_DIR = process.env.ZO_CHECKPOINT_DIR || "/home/workspace/.zo/memory/checkpoints";
17
+ // ─── Database Setup ──────────────────────────────────────────────────────────
18
+ function getDb() {
19
+ const db = new Database(DB_PATH);
20
+ db.exec("PRAGMA journal_mode = WAL");
21
+ db.exec(`
22
+ CREATE TABLE IF NOT EXISTS context_budget_state (
23
+ id INTEGER PRIMARY KEY CHECK (id = 1),
24
+ max_tokens INTEGER NOT NULL DEFAULT 8000,
25
+ warning_threshold REAL NOT NULL DEFAULT 0.70,
26
+ critical_threshold REAL NOT NULL DEFAULT 0.90,
27
+ current_usage INTEGER NOT NULL DEFAULT 0,
28
+ warning_fired INTEGER NOT NULL DEFAULT 0,
29
+ critical_fired INTEGER NOT NULL DEFAULT 0,
30
+ last_updated INTEGER DEFAULT (strftime('%s','now'))
31
+ );
32
+ INSERT OR IGNORE INTO context_budget_state (id) VALUES (1);
33
+ CREATE TABLE IF NOT EXISTS budget_checkpoints (
34
+ id TEXT PRIMARY KEY,
35
+ swarm_id TEXT NOT NULL,
36
+ summary TEXT NOT NULL,
37
+ token_estimate INTEGER NOT NULL,
38
+ compressed_from INTEGER NOT NULL DEFAULT 0,
39
+ compression_ratio REAL NOT NULL DEFAULT 1.0,
40
+ created_at INTEGER DEFAULT (strftime('%s','now'))
41
+ );
42
+ CREATE INDEX IF NOT EXISTS idx_budget_checkpoints_swarm ON budget_checkpoints(swarm_id);
43
+ `);
44
+ return db;
45
+ }
46
+ // ─── Token Estimation ─────────────────────────────────────────────────────────
47
+ // ~4 chars per token is a common heuristic for English text
48
+ const CHARS_PER_TOKEN = 4;
49
+ export function estimateTokens(text) {
50
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
51
+ }
52
+ export function estimateFactTokens(fact) {
53
+ return estimateTokens(`${fact.entity} ${fact.key || ""} ${fact.value}`.trim());
54
+ }
55
+ // ─── Budget State ─────────────────────────────────────────────────────────────
56
+ export function getBudget(db) {
57
+ const _db = db || getDb();
58
+ const row = _db.prepare("SELECT * FROM context_budget_state WHERE id = 1").get();
59
+ if (!row)
60
+ return { maxTokens: 8000, warningThreshold: 0.70, criticalThreshold: 0.90, currentUsage: 0, warningFired: false, criticalFired: false, lastUpdated: Date.now() / 1000 };
61
+ return {
62
+ maxTokens: row.max_tokens,
63
+ warningThreshold: row.warning_threshold,
64
+ criticalThreshold: row.critical_threshold,
65
+ currentUsage: row.current_usage,
66
+ warningFired: Boolean(row.warning_fired),
67
+ criticalFired: Boolean(row.critical_fired),
68
+ lastUpdated: row.last_updated,
69
+ };
70
+ }
71
+ export function updateBudget(currentUsage, db) {
72
+ const _db = db || getDb();
73
+ const budget = getBudget(_db);
74
+ const usage = Math.max(0, currentUsage);
75
+ const ratio = budget.maxTokens > 0 ? usage / budget.maxTokens : 0;
76
+ let warningFired = budget.warningFired;
77
+ let criticalFired = budget.criticalFired;
78
+ if (ratio >= budget.criticalThreshold && !budget.criticalFired) {
79
+ warningFired = true;
80
+ criticalFired = true;
81
+ console.warn(`[context-budget] CRITICAL: ${Math.round(ratio * 100)}% (${usage}/${budget.maxTokens} tokens)`);
82
+ }
83
+ else if (ratio >= budget.warningThreshold && !budget.warningFired) {
84
+ warningFired = true;
85
+ console.warn(`[context-budget] WARNING: ${Math.round(ratio * 100)}% (${usage}/${budget.maxTokens} tokens)`);
86
+ }
87
+ _db.prepare(`UPDATE context_budget_state SET current_usage = ?, warning_fired = ?, critical_fired = ?, last_updated = strftime('%s','now') WHERE id = 1`).run(usage, warningFired ? 1 : 0, criticalFired ? 1 : 0);
88
+ return { level: ratio >= budget.criticalThreshold ? "critical" : ratio >= budget.warningThreshold ? "warning" : "ok", budget: getBudget(_db) };
89
+ }
90
+ export function resetBudget(db) {
91
+ const _db = db || getDb();
92
+ _db.prepare(`UPDATE context_budget_state SET current_usage = 0, warning_fired = 0, critical_fired = 0, last_updated = strftime('%s','now') WHERE id = 1`).run();
93
+ }
94
+ export function initBudget(maxTokens, warningThreshold, criticalThreshold, db) {
95
+ const _db = db || getDb();
96
+ _db.prepare(`UPDATE context_budget_state SET max_tokens = ?, warning_threshold = ?, critical_threshold = ?, last_updated = strftime('%s','now') WHERE id = 1`).run(maxTokens, warningThreshold, criticalThreshold);
97
+ console.log(`Context budget initialized: max=${maxTokens}, warning=${Math.round(warningThreshold * 100)}%, critical=${Math.round(criticalThreshold * 100)}%`);
98
+ }
99
+ // ─── Fact Compression ─────────────────────────────────────────────────────────
100
+ export function planCompression(facts, availableTokens) {
101
+ if (facts.length === 0)
102
+ return { includeFacts: [], excludeCount: 0, estimatedTokens: 0, withinBudget: true };
103
+ const scored = facts.map((f) => {
104
+ const tokens = estimateFactTokens(f);
105
+ const priority = (f.importance ?? 1.0) * (f.confidence ?? 1.0);
106
+ return { fact: f, tokens, score: priority / Math.max(1, tokens) };
107
+ });
108
+ scored.sort((a, b) => b.score - a.score);
109
+ let totalTokens = 0;
110
+ const includeFacts = [];
111
+ for (const item of scored) {
112
+ if (totalTokens + item.tokens <= availableTokens) {
113
+ includeFacts.push({ entity: item.fact.entity, key: item.fact.key, value: item.fact.value, isSummary: false, originalIds: [item.fact.id] });
114
+ totalTokens += item.tokens;
115
+ }
116
+ }
117
+ return { includeFacts, excludeCount: facts.length - includeFacts.length, estimatedTokens: totalTokens, withinBudget: totalTokens <= availableTokens };
118
+ }
119
+ // ─── Checkpoint Management ────────────────────────────────────────────────────
120
+ function ensureCheckpointDir() {
121
+ if (!existsSync(CHECKPOINT_DIR))
122
+ mkdirSync(CHECKPOINT_DIR, { recursive: true });
123
+ }
124
+ export function createCheckpoint(swarmId, summary, facts, openLoopIds, pendingEpisodeIds) {
125
+ ensureCheckpointDir();
126
+ const id = `ckpt-${randomUUID().slice(0, 8)}`;
127
+ const compressedFrom = facts.length;
128
+ const tokenEstimate = facts.reduce((sum, f) => sum + estimateTokens(f.value), 0);
129
+ const compressionRatio = compressedFrom > 0 ? Math.max(0, 1 - (tokenEstimate / Math.max(1, compressedFrom * 200))) : 1.0;
130
+ const checkpoint = { id, swarmId, summary, facts, openLoops: openLoopIds, pendingEpisodes: pendingEpisodeIds, tokenEstimate, compressedFrom, compressionRatio, createdAt: Math.floor(Date.now() / 1000) };
131
+ const db = getDb();
132
+ db.prepare(`INSERT INTO budget_checkpoints (id, swarm_id, summary, token_estimate, compressed_from, compression_ratio) VALUES (?, ?, ?, ?, ?, ?)`).run(id, swarmId, summary, tokenEstimate, compressedFrom, compressionRatio);
133
+ writeFileSync(join(CHECKPOINT_DIR, `${id}.json`), JSON.stringify(checkpoint, null, 2));
134
+ return checkpoint;
135
+ }
136
+ export function loadCheckpoint(checkpointId) {
137
+ const p = join(CHECKPOINT_DIR, `${checkpointId}.json`);
138
+ if (!existsSync(p))
139
+ return null;
140
+ try {
141
+ return JSON.parse(readFileSync(p, "utf-8"));
142
+ }
143
+ catch {
144
+ return null;
145
+ }
146
+ }
147
+ export function listCheckpoints(swarmId, limit = 10) {
148
+ const db = getDb();
149
+ let query = "SELECT * FROM budget_checkpoints";
150
+ const args = [];
151
+ if (swarmId) {
152
+ query += " WHERE swarm_id = ?";
153
+ args.push(swarmId);
154
+ }
155
+ query += " ORDER BY created_at DESC LIMIT ?";
156
+ args.push(limit);
157
+ return db.prepare(query).all(...args).map((row) => {
158
+ const cp = loadCheckpoint(row.id);
159
+ if (cp)
160
+ return cp;
161
+ return { id: row.id, swarmId: row.swarm_id, summary: row.summary, tokenEstimate: row.token_estimate, compressedFrom: row.compressed_from, compressionRatio: row.compression_ratio, createdAt: row.created_at, facts: [], openLoops: [], pendingEpisodes: [] };
162
+ });
163
+ }
164
+ // ─── Integration with memory retrieval ────────────────────────────────────────
165
+ export function retrievalWithBudget(facts, swarmId, operationSummary, maxTokens) {
166
+ const budget = getBudget();
167
+ const availableTokens = maxTokens ?? Math.floor(budget.maxTokens * (1 - budget.criticalThreshold));
168
+ const plan = planCompression(facts, availableTokens);
169
+ const { level } = updateBudget(budget.currentUsage + plan.estimatedTokens);
170
+ let checkpoint;
171
+ if (plan.excludeCount > 0 && plan.excludeCount >= Math.ceil(facts.length * 0.3)) {
172
+ checkpoint = createCheckpoint(swarmId, operationSummary, plan.includeFacts, [], []);
173
+ }
174
+ if (level !== "ok")
175
+ console.warn(`[context-budget] ${level.toUpperCase()}: ${plan.excludeCount} facts excluded, ${plan.estimatedTokens} tokens of ${availableTokens} budget`);
176
+ return { includeFacts: plan.includeFacts, checkpointRecommended: plan.excludeCount > 0, checkpoint, withinBudget: plan.withinBudget, estimatedTokens: plan.estimatedTokens, compressed: plan.excludeCount > 0, excludedCount: plan.excludeCount };
177
+ }
178
+ // ─── CLI ─────────────────────────────────────────────────────────────────────
179
+ async function main() {
180
+ const args = process.argv.slice(2);
181
+ if (args.length === 0) {
182
+ console.log(`Context Budget CLI - v1.0
183
+
184
+ Commands:
185
+ init --maxTokens <n> --warning <0.0-1.0> --critical <0.0-1.0>
186
+ status
187
+ track --operation <name> --inputTokens <n> --outputTokens <n>
188
+ compress --factCount <n> --availableTokens <n>
189
+ checkpoints --swarmId <id>
190
+ reset
191
+ `);
192
+ process.exit(0);
193
+ }
194
+ const command = args[0];
195
+ switch (command) {
196
+ case "init": {
197
+ let maxTokens = 8000, warning = 0.70, critical = 0.90;
198
+ for (let i = 1; i < args.length - 1; i++) {
199
+ if (args[i] === "--maxTokens" || args[i] === "-m")
200
+ maxTokens = parseInt(args[i + 1]);
201
+ if (args[i] === "--warning" || args[i] === "-w")
202
+ warning = parseFloat(args[i + 1]);
203
+ if (args[i] === "--critical" || args[i] === "-c")
204
+ critical = parseFloat(args[i + 1]);
205
+ }
206
+ initBudget(maxTokens, warning, critical);
207
+ break;
208
+ }
209
+ case "status": {
210
+ const budget = getBudget();
211
+ const ratio = budget.maxTokens > 0 ? (budget.currentUsage / budget.maxTokens) : 0;
212
+ const level = ratio >= budget.criticalThreshold ? "CRITICAL" : ratio >= budget.warningThreshold ? "WARNING" : "OK";
213
+ console.log(`Context Budget Status\n Max tokens: ${budget.maxTokens}\n Current: ${budget.currentUsage} (${Math.round(ratio * 100)}%)\n Warning at: ${Math.round(budget.warningThreshold * 100)}%\n Critical at: ${Math.round(budget.criticalThreshold * 100)}%\n Level: ${level}\n Warning fired: ${budget.warningFired}\n Critical fired: ${budget.criticalFired}\n Last updated: ${new Date(budget.lastUpdated * 1000).toISOString()}`);
214
+ break;
215
+ }
216
+ case "track": {
217
+ let operation = "unknown", inputTokens = 0, outputTokens = 0;
218
+ for (let i = 1; i < args.length; i++) {
219
+ if (args[i] === "--operation" || args[i] === "-o")
220
+ operation = args[i + 1] || "unknown";
221
+ if (args[i] === "--inputTokens" || args[i] === "-i")
222
+ inputTokens = parseInt(args[i + 1] || "0");
223
+ if (args[i] === "--outputTokens")
224
+ outputTokens = parseInt(args[i + 1] || "0");
225
+ }
226
+ const totalTokens = inputTokens + outputTokens;
227
+ const budget = getBudget();
228
+ const newUsage = budget.currentUsage + totalTokens;
229
+ const { level } = updateBudget(newUsage);
230
+ console.log(`Tracked: ${operation} +${totalTokens} tokens | Budget: ${newUsage}/${budget.maxTokens} | Level: ${level}`);
231
+ break;
232
+ }
233
+ case "compress": {
234
+ let factCount = 0, availableTokens = 1000;
235
+ for (let i = 1; i < args.length; i++) {
236
+ if (args[i] === "--factCount" || args[i] === "-n")
237
+ factCount = parseInt(args[i + 1] || "0");
238
+ if (args[i] === "--availableTokens" || args[i] === "-t")
239
+ availableTokens = parseInt(args[i + 1] || "0");
240
+ }
241
+ const mockFacts = Array.from({ length: factCount }, (_, i) => ({ id: `mock-${i}`, entity: "mock.entity", key: `key${i}`, value: "Sample fact value with reasonable content for token estimation. ".repeat(5), importance: 1.0, confidence: 1.0 }));
242
+ const plan = planCompression(mockFacts, availableTokens);
243
+ console.log(`Compression Plan\n Total facts: ${factCount}\n Available: ${availableTokens}\n Estimated: ${plan.estimatedTokens}\n Included: ${plan.includeFacts.length}\n Excluded: ${plan.excludeCount}\n Within budget: ${plan.withinBudget ? "YES" : "NO"}`);
244
+ break;
245
+ }
246
+ case "checkpoints": {
247
+ let swarmId;
248
+ for (let i = 1; i < args.length; i++)
249
+ if (args[i] === "--swarmId" || args[i] === "-s")
250
+ swarmId = args[i + 1];
251
+ const checkpoints = listCheckpoints(swarmId);
252
+ if (checkpoints.length === 0) {
253
+ console.log("No checkpoints found.");
254
+ break;
255
+ }
256
+ console.log(`Budget Checkpoints (${checkpoints.length})`);
257
+ for (const cp of checkpoints)
258
+ console.log(` ${cp.id} | ${cp.swarmId} | ${cp.tokenEstimate}t | ${cp.compressedFrom} facts | ${new Date(cp.createdAt * 1000).toISOString()}`);
259
+ break;
260
+ }
261
+ case "reset": {
262
+ resetBudget();
263
+ console.log("Budget reset to zero.");
264
+ break;
265
+ }
266
+ default:
267
+ console.error(`Unknown command: ${command}`);
268
+ process.exit(1);
269
+ }
270
+ }
271
+ if (import.meta.main)
272
+ main();
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * cross-persona.ts — Cross-Persona Memory Sharing & Inheritance
4
+ * MEM-104: Cross-Persona Memory Sharing
5
+ *
6
+ * Usage:
7
+ * bun cross-persona.ts list-shared
8
+ * bun cross-persona.ts share --entity <name> --pools pool1,pool2
9
+ * bun cross-persona.ts inherit --persona <name> --from parent1,parent2
10
+ * bun cross-persona.ts search --persona <name> --query "<text>"
11
+ */
12
+ import { Database } from "bun:sqlite";
13
+ export interface SharedPool {
14
+ id: string;
15
+ name: string;
16
+ description?: string;
17
+ personas: string[];
18
+ factCount: number;
19
+ }
20
+ export interface PersonaNode {
21
+ name: string;
22
+ depth: number;
23
+ children: PersonaNode[];
24
+ }
25
+ export declare function listPools(db: Database): SharedPool[];
26
+ export declare function createPool(db: Database, name: string, description?: string): string;
27
+ export declare function addToPool(db: Database, poolId: string, persona: string): void;
28
+ export declare function removeFromPool(db: Database, poolId: string, persona: string): void;
29
+ export declare function setInheritance(db: Database, childPersona: string, parentPersonas: string[]): void;
30
+ export declare function getAccessiblePersonas(db: Database, persona: string): string[];
31
+ export declare function searchCrossPersona(db: Database, persona: string, query: string, limit?: number): Array<Record<string, unknown>>;
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * cross-persona.ts — Cross-Persona Memory Sharing & Inheritance
4
+ * MEM-104: Cross-Persona Memory Sharing
5
+ *
6
+ * Usage:
7
+ * bun cross-persona.ts list-shared
8
+ * bun cross-persona.ts share --entity <name> --pools pool1,pool2
9
+ * bun cross-persona.ts inherit --persona <name> --from parent1,parent2
10
+ * bun cross-persona.ts search --persona <name> --query "<text>"
11
+ */
12
+ import { Database } from "bun:sqlite";
13
+ const DB_PATH = process.env.ZO_MEMORY_DB || "/home/workspace/.zo/memory/shared-facts.db";
14
+ function getDb() {
15
+ const db = new Database(DB_PATH);
16
+ db.exec("PRAGMA journal_mode = WAL");
17
+ db.exec(`
18
+ CREATE TABLE IF NOT EXISTS persona_pools (
19
+ id TEXT PRIMARY KEY,
20
+ name TEXT NOT NULL UNIQUE,
21
+ description TEXT,
22
+ created_at INTEGER DEFAULT (strftime('%s','now'))
23
+ );
24
+
25
+ CREATE TABLE IF NOT EXISTS persona_pool_members (
26
+ pool_id TEXT NOT NULL REFERENCES persona_pools(id) ON DELETE CASCADE,
27
+ persona TEXT NOT NULL,
28
+ added_at INTEGER DEFAULT (strftime('%s','now')),
29
+ PRIMARY KEY (pool_id, persona)
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS persona_inheritance (
33
+ child_persona TEXT PRIMARY KEY,
34
+ parent_persona TEXT NOT NULL,
35
+ depth INTEGER NOT NULL DEFAULT 1,
36
+ created_at INTEGER DEFAULT (strftime('%s','now'))
37
+ );
38
+
39
+ CREATE INDEX IF NOT EXISTS idx_pool_members_persona ON persona_pool_members(persona);
40
+ CREATE INDEX IF NOT EXISTS idx_inheritance_child ON persona_inheritance(child_persona);
41
+ `);
42
+ return db;
43
+ }
44
+ export function listPools(db) {
45
+ const pools = db.prepare("SELECT * FROM persona_pools ORDER BY name").all();
46
+ return pools.map(p => {
47
+ const members = db.prepare("SELECT persona FROM persona_pool_members WHERE pool_id = ?").all(p.id).map(m => m.persona);
48
+ const factCount = members.length > 0
49
+ ? db.prepare(`SELECT COUNT(*) as c FROM facts WHERE persona IN (${members.map(() => "?").join(",")})`).get(...members).c
50
+ : 0;
51
+ return { id: p.id, name: p.name, description: p.description, personas: members, factCount };
52
+ });
53
+ }
54
+ export function createPool(db, name, description) {
55
+ const { randomUUID } = require("crypto");
56
+ const id = randomUUID();
57
+ db.prepare("INSERT OR IGNORE INTO persona_pools (id, name, description) VALUES (?, ?, ?)").run(id, name, description || null);
58
+ return id;
59
+ }
60
+ export function addToPool(db, poolId, persona) {
61
+ db.prepare("INSERT OR IGNORE INTO persona_pool_members (pool_id, persona) VALUES (?, ?)").run(poolId, persona);
62
+ }
63
+ export function removeFromPool(db, poolId, persona) {
64
+ db.prepare("DELETE FROM persona_pool_members WHERE pool_id = ? AND persona = ?").run(poolId, persona);
65
+ }
66
+ export function setInheritance(db, childPersona, parentPersonas) {
67
+ db.prepare("DELETE FROM persona_inheritance WHERE child_persona = ?").run(childPersona);
68
+ for (let i = 0; i < parentPersonas.length; i++) {
69
+ db.prepare("INSERT OR IGNORE INTO persona_inheritance (child_persona, parent_persona, depth) VALUES (?, ?, ?)").run(childPersona, parentPersonas[i], i + 1);
70
+ }
71
+ }
72
+ export function getAccessiblePersonas(db, persona) {
73
+ const direct = [persona];
74
+ // Direct pool membership
75
+ const pools = db.prepare("SELECT pool_id FROM persona_pool_members WHERE persona = ?").all(persona);
76
+ for (const pool of pools) {
77
+ const members = db.prepare("SELECT persona FROM persona_pool_members WHERE pool_id = ? AND persona != ?").all(pool.pool_id, persona);
78
+ for (const m of members)
79
+ direct.push(m.persona);
80
+ }
81
+ // Inheritance chain
82
+ const parents = db.prepare("SELECT parent_persona FROM persona_inheritance WHERE child_persona = ? ORDER BY depth ASC").all(persona);
83
+ for (const parent of parents)
84
+ direct.push(parent.parent_persona);
85
+ return [...new Set(direct)];
86
+ }
87
+ export function searchCrossPersona(db, persona, query, limit = 10) {
88
+ const accessible = getAccessiblePersonas(db, persona);
89
+ if (accessible.length === 0)
90
+ return [];
91
+ const placeholders = accessible.map(() => "?").join(",");
92
+ const safeQuery = query.replace(/['"*]/g, "").trim();
93
+ try {
94
+ return db.prepare(`
95
+ SELECT f.*, rank,
96
+ CASE WHEN f.persona = ? THEN 1.0 ELSE 0.8 END as access_bonus
97
+ FROM facts f
98
+ JOIN facts_fts fts ON f.rowid = fts.rowid
99
+ WHERE facts_fts MATCH ? AND f.persona IN (${placeholders})
100
+ AND (f.expires_at IS NULL OR f.expires_at > ?)
101
+ ORDER BY rank * (CASE WHEN f.persona = ? THEN 1.0 ELSE 0.8 END) ASC
102
+ LIMIT ?
103
+ `).all(persona, safeQuery, ...accessible, Math.floor(Date.now() / 1000), persona, limit);
104
+ }
105
+ catch {
106
+ return [];
107
+ }
108
+ }
109
+ async function main() {
110
+ const args = process.argv.slice(2);
111
+ if (args.length === 0) {
112
+ console.log("Cross-Persona Memory CLI\n\nCommands:\n list-pools List all shared pools\n create-pool --name <n> --desc <d> Create a shared pool\n add-to-pool --pool <id> --persona <p> Add persona to pool\n remove-from-pool --pool <id> --persona <p> Remove persona from pool\n list-inheritance Show inheritance hierarchy\n set-inheritance --child <p> --parents p1,p2 Set parent chain\n accessible --persona <name> Show accessible personas for a persona\n search --persona <name> --query <text> Cross-persona search");
113
+ process.exit(0);
114
+ }
115
+ const flags = {};
116
+ for (let i = 0; i < args.length; i++)
117
+ if (args[i].startsWith("--"))
118
+ flags[args[i].slice(2)] = args[i + 1] || "";
119
+ const command = args[0];
120
+ const db = getDb();
121
+ if (command === "list-pools") {
122
+ const pools = listPools(db);
123
+ if (pools.length === 0) {
124
+ console.log("No shared pools.");
125
+ }
126
+ else {
127
+ pools.forEach(p => { console.log(`[${p.id.slice(0, 8)}] ${p.name} | ${p.description || "no description"} | ${p.personas.length} personas | ${p.factCount} facts`); if (p.personas.length)
128
+ console.log(` Members: ${p.personas.join(", ")}`); });
129
+ }
130
+ }
131
+ else if (command === "create-pool") {
132
+ if (!flags.name) {
133
+ console.error("--name required");
134
+ process.exit(1);
135
+ }
136
+ const id = createPool(db, flags.name, flags.desc);
137
+ console.log(`Pool "${flags.name}" created: ${id.slice(0, 8)}`);
138
+ }
139
+ else if (command === "add-to-pool") {
140
+ if (!flags.pool || !flags.persona) {
141
+ console.error("--pool and --persona required");
142
+ process.exit(1);
143
+ }
144
+ addToPool(db, flags.pool, flags.persona);
145
+ console.log(`Added "${flags.persona}" to pool ${flags.pool.slice(0, 8)}.`);
146
+ }
147
+ else if (command === "remove-from-pool") {
148
+ if (!flags.pool || !flags.persona) {
149
+ console.error("--pool and --persona required");
150
+ process.exit(1);
151
+ }
152
+ removeFromPool(db, flags.pool, flags.persona);
153
+ console.log(`Removed "${flags.persona}" from pool ${flags.pool.slice(0, 8)}.`);
154
+ }
155
+ else if (command === "set-inheritance") {
156
+ if (!flags.child || !flags.parents) {
157
+ console.error("--child and --parents required");
158
+ process.exit(1);
159
+ }
160
+ const parents = flags.parents.split(",").map(p => p.trim()).filter(Boolean);
161
+ setInheritance(db, flags.child, parents);
162
+ console.log(`"${flags.child}" now inherits from: ${parents.join(" > ")}`);
163
+ }
164
+ else if (command === "accessible") {
165
+ if (!flags.persona) {
166
+ console.error("--persona required");
167
+ process.exit(1);
168
+ }
169
+ const accessible = getAccessiblePersonas(db, flags.persona);
170
+ console.log(`Accessible for "${flags.persona}": ${accessible.join(", ")}`);
171
+ }
172
+ else if (command === "search") {
173
+ if (!flags.persona || !flags.query) {
174
+ console.error("--persona and --query required");
175
+ process.exit(1);
176
+ }
177
+ const results = await searchCrossPersona(db, flags.persona, flags.query);
178
+ if (results.length === 0) {
179
+ console.log("No results.");
180
+ }
181
+ else {
182
+ results.forEach(r => console.log(` [${r.persona.padEnd(20)}] ${r.entity}.${(r.key || "_")} = ${r.value.slice(0, 80)}`));
183
+ }
184
+ }
185
+ db.close();
186
+ }
187
+ if (import.meta.main)
188
+ main();
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Database management for Zouroboros Memory
3
+ */
4
+ import { Database } from 'bun:sqlite';
5
+ import type { MemoryConfig } from 'zouroboros-core';
6
+ /**
7
+ * Initialize the database with schema
8
+ */
9
+ export declare function initDatabase(config: MemoryConfig): Database;
10
+ /**
11
+ * Get the database instance (must call initDatabase first)
12
+ */
13
+ export declare function getDatabase(): Database;
14
+ /**
15
+ * Close the database connection
16
+ */
17
+ export declare function closeDatabase(): void;
18
+ /**
19
+ * Check if database is initialized
20
+ */
21
+ export declare function isInitialized(): boolean;
22
+ /**
23
+ * Run database migrations
24
+ */
25
+ export declare function runMigrations(config: MemoryConfig): void;
26
+ /**
27
+ * Get database statistics
28
+ */
29
+ export declare function getDbStats(config: MemoryConfig): {
30
+ facts: number;
31
+ episodes: number;
32
+ procedures: number;
33
+ openLoops: number;
34
+ embeddings: number;
35
+ };