@askexenow/exe-os 0.9.34 → 0.9.35

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 (70) hide show
  1. package/dist/bin/backfill-conversations.js +210 -10
  2. package/dist/bin/backfill-responses.js +210 -10
  3. package/dist/bin/backfill-vectors.js +13 -2
  4. package/dist/bin/cleanup-stale-review-tasks.js +217 -10
  5. package/dist/bin/cli.js +222 -11
  6. package/dist/bin/exe-assign.js +210 -10
  7. package/dist/bin/exe-boot.js +24 -3
  8. package/dist/bin/exe-dispatch.js +222 -11
  9. package/dist/bin/exe-doctor.js +13 -2
  10. package/dist/bin/exe-export-behaviors.js +217 -10
  11. package/dist/bin/exe-forget.js +217 -10
  12. package/dist/bin/exe-gateway.js +222 -11
  13. package/dist/bin/exe-heartbeat.js +217 -10
  14. package/dist/bin/exe-kill.js +217 -10
  15. package/dist/bin/exe-launch-agent.js +92 -2
  16. package/dist/bin/exe-link.js +8 -0
  17. package/dist/bin/exe-pending-messages.js +217 -10
  18. package/dist/bin/exe-pending-notifications.js +217 -10
  19. package/dist/bin/exe-pending-reviews.js +217 -10
  20. package/dist/bin/exe-rename.js +8 -0
  21. package/dist/bin/exe-review.js +217 -10
  22. package/dist/bin/exe-search.js +217 -10
  23. package/dist/bin/exe-session-cleanup.js +222 -11
  24. package/dist/bin/exe-start-codex.js +92 -2
  25. package/dist/bin/exe-start-opencode.js +92 -2
  26. package/dist/bin/exe-status.js +217 -10
  27. package/dist/bin/exe-team.js +217 -10
  28. package/dist/bin/git-sweep.js +222 -11
  29. package/dist/bin/graph-backfill.js +92 -2
  30. package/dist/bin/graph-export.js +217 -10
  31. package/dist/bin/intercom-check.js +222 -11
  32. package/dist/bin/scan-tasks.js +222 -11
  33. package/dist/bin/setup.js +8 -0
  34. package/dist/bin/shard-migrate.js +92 -2
  35. package/dist/gateway/index.js +222 -11
  36. package/dist/hooks/bug-report-worker.js +222 -11
  37. package/dist/hooks/codex-stop-task-finalizer.js +217 -10
  38. package/dist/hooks/commit-complete.js +222 -11
  39. package/dist/hooks/error-recall.js +217 -10
  40. package/dist/hooks/ingest.js +217 -10
  41. package/dist/hooks/instructions-loaded.js +217 -10
  42. package/dist/hooks/notification.js +217 -10
  43. package/dist/hooks/post-compact.js +217 -10
  44. package/dist/hooks/post-tool-combined.js +217 -10
  45. package/dist/hooks/pre-compact.js +222 -11
  46. package/dist/hooks/pre-tool-use.js +217 -10
  47. package/dist/hooks/prompt-submit.js +222 -11
  48. package/dist/hooks/session-end.js +222 -11
  49. package/dist/hooks/session-start.js +217 -10
  50. package/dist/hooks/stop.js +217 -10
  51. package/dist/hooks/subagent-stop.js +217 -10
  52. package/dist/hooks/summary-worker.js +14 -1
  53. package/dist/index.js +222 -11
  54. package/dist/lib/cloud-sync.js +8 -0
  55. package/dist/lib/consolidation.js +3 -1
  56. package/dist/lib/database.js +8 -0
  57. package/dist/lib/db.js +8 -0
  58. package/dist/lib/device-registry.js +8 -0
  59. package/dist/lib/exe-daemon.js +1667 -1413
  60. package/dist/lib/hybrid-search.js +217 -10
  61. package/dist/lib/schedules.js +13 -2
  62. package/dist/lib/store.js +210 -10
  63. package/dist/lib/tasks.js +5 -1
  64. package/dist/lib/tmux-routing.js +5 -1
  65. package/dist/mcp/server.js +222 -11
  66. package/dist/mcp/tools/create-task.js +5 -1
  67. package/dist/mcp/tools/update-task.js +5 -1
  68. package/dist/runtime/index.js +222 -11
  69. package/dist/tui/App.js +222 -11
  70. package/package.json +1 -1
@@ -2298,6 +2298,14 @@ async function ensureSchema() {
2298
2298
  );
2299
2299
  } catch {
2300
2300
  }
2301
+ try {
2302
+ await client.execute(
2303
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2304
+ ON memories(content_hash, agent_id, project_name, memory_type)
2305
+ WHERE content_hash IS NOT NULL`
2306
+ );
2307
+ } catch {
2308
+ }
2301
2309
  await client.executeMultiple(`
2302
2310
  CREATE TABLE IF NOT EXISTS entities (
2303
2311
  id TEXT PRIMARY KEY,
@@ -3452,6 +3460,196 @@ var init_state_bus = __esm({
3452
3460
  }
3453
3461
  });
3454
3462
 
3463
+ // src/lib/memory-write-governor.ts
3464
+ import { createHash } from "crypto";
3465
+ function normalizeMemoryText(text) {
3466
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3467
+ }
3468
+ function classifyMemoryType(input) {
3469
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3470
+ const tool = input.tool_name.toLowerCase();
3471
+ const text = input.raw_text.toLowerCase();
3472
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3473
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3474
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3475
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3476
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3477
+ if (tool === "store_memory" || tool === "manual") return "observation";
3478
+ return "raw";
3479
+ }
3480
+ function shouldDropMemory(text) {
3481
+ const normalized = normalizeMemoryText(text);
3482
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3483
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3484
+ return { drop: true, reason: "known_boilerplate_noise" };
3485
+ }
3486
+ return { drop: false };
3487
+ }
3488
+ function shouldSkipEmbedding(input) {
3489
+ const type = classifyMemoryType(input);
3490
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3491
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3492
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3493
+ return false;
3494
+ }
3495
+ function hashMemoryContent(text) {
3496
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3497
+ }
3498
+ function scopedDedupArgs(input) {
3499
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3500
+ }
3501
+ function governMemoryRecord(record) {
3502
+ const normalized = normalizeMemoryText(record.raw_text);
3503
+ const memoryType = classifyMemoryType({
3504
+ raw_text: normalized,
3505
+ agent_id: record.agent_id,
3506
+ project_name: record.project_name,
3507
+ tool_name: record.tool_name,
3508
+ memory_type: record.memory_type
3509
+ });
3510
+ const drop = shouldDropMemory(normalized);
3511
+ const skipEmbedding = shouldSkipEmbedding({
3512
+ raw_text: normalized,
3513
+ agent_id: record.agent_id,
3514
+ project_name: record.project_name,
3515
+ tool_name: record.tool_name,
3516
+ memory_type: memoryType
3517
+ });
3518
+ return {
3519
+ record: {
3520
+ ...record,
3521
+ raw_text: normalized,
3522
+ memory_type: memoryType,
3523
+ vector: skipEmbedding ? null : record.vector
3524
+ },
3525
+ contentHash: hashMemoryContent(normalized),
3526
+ shouldDrop: drop.drop,
3527
+ dropReason: drop.reason,
3528
+ skipEmbedding,
3529
+ hygiene: {
3530
+ dedup: true,
3531
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3532
+ }
3533
+ };
3534
+ }
3535
+ async function findScopedDuplicate(input) {
3536
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3537
+ const client = getClient2();
3538
+ const args = scopedDedupArgs(input);
3539
+ let sql = `SELECT id FROM memories
3540
+ WHERE content_hash = ?
3541
+ AND agent_id = ?
3542
+ AND project_name = ?
3543
+ AND COALESCE(memory_type, 'raw') = ?
3544
+ AND COALESCE(status, 'active') != 'deleted'`;
3545
+ if (input.excludeId) {
3546
+ sql += " AND id != ?";
3547
+ args.push(input.excludeId);
3548
+ }
3549
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3550
+ const result = await client.execute({ sql, args });
3551
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3552
+ }
3553
+ async function runPostWriteMemoryHygiene(memoryId) {
3554
+ try {
3555
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3556
+ const client = getClient2();
3557
+ const current = await client.execute({
3558
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3559
+ importance, timestamp
3560
+ FROM memories
3561
+ WHERE id = ?
3562
+ LIMIT 1`,
3563
+ args: [memoryId]
3564
+ });
3565
+ const row = current.rows[0];
3566
+ if (!row) return;
3567
+ const memoryType = String(row.memory_type ?? "raw");
3568
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3569
+ const agentId = String(row.agent_id);
3570
+ const projectName = String(row.project_name);
3571
+ if (contentHash) {
3572
+ await client.execute({
3573
+ sql: `UPDATE memories
3574
+ SET status = 'deleted',
3575
+ outcome = COALESCE(outcome, 'superseded')
3576
+ WHERE id != ?
3577
+ AND content_hash = ?
3578
+ AND agent_id = ?
3579
+ AND project_name = ?
3580
+ AND COALESCE(memory_type, 'raw') = ?
3581
+ AND COALESCE(status, 'active') = 'active'`,
3582
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3583
+ });
3584
+ }
3585
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3586
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3587
+ const old = await client.execute({
3588
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3589
+ args: [supersedesId]
3590
+ });
3591
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3592
+ const newImportance = Number(row.importance ?? 0);
3593
+ await client.batch([
3594
+ {
3595
+ sql: `UPDATE memories
3596
+ SET status = 'archived',
3597
+ outcome = COALESCE(outcome, 'superseded')
3598
+ WHERE id = ?`,
3599
+ args: [supersedesId]
3600
+ },
3601
+ {
3602
+ sql: `UPDATE memories
3603
+ SET importance = MAX(COALESCE(importance, 5), ?),
3604
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3605
+ WHERE id = ?`,
3606
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3607
+ }
3608
+ ], "write");
3609
+ }
3610
+ } catch (err) {
3611
+ process.stderr.write(
3612
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3613
+ `
3614
+ );
3615
+ }
3616
+ }
3617
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3618
+ if (memoryIds.length === 0) return;
3619
+ const run = () => {
3620
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3621
+ };
3622
+ if (typeof setImmediate === "function") setImmediate(run);
3623
+ else setTimeout(run, 0);
3624
+ }
3625
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3626
+ var init_memory_write_governor = __esm({
3627
+ "src/lib/memory-write-governor.ts"() {
3628
+ "use strict";
3629
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3630
+ "decision",
3631
+ "adr",
3632
+ "behavior",
3633
+ "procedure"
3634
+ ]);
3635
+ NOISE_DROP_PATTERNS = [
3636
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3637
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3638
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3639
+ /^\s*Intercom is a speedup, not delivery/im,
3640
+ /^\s*Context bar reads as USAGE not remaining/im
3641
+ ];
3642
+ SKIP_EMBED_PATTERNS = [
3643
+ /tmux capture-pane\b/i,
3644
+ /docker ps\b/i,
3645
+ /docker images\b/i,
3646
+ /git status\b/i,
3647
+ /grep .*node_modules/i,
3648
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3649
+ ];
3650
+ }
3651
+ });
3652
+
3455
3653
  // src/lib/shard-manager.ts
3456
3654
  var shard_manager_exports = {};
3457
3655
  __export(shard_manager_exports, {
@@ -3616,7 +3814,8 @@ async function ensureShardSchema(client) {
3616
3814
  }
3617
3815
  for (const idx of [
3618
3816
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3619
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3817
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3818
+ "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
3620
3819
  ]) {
3621
3820
  try {
3622
3821
  await client.execute(idx);
@@ -4041,7 +4240,6 @@ __export(store_exports, {
4041
4240
  vectorToBlob: () => vectorToBlob,
4042
4241
  writeMemory: () => writeMemory
4043
4242
  });
4044
- import { createHash } from "crypto";
4045
4243
  function isBusyError2(err) {
4046
4244
  if (err instanceof Error) {
4047
4245
  const msg = err.message.toLowerCase();
@@ -4155,17 +4353,24 @@ async function writeMemory(record) {
4155
4353
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4156
4354
  );
4157
4355
  }
4158
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4159
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4356
+ const governed = governMemoryRecord(record);
4357
+ if (governed.shouldDrop) return;
4358
+ record = governed.record;
4359
+ const contentHash = governed.contentHash;
4360
+ const memoryType = record.memory_type ?? "raw";
4361
+ if (_pendingRecords.some(
4362
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4363
+ )) {
4160
4364
  return;
4161
4365
  }
4162
4366
  try {
4163
- const client = getClient();
4164
- const existing = await client.execute({
4165
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4166
- args: [contentHash, record.agent_id]
4367
+ const existing = await findScopedDuplicate({
4368
+ contentHash,
4369
+ agentId: record.agent_id,
4370
+ projectName: record.project_name,
4371
+ memoryType
4167
4372
  });
4168
- if (existing.rows.length > 0) return;
4373
+ if (existing) return;
4169
4374
  } catch {
4170
4375
  }
4171
4376
  const dbRow = {
@@ -4196,7 +4401,7 @@ async function writeMemory(record) {
4196
4401
  tier: record.tier ?? classifyTier(record),
4197
4402
  supersedes_id: record.supersedes_id ?? null,
4198
4403
  draft: record.draft ? 1 : 0,
4199
- memory_type: record.memory_type ?? "raw",
4404
+ memory_type: memoryType,
4200
4405
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4201
4406
  content_hash: contentHash,
4202
4407
  intent: record.intent ?? null,
@@ -4355,6 +4560,7 @@ async function flushBatch() {
4355
4560
  const globalClient = getClient();
4356
4561
  const globalStmts = batch.map(buildStmt);
4357
4562
  await globalClient.batch(globalStmts, "write");
4563
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4358
4564
  _pendingRecords.splice(0, batch.length);
4359
4565
  try {
4360
4566
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4613,6 +4819,7 @@ var init_store = __esm({
4613
4819
  init_keychain();
4614
4820
  init_config();
4615
4821
  init_state_bus();
4822
+ init_memory_write_governor();
4616
4823
  INIT_MAX_RETRIES = 3;
4617
4824
  INIT_RETRY_DELAY_MS = 1e3;
4618
4825
  _pendingRecords = [];
@@ -2299,6 +2299,14 @@ async function ensureSchema() {
2299
2299
  );
2300
2300
  } catch {
2301
2301
  }
2302
+ try {
2303
+ await client.execute(
2304
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2305
+ ON memories(content_hash, agent_id, project_name, memory_type)
2306
+ WHERE content_hash IS NOT NULL`
2307
+ );
2308
+ } catch {
2309
+ }
2302
2310
  await client.executeMultiple(`
2303
2311
  CREATE TABLE IF NOT EXISTS entities (
2304
2312
  id TEXT PRIMARY KEY,
@@ -3518,6 +3526,196 @@ var init_state_bus = __esm({
3518
3526
  }
3519
3527
  });
3520
3528
 
3529
+ // src/lib/memory-write-governor.ts
3530
+ import { createHash } from "crypto";
3531
+ function normalizeMemoryText(text) {
3532
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3533
+ }
3534
+ function classifyMemoryType(input) {
3535
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3536
+ const tool = input.tool_name.toLowerCase();
3537
+ const text = input.raw_text.toLowerCase();
3538
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3539
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3540
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3541
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3542
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3543
+ if (tool === "store_memory" || tool === "manual") return "observation";
3544
+ return "raw";
3545
+ }
3546
+ function shouldDropMemory(text) {
3547
+ const normalized = normalizeMemoryText(text);
3548
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3549
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3550
+ return { drop: true, reason: "known_boilerplate_noise" };
3551
+ }
3552
+ return { drop: false };
3553
+ }
3554
+ function shouldSkipEmbedding(input) {
3555
+ const type = classifyMemoryType(input);
3556
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3557
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3558
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3559
+ return false;
3560
+ }
3561
+ function hashMemoryContent(text) {
3562
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3563
+ }
3564
+ function scopedDedupArgs(input) {
3565
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3566
+ }
3567
+ function governMemoryRecord(record) {
3568
+ const normalized = normalizeMemoryText(record.raw_text);
3569
+ const memoryType = classifyMemoryType({
3570
+ raw_text: normalized,
3571
+ agent_id: record.agent_id,
3572
+ project_name: record.project_name,
3573
+ tool_name: record.tool_name,
3574
+ memory_type: record.memory_type
3575
+ });
3576
+ const drop = shouldDropMemory(normalized);
3577
+ const skipEmbedding = shouldSkipEmbedding({
3578
+ raw_text: normalized,
3579
+ agent_id: record.agent_id,
3580
+ project_name: record.project_name,
3581
+ tool_name: record.tool_name,
3582
+ memory_type: memoryType
3583
+ });
3584
+ return {
3585
+ record: {
3586
+ ...record,
3587
+ raw_text: normalized,
3588
+ memory_type: memoryType,
3589
+ vector: skipEmbedding ? null : record.vector
3590
+ },
3591
+ contentHash: hashMemoryContent(normalized),
3592
+ shouldDrop: drop.drop,
3593
+ dropReason: drop.reason,
3594
+ skipEmbedding,
3595
+ hygiene: {
3596
+ dedup: true,
3597
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3598
+ }
3599
+ };
3600
+ }
3601
+ async function findScopedDuplicate(input) {
3602
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3603
+ const client = getClient2();
3604
+ const args = scopedDedupArgs(input);
3605
+ let sql = `SELECT id FROM memories
3606
+ WHERE content_hash = ?
3607
+ AND agent_id = ?
3608
+ AND project_name = ?
3609
+ AND COALESCE(memory_type, 'raw') = ?
3610
+ AND COALESCE(status, 'active') != 'deleted'`;
3611
+ if (input.excludeId) {
3612
+ sql += " AND id != ?";
3613
+ args.push(input.excludeId);
3614
+ }
3615
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3616
+ const result = await client.execute({ sql, args });
3617
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3618
+ }
3619
+ async function runPostWriteMemoryHygiene(memoryId) {
3620
+ try {
3621
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3622
+ const client = getClient2();
3623
+ const current = await client.execute({
3624
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3625
+ importance, timestamp
3626
+ FROM memories
3627
+ WHERE id = ?
3628
+ LIMIT 1`,
3629
+ args: [memoryId]
3630
+ });
3631
+ const row = current.rows[0];
3632
+ if (!row) return;
3633
+ const memoryType = String(row.memory_type ?? "raw");
3634
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3635
+ const agentId = String(row.agent_id);
3636
+ const projectName = String(row.project_name);
3637
+ if (contentHash) {
3638
+ await client.execute({
3639
+ sql: `UPDATE memories
3640
+ SET status = 'deleted',
3641
+ outcome = COALESCE(outcome, 'superseded')
3642
+ WHERE id != ?
3643
+ AND content_hash = ?
3644
+ AND agent_id = ?
3645
+ AND project_name = ?
3646
+ AND COALESCE(memory_type, 'raw') = ?
3647
+ AND COALESCE(status, 'active') = 'active'`,
3648
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3649
+ });
3650
+ }
3651
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3652
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3653
+ const old = await client.execute({
3654
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3655
+ args: [supersedesId]
3656
+ });
3657
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3658
+ const newImportance = Number(row.importance ?? 0);
3659
+ await client.batch([
3660
+ {
3661
+ sql: `UPDATE memories
3662
+ SET status = 'archived',
3663
+ outcome = COALESCE(outcome, 'superseded')
3664
+ WHERE id = ?`,
3665
+ args: [supersedesId]
3666
+ },
3667
+ {
3668
+ sql: `UPDATE memories
3669
+ SET importance = MAX(COALESCE(importance, 5), ?),
3670
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3671
+ WHERE id = ?`,
3672
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3673
+ }
3674
+ ], "write");
3675
+ }
3676
+ } catch (err) {
3677
+ process.stderr.write(
3678
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3679
+ `
3680
+ );
3681
+ }
3682
+ }
3683
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3684
+ if (memoryIds.length === 0) return;
3685
+ const run = () => {
3686
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3687
+ };
3688
+ if (typeof setImmediate === "function") setImmediate(run);
3689
+ else setTimeout(run, 0);
3690
+ }
3691
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3692
+ var init_memory_write_governor = __esm({
3693
+ "src/lib/memory-write-governor.ts"() {
3694
+ "use strict";
3695
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3696
+ "decision",
3697
+ "adr",
3698
+ "behavior",
3699
+ "procedure"
3700
+ ]);
3701
+ NOISE_DROP_PATTERNS = [
3702
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3703
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3704
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3705
+ /^\s*Intercom is a speedup, not delivery/im,
3706
+ /^\s*Context bar reads as USAGE not remaining/im
3707
+ ];
3708
+ SKIP_EMBED_PATTERNS = [
3709
+ /tmux capture-pane\b/i,
3710
+ /docker ps\b/i,
3711
+ /docker images\b/i,
3712
+ /git status\b/i,
3713
+ /grep .*node_modules/i,
3714
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3715
+ ];
3716
+ }
3717
+ });
3718
+
3521
3719
  // src/lib/shard-manager.ts
3522
3720
  var shard_manager_exports = {};
3523
3721
  __export(shard_manager_exports, {
@@ -3682,7 +3880,8 @@ async function ensureShardSchema(client) {
3682
3880
  }
3683
3881
  for (const idx of [
3684
3882
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3685
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3883
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3884
+ "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
3686
3885
  ]) {
3687
3886
  try {
3688
3887
  await client.execute(idx);
@@ -4107,7 +4306,6 @@ __export(store_exports, {
4107
4306
  vectorToBlob: () => vectorToBlob,
4108
4307
  writeMemory: () => writeMemory
4109
4308
  });
4110
- import { createHash } from "crypto";
4111
4309
  function isBusyError2(err) {
4112
4310
  if (err instanceof Error) {
4113
4311
  const msg = err.message.toLowerCase();
@@ -4221,17 +4419,24 @@ async function writeMemory(record) {
4221
4419
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4222
4420
  );
4223
4421
  }
4224
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4225
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4422
+ const governed = governMemoryRecord(record);
4423
+ if (governed.shouldDrop) return;
4424
+ record = governed.record;
4425
+ const contentHash = governed.contentHash;
4426
+ const memoryType = record.memory_type ?? "raw";
4427
+ if (_pendingRecords.some(
4428
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4429
+ )) {
4226
4430
  return;
4227
4431
  }
4228
4432
  try {
4229
- const client = getClient();
4230
- const existing = await client.execute({
4231
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4232
- args: [contentHash, record.agent_id]
4433
+ const existing = await findScopedDuplicate({
4434
+ contentHash,
4435
+ agentId: record.agent_id,
4436
+ projectName: record.project_name,
4437
+ memoryType
4233
4438
  });
4234
- if (existing.rows.length > 0) return;
4439
+ if (existing) return;
4235
4440
  } catch {
4236
4441
  }
4237
4442
  const dbRow = {
@@ -4262,7 +4467,7 @@ async function writeMemory(record) {
4262
4467
  tier: record.tier ?? classifyTier(record),
4263
4468
  supersedes_id: record.supersedes_id ?? null,
4264
4469
  draft: record.draft ? 1 : 0,
4265
- memory_type: record.memory_type ?? "raw",
4470
+ memory_type: memoryType,
4266
4471
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4267
4472
  content_hash: contentHash,
4268
4473
  intent: record.intent ?? null,
@@ -4421,6 +4626,7 @@ async function flushBatch() {
4421
4626
  const globalClient = getClient();
4422
4627
  const globalStmts = batch.map(buildStmt);
4423
4628
  await globalClient.batch(globalStmts, "write");
4629
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4424
4630
  _pendingRecords.splice(0, batch.length);
4425
4631
  try {
4426
4632
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4679,6 +4885,7 @@ var init_store = __esm({
4679
4885
  init_keychain();
4680
4886
  init_config();
4681
4887
  init_state_bus();
4888
+ init_memory_write_governor();
4682
4889
  INIT_MAX_RETRIES = 3;
4683
4890
  INIT_RETRY_DELAY_MS = 1e3;
4684
4891
  _pendingRecords = [];