@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
@@ -2213,6 +2213,14 @@ async function ensureSchema() {
2213
2213
  );
2214
2214
  } catch {
2215
2215
  }
2216
+ try {
2217
+ await client.execute(
2218
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2219
+ ON memories(content_hash, agent_id, project_name, memory_type)
2220
+ WHERE content_hash IS NOT NULL`
2221
+ );
2222
+ } catch {
2223
+ }
2216
2224
  await client.executeMultiple(`
2217
2225
  CREATE TABLE IF NOT EXISTS entities (
2218
2226
  id TEXT PRIMARY KEY,
@@ -2882,7 +2890,8 @@ async function ensureShardSchema(client) {
2882
2890
  }
2883
2891
  for (const idx of [
2884
2892
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2885
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
2893
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2894
+ "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"
2886
2895
  ]) {
2887
2896
  try {
2888
2897
  await client.execute(idx);
@@ -3301,7 +3310,6 @@ import { parseArgs } from "util";
3301
3310
  // src/lib/store.ts
3302
3311
  init_memory();
3303
3312
  init_database();
3304
- import { createHash } from "crypto";
3305
3313
 
3306
3314
  // src/lib/keychain.ts
3307
3315
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -3534,6 +3542,190 @@ var StateBus = class {
3534
3542
  };
3535
3543
  var orgBus = new StateBus();
3536
3544
 
3545
+ // src/lib/memory-write-governor.ts
3546
+ import { createHash } from "crypto";
3547
+ var HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3548
+ "decision",
3549
+ "adr",
3550
+ "behavior",
3551
+ "procedure"
3552
+ ]);
3553
+ var NOISE_DROP_PATTERNS = [
3554
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3555
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3556
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3557
+ /^\s*Intercom is a speedup, not delivery/im,
3558
+ /^\s*Context bar reads as USAGE not remaining/im
3559
+ ];
3560
+ var SKIP_EMBED_PATTERNS = [
3561
+ /tmux capture-pane\b/i,
3562
+ /docker ps\b/i,
3563
+ /docker images\b/i,
3564
+ /git status\b/i,
3565
+ /grep .*node_modules/i,
3566
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3567
+ ];
3568
+ function normalizeMemoryText(text) {
3569
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3570
+ }
3571
+ function classifyMemoryType(input) {
3572
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3573
+ const tool = input.tool_name.toLowerCase();
3574
+ const text = input.raw_text.toLowerCase();
3575
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3576
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3577
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3578
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3579
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3580
+ if (tool === "store_memory" || tool === "manual") return "observation";
3581
+ return "raw";
3582
+ }
3583
+ function shouldDropMemory(text) {
3584
+ const normalized = normalizeMemoryText(text);
3585
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3586
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3587
+ return { drop: true, reason: "known_boilerplate_noise" };
3588
+ }
3589
+ return { drop: false };
3590
+ }
3591
+ function shouldSkipEmbedding(input) {
3592
+ const type = classifyMemoryType(input);
3593
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3594
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3595
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3596
+ return false;
3597
+ }
3598
+ function hashMemoryContent(text) {
3599
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3600
+ }
3601
+ function scopedDedupArgs(input) {
3602
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3603
+ }
3604
+ function governMemoryRecord(record) {
3605
+ const normalized = normalizeMemoryText(record.raw_text);
3606
+ const memoryType = classifyMemoryType({
3607
+ raw_text: normalized,
3608
+ agent_id: record.agent_id,
3609
+ project_name: record.project_name,
3610
+ tool_name: record.tool_name,
3611
+ memory_type: record.memory_type
3612
+ });
3613
+ const drop = shouldDropMemory(normalized);
3614
+ const skipEmbedding = shouldSkipEmbedding({
3615
+ raw_text: normalized,
3616
+ agent_id: record.agent_id,
3617
+ project_name: record.project_name,
3618
+ tool_name: record.tool_name,
3619
+ memory_type: memoryType
3620
+ });
3621
+ return {
3622
+ record: {
3623
+ ...record,
3624
+ raw_text: normalized,
3625
+ memory_type: memoryType,
3626
+ vector: skipEmbedding ? null : record.vector
3627
+ },
3628
+ contentHash: hashMemoryContent(normalized),
3629
+ shouldDrop: drop.drop,
3630
+ dropReason: drop.reason,
3631
+ skipEmbedding,
3632
+ hygiene: {
3633
+ dedup: true,
3634
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3635
+ }
3636
+ };
3637
+ }
3638
+ async function findScopedDuplicate(input) {
3639
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3640
+ const client = getClient2();
3641
+ const args = scopedDedupArgs(input);
3642
+ let sql = `SELECT id FROM memories
3643
+ WHERE content_hash = ?
3644
+ AND agent_id = ?
3645
+ AND project_name = ?
3646
+ AND COALESCE(memory_type, 'raw') = ?
3647
+ AND COALESCE(status, 'active') != 'deleted'`;
3648
+ if (input.excludeId) {
3649
+ sql += " AND id != ?";
3650
+ args.push(input.excludeId);
3651
+ }
3652
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3653
+ const result = await client.execute({ sql, args });
3654
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3655
+ }
3656
+ async function runPostWriteMemoryHygiene(memoryId) {
3657
+ try {
3658
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3659
+ const client = getClient2();
3660
+ const current = await client.execute({
3661
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3662
+ importance, timestamp
3663
+ FROM memories
3664
+ WHERE id = ?
3665
+ LIMIT 1`,
3666
+ args: [memoryId]
3667
+ });
3668
+ const row = current.rows[0];
3669
+ if (!row) return;
3670
+ const memoryType = String(row.memory_type ?? "raw");
3671
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3672
+ const agentId = String(row.agent_id);
3673
+ const projectName = String(row.project_name);
3674
+ if (contentHash) {
3675
+ await client.execute({
3676
+ sql: `UPDATE memories
3677
+ SET status = 'deleted',
3678
+ outcome = COALESCE(outcome, 'superseded')
3679
+ WHERE id != ?
3680
+ AND content_hash = ?
3681
+ AND agent_id = ?
3682
+ AND project_name = ?
3683
+ AND COALESCE(memory_type, 'raw') = ?
3684
+ AND COALESCE(status, 'active') = 'active'`,
3685
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3686
+ });
3687
+ }
3688
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3689
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3690
+ const old = await client.execute({
3691
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3692
+ args: [supersedesId]
3693
+ });
3694
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3695
+ const newImportance = Number(row.importance ?? 0);
3696
+ await client.batch([
3697
+ {
3698
+ sql: `UPDATE memories
3699
+ SET status = 'archived',
3700
+ outcome = COALESCE(outcome, 'superseded')
3701
+ WHERE id = ?`,
3702
+ args: [supersedesId]
3703
+ },
3704
+ {
3705
+ sql: `UPDATE memories
3706
+ SET importance = MAX(COALESCE(importance, 5), ?),
3707
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3708
+ WHERE id = ?`,
3709
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3710
+ }
3711
+ ], "write");
3712
+ }
3713
+ } catch (err) {
3714
+ process.stderr.write(
3715
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3716
+ `
3717
+ );
3718
+ }
3719
+ }
3720
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3721
+ if (memoryIds.length === 0) return;
3722
+ const run = () => {
3723
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3724
+ };
3725
+ if (typeof setImmediate === "function") setImmediate(run);
3726
+ else setTimeout(run, 0);
3727
+ }
3728
+
3537
3729
  // src/lib/store.ts
3538
3730
  var INIT_MAX_RETRIES = 3;
3539
3731
  var INIT_RETRY_DELAY_MS = 1e3;
@@ -3656,17 +3848,24 @@ async function writeMemory(record) {
3656
3848
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3657
3849
  );
3658
3850
  }
3659
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3660
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3851
+ const governed = governMemoryRecord(record);
3852
+ if (governed.shouldDrop) return;
3853
+ record = governed.record;
3854
+ const contentHash = governed.contentHash;
3855
+ const memoryType = record.memory_type ?? "raw";
3856
+ if (_pendingRecords.some(
3857
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3858
+ )) {
3661
3859
  return;
3662
3860
  }
3663
3861
  try {
3664
- const client = getClient();
3665
- const existing = await client.execute({
3666
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3667
- args: [contentHash, record.agent_id]
3862
+ const existing = await findScopedDuplicate({
3863
+ contentHash,
3864
+ agentId: record.agent_id,
3865
+ projectName: record.project_name,
3866
+ memoryType
3668
3867
  });
3669
- if (existing.rows.length > 0) return;
3868
+ if (existing) return;
3670
3869
  } catch {
3671
3870
  }
3672
3871
  const dbRow = {
@@ -3697,7 +3896,7 @@ async function writeMemory(record) {
3697
3896
  tier: record.tier ?? classifyTier(record),
3698
3897
  supersedes_id: record.supersedes_id ?? null,
3699
3898
  draft: record.draft ? 1 : 0,
3700
- memory_type: record.memory_type ?? "raw",
3899
+ memory_type: memoryType,
3701
3900
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3702
3901
  content_hash: contentHash,
3703
3902
  intent: record.intent ?? null,
@@ -3856,6 +4055,7 @@ async function flushBatch() {
3856
4055
  const globalClient = getClient();
3857
4056
  const globalStmts = batch.map(buildStmt);
3858
4057
  await globalClient.batch(globalStmts, "write");
4058
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3859
4059
  _pendingRecords.splice(0, batch.length);
3860
4060
  try {
3861
4061
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -2213,6 +2213,14 @@ async function ensureSchema() {
2213
2213
  );
2214
2214
  } catch {
2215
2215
  }
2216
+ try {
2217
+ await client.execute(
2218
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2219
+ ON memories(content_hash, agent_id, project_name, memory_type)
2220
+ WHERE content_hash IS NOT NULL`
2221
+ );
2222
+ } catch {
2223
+ }
2216
2224
  await client.executeMultiple(`
2217
2225
  CREATE TABLE IF NOT EXISTS entities (
2218
2226
  id TEXT PRIMARY KEY,
@@ -2882,7 +2890,8 @@ async function ensureShardSchema(client) {
2882
2890
  }
2883
2891
  for (const idx of [
2884
2892
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2885
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
2893
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2894
+ "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"
2886
2895
  ]) {
2887
2896
  try {
2888
2897
  await client.execute(idx);
@@ -3300,7 +3309,6 @@ import { homedir } from "os";
3300
3309
  // src/lib/store.ts
3301
3310
  init_memory();
3302
3311
  init_database();
3303
- import { createHash } from "crypto";
3304
3312
 
3305
3313
  // src/lib/keychain.ts
3306
3314
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -3533,6 +3541,190 @@ var StateBus = class {
3533
3541
  };
3534
3542
  var orgBus = new StateBus();
3535
3543
 
3544
+ // src/lib/memory-write-governor.ts
3545
+ import { createHash } from "crypto";
3546
+ var HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3547
+ "decision",
3548
+ "adr",
3549
+ "behavior",
3550
+ "procedure"
3551
+ ]);
3552
+ var NOISE_DROP_PATTERNS = [
3553
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3554
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3555
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3556
+ /^\s*Intercom is a speedup, not delivery/im,
3557
+ /^\s*Context bar reads as USAGE not remaining/im
3558
+ ];
3559
+ var SKIP_EMBED_PATTERNS = [
3560
+ /tmux capture-pane\b/i,
3561
+ /docker ps\b/i,
3562
+ /docker images\b/i,
3563
+ /git status\b/i,
3564
+ /grep .*node_modules/i,
3565
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3566
+ ];
3567
+ function normalizeMemoryText(text) {
3568
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3569
+ }
3570
+ function classifyMemoryType(input) {
3571
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3572
+ const tool = input.tool_name.toLowerCase();
3573
+ const text = input.raw_text.toLowerCase();
3574
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3575
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3576
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3577
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3578
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3579
+ if (tool === "store_memory" || tool === "manual") return "observation";
3580
+ return "raw";
3581
+ }
3582
+ function shouldDropMemory(text) {
3583
+ const normalized = normalizeMemoryText(text);
3584
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3585
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3586
+ return { drop: true, reason: "known_boilerplate_noise" };
3587
+ }
3588
+ return { drop: false };
3589
+ }
3590
+ function shouldSkipEmbedding(input) {
3591
+ const type = classifyMemoryType(input);
3592
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3593
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3594
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3595
+ return false;
3596
+ }
3597
+ function hashMemoryContent(text) {
3598
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3599
+ }
3600
+ function scopedDedupArgs(input) {
3601
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3602
+ }
3603
+ function governMemoryRecord(record) {
3604
+ const normalized = normalizeMemoryText(record.raw_text);
3605
+ const memoryType = classifyMemoryType({
3606
+ raw_text: normalized,
3607
+ agent_id: record.agent_id,
3608
+ project_name: record.project_name,
3609
+ tool_name: record.tool_name,
3610
+ memory_type: record.memory_type
3611
+ });
3612
+ const drop = shouldDropMemory(normalized);
3613
+ const skipEmbedding = shouldSkipEmbedding({
3614
+ raw_text: normalized,
3615
+ agent_id: record.agent_id,
3616
+ project_name: record.project_name,
3617
+ tool_name: record.tool_name,
3618
+ memory_type: memoryType
3619
+ });
3620
+ return {
3621
+ record: {
3622
+ ...record,
3623
+ raw_text: normalized,
3624
+ memory_type: memoryType,
3625
+ vector: skipEmbedding ? null : record.vector
3626
+ },
3627
+ contentHash: hashMemoryContent(normalized),
3628
+ shouldDrop: drop.drop,
3629
+ dropReason: drop.reason,
3630
+ skipEmbedding,
3631
+ hygiene: {
3632
+ dedup: true,
3633
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3634
+ }
3635
+ };
3636
+ }
3637
+ async function findScopedDuplicate(input) {
3638
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3639
+ const client = getClient2();
3640
+ const args = scopedDedupArgs(input);
3641
+ let sql = `SELECT id FROM memories
3642
+ WHERE content_hash = ?
3643
+ AND agent_id = ?
3644
+ AND project_name = ?
3645
+ AND COALESCE(memory_type, 'raw') = ?
3646
+ AND COALESCE(status, 'active') != 'deleted'`;
3647
+ if (input.excludeId) {
3648
+ sql += " AND id != ?";
3649
+ args.push(input.excludeId);
3650
+ }
3651
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3652
+ const result = await client.execute({ sql, args });
3653
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3654
+ }
3655
+ async function runPostWriteMemoryHygiene(memoryId) {
3656
+ try {
3657
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3658
+ const client = getClient2();
3659
+ const current = await client.execute({
3660
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3661
+ importance, timestamp
3662
+ FROM memories
3663
+ WHERE id = ?
3664
+ LIMIT 1`,
3665
+ args: [memoryId]
3666
+ });
3667
+ const row = current.rows[0];
3668
+ if (!row) return;
3669
+ const memoryType = String(row.memory_type ?? "raw");
3670
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3671
+ const agentId = String(row.agent_id);
3672
+ const projectName = String(row.project_name);
3673
+ if (contentHash) {
3674
+ await client.execute({
3675
+ sql: `UPDATE memories
3676
+ SET status = 'deleted',
3677
+ outcome = COALESCE(outcome, 'superseded')
3678
+ WHERE id != ?
3679
+ AND content_hash = ?
3680
+ AND agent_id = ?
3681
+ AND project_name = ?
3682
+ AND COALESCE(memory_type, 'raw') = ?
3683
+ AND COALESCE(status, 'active') = 'active'`,
3684
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3685
+ });
3686
+ }
3687
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3688
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3689
+ const old = await client.execute({
3690
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3691
+ args: [supersedesId]
3692
+ });
3693
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3694
+ const newImportance = Number(row.importance ?? 0);
3695
+ await client.batch([
3696
+ {
3697
+ sql: `UPDATE memories
3698
+ SET status = 'archived',
3699
+ outcome = COALESCE(outcome, 'superseded')
3700
+ WHERE id = ?`,
3701
+ args: [supersedesId]
3702
+ },
3703
+ {
3704
+ sql: `UPDATE memories
3705
+ SET importance = MAX(COALESCE(importance, 5), ?),
3706
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3707
+ WHERE id = ?`,
3708
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3709
+ }
3710
+ ], "write");
3711
+ }
3712
+ } catch (err) {
3713
+ process.stderr.write(
3714
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3715
+ `
3716
+ );
3717
+ }
3718
+ }
3719
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3720
+ if (memoryIds.length === 0) return;
3721
+ const run = () => {
3722
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3723
+ };
3724
+ if (typeof setImmediate === "function") setImmediate(run);
3725
+ else setTimeout(run, 0);
3726
+ }
3727
+
3536
3728
  // src/lib/store.ts
3537
3729
  var INIT_MAX_RETRIES = 3;
3538
3730
  var INIT_RETRY_DELAY_MS = 1e3;
@@ -3655,17 +3847,24 @@ async function writeMemory(record) {
3655
3847
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3656
3848
  );
3657
3849
  }
3658
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3659
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3850
+ const governed = governMemoryRecord(record);
3851
+ if (governed.shouldDrop) return;
3852
+ record = governed.record;
3853
+ const contentHash = governed.contentHash;
3854
+ const memoryType = record.memory_type ?? "raw";
3855
+ if (_pendingRecords.some(
3856
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3857
+ )) {
3660
3858
  return;
3661
3859
  }
3662
3860
  try {
3663
- const client = getClient();
3664
- const existing = await client.execute({
3665
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3666
- args: [contentHash, record.agent_id]
3861
+ const existing = await findScopedDuplicate({
3862
+ contentHash,
3863
+ agentId: record.agent_id,
3864
+ projectName: record.project_name,
3865
+ memoryType
3667
3866
  });
3668
- if (existing.rows.length > 0) return;
3867
+ if (existing) return;
3669
3868
  } catch {
3670
3869
  }
3671
3870
  const dbRow = {
@@ -3696,7 +3895,7 @@ async function writeMemory(record) {
3696
3895
  tier: record.tier ?? classifyTier(record),
3697
3896
  supersedes_id: record.supersedes_id ?? null,
3698
3897
  draft: record.draft ? 1 : 0,
3699
- memory_type: record.memory_type ?? "raw",
3898
+ memory_type: memoryType,
3700
3899
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3701
3900
  content_hash: contentHash,
3702
3901
  intent: record.intent ?? null,
@@ -3855,6 +4054,7 @@ async function flushBatch() {
3855
4054
  const globalClient = getClient();
3856
4055
  const globalStmts = batch.map(buildStmt);
3857
4056
  await globalClient.batch(globalStmts, "write");
4057
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3858
4058
  _pendingRecords.splice(0, batch.length);
3859
4059
  try {
3860
4060
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -2209,6 +2209,14 @@ async function ensureSchema() {
2209
2209
  );
2210
2210
  } catch {
2211
2211
  }
2212
+ try {
2213
+ await client.execute(
2214
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2215
+ ON memories(content_hash, agent_id, project_name, memory_type)
2216
+ WHERE content_hash IS NOT NULL`
2217
+ );
2218
+ } catch {
2219
+ }
2212
2220
  await client.executeMultiple(`
2213
2221
  CREATE TABLE IF NOT EXISTS entities (
2214
2222
  id TEXT PRIMARY KEY,
@@ -2878,7 +2886,8 @@ async function ensureShardSchema(client) {
2878
2886
  }
2879
2887
  for (const idx of [
2880
2888
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2881
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
2889
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2890
+ "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"
2882
2891
  ]) {
2883
2892
  try {
2884
2893
  await client.execute(idx);
@@ -3288,7 +3297,6 @@ ${p.content}`).join("\n\n");
3288
3297
  // src/lib/store.ts
3289
3298
  init_memory();
3290
3299
  init_database();
3291
- import { createHash } from "crypto";
3292
3300
 
3293
3301
  // src/lib/keychain.ts
3294
3302
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -3521,6 +3529,9 @@ var StateBus = class {
3521
3529
  };
3522
3530
  var orgBus = new StateBus();
3523
3531
 
3532
+ // src/lib/memory-write-governor.ts
3533
+ import { createHash } from "crypto";
3534
+
3524
3535
  // src/lib/store.ts
3525
3536
  var INIT_MAX_RETRIES = 3;
3526
3537
  var INIT_RETRY_DELAY_MS = 1e3;