@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
@@ -2611,6 +2611,14 @@ async function ensureSchema() {
2611
2611
  );
2612
2612
  } catch {
2613
2613
  }
2614
+ try {
2615
+ await client.execute(
2616
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2617
+ ON memories(content_hash, agent_id, project_name, memory_type)
2618
+ WHERE content_hash IS NOT NULL`
2619
+ );
2620
+ } catch {
2621
+ }
2614
2622
  await client.executeMultiple(`
2615
2623
  CREATE TABLE IF NOT EXISTS entities (
2616
2624
  id TEXT PRIMARY KEY,
@@ -3481,6 +3489,196 @@ var init_state_bus = __esm({
3481
3489
  }
3482
3490
  });
3483
3491
 
3492
+ // src/lib/memory-write-governor.ts
3493
+ import { createHash } from "crypto";
3494
+ function normalizeMemoryText(text) {
3495
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3496
+ }
3497
+ function classifyMemoryType(input2) {
3498
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3499
+ const tool = input2.tool_name.toLowerCase();
3500
+ const text = input2.raw_text.toLowerCase();
3501
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3502
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3503
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3504
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3505
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3506
+ if (tool === "store_memory" || tool === "manual") return "observation";
3507
+ return "raw";
3508
+ }
3509
+ function shouldDropMemory(text) {
3510
+ const normalized = normalizeMemoryText(text);
3511
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3512
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3513
+ return { drop: true, reason: "known_boilerplate_noise" };
3514
+ }
3515
+ return { drop: false };
3516
+ }
3517
+ function shouldSkipEmbedding(input2) {
3518
+ const type = classifyMemoryType(input2);
3519
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3520
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3521
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3522
+ return false;
3523
+ }
3524
+ function hashMemoryContent(text) {
3525
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3526
+ }
3527
+ function scopedDedupArgs(input2) {
3528
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3529
+ }
3530
+ function governMemoryRecord(record) {
3531
+ const normalized = normalizeMemoryText(record.raw_text);
3532
+ const memoryType = classifyMemoryType({
3533
+ raw_text: normalized,
3534
+ agent_id: record.agent_id,
3535
+ project_name: record.project_name,
3536
+ tool_name: record.tool_name,
3537
+ memory_type: record.memory_type
3538
+ });
3539
+ const drop = shouldDropMemory(normalized);
3540
+ const skipEmbedding = shouldSkipEmbedding({
3541
+ raw_text: normalized,
3542
+ agent_id: record.agent_id,
3543
+ project_name: record.project_name,
3544
+ tool_name: record.tool_name,
3545
+ memory_type: memoryType
3546
+ });
3547
+ return {
3548
+ record: {
3549
+ ...record,
3550
+ raw_text: normalized,
3551
+ memory_type: memoryType,
3552
+ vector: skipEmbedding ? null : record.vector
3553
+ },
3554
+ contentHash: hashMemoryContent(normalized),
3555
+ shouldDrop: drop.drop,
3556
+ dropReason: drop.reason,
3557
+ skipEmbedding,
3558
+ hygiene: {
3559
+ dedup: true,
3560
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3561
+ }
3562
+ };
3563
+ }
3564
+ async function findScopedDuplicate(input2) {
3565
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3566
+ const client = getClient2();
3567
+ const args = scopedDedupArgs(input2);
3568
+ let sql = `SELECT id FROM memories
3569
+ WHERE content_hash = ?
3570
+ AND agent_id = ?
3571
+ AND project_name = ?
3572
+ AND COALESCE(memory_type, 'raw') = ?
3573
+ AND COALESCE(status, 'active') != 'deleted'`;
3574
+ if (input2.excludeId) {
3575
+ sql += " AND id != ?";
3576
+ args.push(input2.excludeId);
3577
+ }
3578
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3579
+ const result = await client.execute({ sql, args });
3580
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3581
+ }
3582
+ async function runPostWriteMemoryHygiene(memoryId) {
3583
+ try {
3584
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3585
+ const client = getClient2();
3586
+ const current = await client.execute({
3587
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3588
+ importance, timestamp
3589
+ FROM memories
3590
+ WHERE id = ?
3591
+ LIMIT 1`,
3592
+ args: [memoryId]
3593
+ });
3594
+ const row = current.rows[0];
3595
+ if (!row) return;
3596
+ const memoryType = String(row.memory_type ?? "raw");
3597
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3598
+ const agentId = String(row.agent_id);
3599
+ const projectName = String(row.project_name);
3600
+ if (contentHash) {
3601
+ await client.execute({
3602
+ sql: `UPDATE memories
3603
+ SET status = 'deleted',
3604
+ outcome = COALESCE(outcome, 'superseded')
3605
+ WHERE id != ?
3606
+ AND content_hash = ?
3607
+ AND agent_id = ?
3608
+ AND project_name = ?
3609
+ AND COALESCE(memory_type, 'raw') = ?
3610
+ AND COALESCE(status, 'active') = 'active'`,
3611
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3612
+ });
3613
+ }
3614
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3615
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3616
+ const old = await client.execute({
3617
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3618
+ args: [supersedesId]
3619
+ });
3620
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3621
+ const newImportance = Number(row.importance ?? 0);
3622
+ await client.batch([
3623
+ {
3624
+ sql: `UPDATE memories
3625
+ SET status = 'archived',
3626
+ outcome = COALESCE(outcome, 'superseded')
3627
+ WHERE id = ?`,
3628
+ args: [supersedesId]
3629
+ },
3630
+ {
3631
+ sql: `UPDATE memories
3632
+ SET importance = MAX(COALESCE(importance, 5), ?),
3633
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3634
+ WHERE id = ?`,
3635
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3636
+ }
3637
+ ], "write");
3638
+ }
3639
+ } catch (err) {
3640
+ process.stderr.write(
3641
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3642
+ `
3643
+ );
3644
+ }
3645
+ }
3646
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3647
+ if (memoryIds.length === 0) return;
3648
+ const run = () => {
3649
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3650
+ };
3651
+ if (typeof setImmediate === "function") setImmediate(run);
3652
+ else setTimeout(run, 0);
3653
+ }
3654
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3655
+ var init_memory_write_governor = __esm({
3656
+ "src/lib/memory-write-governor.ts"() {
3657
+ "use strict";
3658
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3659
+ "decision",
3660
+ "adr",
3661
+ "behavior",
3662
+ "procedure"
3663
+ ]);
3664
+ NOISE_DROP_PATTERNS = [
3665
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3666
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3667
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3668
+ /^\s*Intercom is a speedup, not delivery/im,
3669
+ /^\s*Context bar reads as USAGE not remaining/im
3670
+ ];
3671
+ SKIP_EMBED_PATTERNS = [
3672
+ /tmux capture-pane\b/i,
3673
+ /docker ps\b/i,
3674
+ /docker images\b/i,
3675
+ /git status\b/i,
3676
+ /grep .*node_modules/i,
3677
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3678
+ ];
3679
+ }
3680
+ });
3681
+
3484
3682
  // src/lib/shard-manager.ts
3485
3683
  var shard_manager_exports = {};
3486
3684
  __export(shard_manager_exports, {
@@ -3645,7 +3843,8 @@ async function ensureShardSchema(client) {
3645
3843
  }
3646
3844
  for (const idx of [
3647
3845
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3648
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3846
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3847
+ "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"
3649
3848
  ]) {
3650
3849
  try {
3651
3850
  await client.execute(idx);
@@ -4070,7 +4269,6 @@ __export(store_exports, {
4070
4269
  vectorToBlob: () => vectorToBlob,
4071
4270
  writeMemory: () => writeMemory
4072
4271
  });
4073
- import { createHash } from "crypto";
4074
4272
  function isBusyError2(err) {
4075
4273
  if (err instanceof Error) {
4076
4274
  const msg = err.message.toLowerCase();
@@ -4184,17 +4382,24 @@ async function writeMemory(record) {
4184
4382
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4185
4383
  );
4186
4384
  }
4187
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4188
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4385
+ const governed = governMemoryRecord(record);
4386
+ if (governed.shouldDrop) return;
4387
+ record = governed.record;
4388
+ const contentHash = governed.contentHash;
4389
+ const memoryType = record.memory_type ?? "raw";
4390
+ if (_pendingRecords.some(
4391
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4392
+ )) {
4189
4393
  return;
4190
4394
  }
4191
4395
  try {
4192
- const client = getClient();
4193
- const existing = await client.execute({
4194
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4195
- args: [contentHash, record.agent_id]
4396
+ const existing = await findScopedDuplicate({
4397
+ contentHash,
4398
+ agentId: record.agent_id,
4399
+ projectName: record.project_name,
4400
+ memoryType
4196
4401
  });
4197
- if (existing.rows.length > 0) return;
4402
+ if (existing) return;
4198
4403
  } catch {
4199
4404
  }
4200
4405
  const dbRow = {
@@ -4225,7 +4430,7 @@ async function writeMemory(record) {
4225
4430
  tier: record.tier ?? classifyTier(record),
4226
4431
  supersedes_id: record.supersedes_id ?? null,
4227
4432
  draft: record.draft ? 1 : 0,
4228
- memory_type: record.memory_type ?? "raw",
4433
+ memory_type: memoryType,
4229
4434
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4230
4435
  content_hash: contentHash,
4231
4436
  intent: record.intent ?? null,
@@ -4384,6 +4589,7 @@ async function flushBatch() {
4384
4589
  const globalClient = getClient();
4385
4590
  const globalStmts = batch.map(buildStmt);
4386
4591
  await globalClient.batch(globalStmts, "write");
4592
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4387
4593
  _pendingRecords.splice(0, batch.length);
4388
4594
  try {
4389
4595
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4642,6 +4848,7 @@ var init_store = __esm({
4642
4848
  init_keychain();
4643
4849
  init_config();
4644
4850
  init_state_bus();
4851
+ init_memory_write_governor();
4645
4852
  INIT_MAX_RETRIES = 3;
4646
4853
  INIT_RETRY_DELAY_MS = 1e3;
4647
4854
  _pendingRecords = [];
@@ -2592,6 +2592,14 @@ async function ensureSchema() {
2592
2592
  );
2593
2593
  } catch {
2594
2594
  }
2595
+ try {
2596
+ await client.execute(
2597
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2598
+ ON memories(content_hash, agent_id, project_name, memory_type)
2599
+ WHERE content_hash IS NOT NULL`
2600
+ );
2601
+ } catch {
2602
+ }
2595
2603
  await client.executeMultiple(`
2596
2604
  CREATE TABLE IF NOT EXISTS entities (
2597
2605
  id TEXT PRIMARY KEY,
@@ -3462,6 +3470,196 @@ var init_state_bus = __esm({
3462
3470
  }
3463
3471
  });
3464
3472
 
3473
+ // src/lib/memory-write-governor.ts
3474
+ import { createHash } from "crypto";
3475
+ function normalizeMemoryText(text) {
3476
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3477
+ }
3478
+ function classifyMemoryType(input2) {
3479
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3480
+ const tool = input2.tool_name.toLowerCase();
3481
+ const text = input2.raw_text.toLowerCase();
3482
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3483
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3484
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3485
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3486
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3487
+ if (tool === "store_memory" || tool === "manual") return "observation";
3488
+ return "raw";
3489
+ }
3490
+ function shouldDropMemory(text) {
3491
+ const normalized = normalizeMemoryText(text);
3492
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3493
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3494
+ return { drop: true, reason: "known_boilerplate_noise" };
3495
+ }
3496
+ return { drop: false };
3497
+ }
3498
+ function shouldSkipEmbedding(input2) {
3499
+ const type = classifyMemoryType(input2);
3500
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3501
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3502
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3503
+ return false;
3504
+ }
3505
+ function hashMemoryContent(text) {
3506
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3507
+ }
3508
+ function scopedDedupArgs(input2) {
3509
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3510
+ }
3511
+ function governMemoryRecord(record) {
3512
+ const normalized = normalizeMemoryText(record.raw_text);
3513
+ const memoryType = classifyMemoryType({
3514
+ raw_text: normalized,
3515
+ agent_id: record.agent_id,
3516
+ project_name: record.project_name,
3517
+ tool_name: record.tool_name,
3518
+ memory_type: record.memory_type
3519
+ });
3520
+ const drop = shouldDropMemory(normalized);
3521
+ const skipEmbedding = shouldSkipEmbedding({
3522
+ raw_text: normalized,
3523
+ agent_id: record.agent_id,
3524
+ project_name: record.project_name,
3525
+ tool_name: record.tool_name,
3526
+ memory_type: memoryType
3527
+ });
3528
+ return {
3529
+ record: {
3530
+ ...record,
3531
+ raw_text: normalized,
3532
+ memory_type: memoryType,
3533
+ vector: skipEmbedding ? null : record.vector
3534
+ },
3535
+ contentHash: hashMemoryContent(normalized),
3536
+ shouldDrop: drop.drop,
3537
+ dropReason: drop.reason,
3538
+ skipEmbedding,
3539
+ hygiene: {
3540
+ dedup: true,
3541
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3542
+ }
3543
+ };
3544
+ }
3545
+ async function findScopedDuplicate(input2) {
3546
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3547
+ const client = getClient2();
3548
+ const args = scopedDedupArgs(input2);
3549
+ let sql = `SELECT id FROM memories
3550
+ WHERE content_hash = ?
3551
+ AND agent_id = ?
3552
+ AND project_name = ?
3553
+ AND COALESCE(memory_type, 'raw') = ?
3554
+ AND COALESCE(status, 'active') != 'deleted'`;
3555
+ if (input2.excludeId) {
3556
+ sql += " AND id != ?";
3557
+ args.push(input2.excludeId);
3558
+ }
3559
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3560
+ const result = await client.execute({ sql, args });
3561
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3562
+ }
3563
+ async function runPostWriteMemoryHygiene(memoryId) {
3564
+ try {
3565
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3566
+ const client = getClient2();
3567
+ const current = await client.execute({
3568
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3569
+ importance, timestamp
3570
+ FROM memories
3571
+ WHERE id = ?
3572
+ LIMIT 1`,
3573
+ args: [memoryId]
3574
+ });
3575
+ const row = current.rows[0];
3576
+ if (!row) return;
3577
+ const memoryType = String(row.memory_type ?? "raw");
3578
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3579
+ const agentId = String(row.agent_id);
3580
+ const projectName = String(row.project_name);
3581
+ if (contentHash) {
3582
+ await client.execute({
3583
+ sql: `UPDATE memories
3584
+ SET status = 'deleted',
3585
+ outcome = COALESCE(outcome, 'superseded')
3586
+ WHERE id != ?
3587
+ AND content_hash = ?
3588
+ AND agent_id = ?
3589
+ AND project_name = ?
3590
+ AND COALESCE(memory_type, 'raw') = ?
3591
+ AND COALESCE(status, 'active') = 'active'`,
3592
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3593
+ });
3594
+ }
3595
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3596
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3597
+ const old = await client.execute({
3598
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3599
+ args: [supersedesId]
3600
+ });
3601
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3602
+ const newImportance = Number(row.importance ?? 0);
3603
+ await client.batch([
3604
+ {
3605
+ sql: `UPDATE memories
3606
+ SET status = 'archived',
3607
+ outcome = COALESCE(outcome, 'superseded')
3608
+ WHERE id = ?`,
3609
+ args: [supersedesId]
3610
+ },
3611
+ {
3612
+ sql: `UPDATE memories
3613
+ SET importance = MAX(COALESCE(importance, 5), ?),
3614
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3615
+ WHERE id = ?`,
3616
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3617
+ }
3618
+ ], "write");
3619
+ }
3620
+ } catch (err) {
3621
+ process.stderr.write(
3622
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3623
+ `
3624
+ );
3625
+ }
3626
+ }
3627
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3628
+ if (memoryIds.length === 0) return;
3629
+ const run = () => {
3630
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3631
+ };
3632
+ if (typeof setImmediate === "function") setImmediate(run);
3633
+ else setTimeout(run, 0);
3634
+ }
3635
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3636
+ var init_memory_write_governor = __esm({
3637
+ "src/lib/memory-write-governor.ts"() {
3638
+ "use strict";
3639
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3640
+ "decision",
3641
+ "adr",
3642
+ "behavior",
3643
+ "procedure"
3644
+ ]);
3645
+ NOISE_DROP_PATTERNS = [
3646
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3647
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3648
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3649
+ /^\s*Intercom is a speedup, not delivery/im,
3650
+ /^\s*Context bar reads as USAGE not remaining/im
3651
+ ];
3652
+ SKIP_EMBED_PATTERNS = [
3653
+ /tmux capture-pane\b/i,
3654
+ /docker ps\b/i,
3655
+ /docker images\b/i,
3656
+ /git status\b/i,
3657
+ /grep .*node_modules/i,
3658
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3659
+ ];
3660
+ }
3661
+ });
3662
+
3465
3663
  // src/lib/shard-manager.ts
3466
3664
  var shard_manager_exports = {};
3467
3665
  __export(shard_manager_exports, {
@@ -3626,7 +3824,8 @@ async function ensureShardSchema(client) {
3626
3824
  }
3627
3825
  for (const idx of [
3628
3826
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3629
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3827
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3828
+ "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"
3630
3829
  ]) {
3631
3830
  try {
3632
3831
  await client.execute(idx);
@@ -4051,7 +4250,6 @@ __export(store_exports, {
4051
4250
  vectorToBlob: () => vectorToBlob,
4052
4251
  writeMemory: () => writeMemory
4053
4252
  });
4054
- import { createHash } from "crypto";
4055
4253
  function isBusyError2(err) {
4056
4254
  if (err instanceof Error) {
4057
4255
  const msg = err.message.toLowerCase();
@@ -4165,17 +4363,24 @@ async function writeMemory(record) {
4165
4363
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4166
4364
  );
4167
4365
  }
4168
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4169
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4366
+ const governed = governMemoryRecord(record);
4367
+ if (governed.shouldDrop) return;
4368
+ record = governed.record;
4369
+ const contentHash = governed.contentHash;
4370
+ const memoryType = record.memory_type ?? "raw";
4371
+ if (_pendingRecords.some(
4372
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4373
+ )) {
4170
4374
  return;
4171
4375
  }
4172
4376
  try {
4173
- const client = getClient();
4174
- const existing = await client.execute({
4175
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4176
- args: [contentHash, record.agent_id]
4377
+ const existing = await findScopedDuplicate({
4378
+ contentHash,
4379
+ agentId: record.agent_id,
4380
+ projectName: record.project_name,
4381
+ memoryType
4177
4382
  });
4178
- if (existing.rows.length > 0) return;
4383
+ if (existing) return;
4179
4384
  } catch {
4180
4385
  }
4181
4386
  const dbRow = {
@@ -4206,7 +4411,7 @@ async function writeMemory(record) {
4206
4411
  tier: record.tier ?? classifyTier(record),
4207
4412
  supersedes_id: record.supersedes_id ?? null,
4208
4413
  draft: record.draft ? 1 : 0,
4209
- memory_type: record.memory_type ?? "raw",
4414
+ memory_type: memoryType,
4210
4415
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4211
4416
  content_hash: contentHash,
4212
4417
  intent: record.intent ?? null,
@@ -4365,6 +4570,7 @@ async function flushBatch() {
4365
4570
  const globalClient = getClient();
4366
4571
  const globalStmts = batch.map(buildStmt);
4367
4572
  await globalClient.batch(globalStmts, "write");
4573
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4368
4574
  _pendingRecords.splice(0, batch.length);
4369
4575
  try {
4370
4576
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4623,6 +4829,7 @@ var init_store = __esm({
4623
4829
  init_keychain();
4624
4830
  init_config();
4625
4831
  init_state_bus();
4832
+ init_memory_write_governor();
4626
4833
  INIT_MAX_RETRIES = 3;
4627
4834
  INIT_RETRY_DELAY_MS = 1e3;
4628
4835
  _pendingRecords = [];
@@ -2349,6 +2349,14 @@ async function ensureSchema() {
2349
2349
  );
2350
2350
  } catch {
2351
2351
  }
2352
+ try {
2353
+ await client.execute(
2354
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2355
+ ON memories(content_hash, agent_id, project_name, memory_type)
2356
+ WHERE content_hash IS NOT NULL`
2357
+ );
2358
+ } catch {
2359
+ }
2352
2360
  await client.executeMultiple(`
2353
2361
  CREATE TABLE IF NOT EXISTS entities (
2354
2362
  id TEXT PRIMARY KEY,
@@ -3373,7 +3381,8 @@ async function ensureShardSchema(client) {
3373
3381
  }
3374
3382
  for (const idx of [
3375
3383
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3376
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3384
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3385
+ "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"
3377
3386
  ]) {
3378
3387
  try {
3379
3388
  await client.execute(idx);
@@ -6598,7 +6607,11 @@ init_database();
6598
6607
  init_keychain();
6599
6608
  init_config();
6600
6609
  init_state_bus();
6610
+
6611
+ // src/lib/memory-write-governor.ts
6601
6612
  import { createHash } from "crypto";
6613
+
6614
+ // src/lib/store.ts
6602
6615
  var INIT_MAX_RETRIES = 3;
6603
6616
  var INIT_RETRY_DELAY_MS = 1e3;
6604
6617
  function isBusyError2(err) {