@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
@@ -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,
@@ -3557,6 +3565,196 @@ var init_keychain = __esm({
3557
3565
  }
3558
3566
  });
3559
3567
 
3568
+ // src/lib/memory-write-governor.ts
3569
+ import { createHash } from "crypto";
3570
+ function normalizeMemoryText(text) {
3571
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3572
+ }
3573
+ function classifyMemoryType(input) {
3574
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3575
+ const tool = input.tool_name.toLowerCase();
3576
+ const text = input.raw_text.toLowerCase();
3577
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3578
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3579
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3580
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3581
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3582
+ if (tool === "store_memory" || tool === "manual") return "observation";
3583
+ return "raw";
3584
+ }
3585
+ function shouldDropMemory(text) {
3586
+ const normalized = normalizeMemoryText(text);
3587
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3588
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3589
+ return { drop: true, reason: "known_boilerplate_noise" };
3590
+ }
3591
+ return { drop: false };
3592
+ }
3593
+ function shouldSkipEmbedding(input) {
3594
+ const type = classifyMemoryType(input);
3595
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3596
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3597
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3598
+ return false;
3599
+ }
3600
+ function hashMemoryContent(text) {
3601
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3602
+ }
3603
+ function scopedDedupArgs(input) {
3604
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3605
+ }
3606
+ function governMemoryRecord(record) {
3607
+ const normalized = normalizeMemoryText(record.raw_text);
3608
+ const memoryType = classifyMemoryType({
3609
+ raw_text: normalized,
3610
+ agent_id: record.agent_id,
3611
+ project_name: record.project_name,
3612
+ tool_name: record.tool_name,
3613
+ memory_type: record.memory_type
3614
+ });
3615
+ const drop = shouldDropMemory(normalized);
3616
+ const skipEmbedding = shouldSkipEmbedding({
3617
+ raw_text: normalized,
3618
+ agent_id: record.agent_id,
3619
+ project_name: record.project_name,
3620
+ tool_name: record.tool_name,
3621
+ memory_type: memoryType
3622
+ });
3623
+ return {
3624
+ record: {
3625
+ ...record,
3626
+ raw_text: normalized,
3627
+ memory_type: memoryType,
3628
+ vector: skipEmbedding ? null : record.vector
3629
+ },
3630
+ contentHash: hashMemoryContent(normalized),
3631
+ shouldDrop: drop.drop,
3632
+ dropReason: drop.reason,
3633
+ skipEmbedding,
3634
+ hygiene: {
3635
+ dedup: true,
3636
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3637
+ }
3638
+ };
3639
+ }
3640
+ async function findScopedDuplicate(input) {
3641
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3642
+ const client = getClient2();
3643
+ const args = scopedDedupArgs(input);
3644
+ let sql = `SELECT id FROM memories
3645
+ WHERE content_hash = ?
3646
+ AND agent_id = ?
3647
+ AND project_name = ?
3648
+ AND COALESCE(memory_type, 'raw') = ?
3649
+ AND COALESCE(status, 'active') != 'deleted'`;
3650
+ if (input.excludeId) {
3651
+ sql += " AND id != ?";
3652
+ args.push(input.excludeId);
3653
+ }
3654
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3655
+ const result = await client.execute({ sql, args });
3656
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3657
+ }
3658
+ async function runPostWriteMemoryHygiene(memoryId) {
3659
+ try {
3660
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3661
+ const client = getClient2();
3662
+ const current = await client.execute({
3663
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3664
+ importance, timestamp
3665
+ FROM memories
3666
+ WHERE id = ?
3667
+ LIMIT 1`,
3668
+ args: [memoryId]
3669
+ });
3670
+ const row = current.rows[0];
3671
+ if (!row) return;
3672
+ const memoryType = String(row.memory_type ?? "raw");
3673
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3674
+ const agentId = String(row.agent_id);
3675
+ const projectName = String(row.project_name);
3676
+ if (contentHash) {
3677
+ await client.execute({
3678
+ sql: `UPDATE memories
3679
+ SET status = 'deleted',
3680
+ outcome = COALESCE(outcome, 'superseded')
3681
+ WHERE id != ?
3682
+ AND content_hash = ?
3683
+ AND agent_id = ?
3684
+ AND project_name = ?
3685
+ AND COALESCE(memory_type, 'raw') = ?
3686
+ AND COALESCE(status, 'active') = 'active'`,
3687
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3688
+ });
3689
+ }
3690
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3691
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3692
+ const old = await client.execute({
3693
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3694
+ args: [supersedesId]
3695
+ });
3696
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3697
+ const newImportance = Number(row.importance ?? 0);
3698
+ await client.batch([
3699
+ {
3700
+ sql: `UPDATE memories
3701
+ SET status = 'archived',
3702
+ outcome = COALESCE(outcome, 'superseded')
3703
+ WHERE id = ?`,
3704
+ args: [supersedesId]
3705
+ },
3706
+ {
3707
+ sql: `UPDATE memories
3708
+ SET importance = MAX(COALESCE(importance, 5), ?),
3709
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3710
+ WHERE id = ?`,
3711
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3712
+ }
3713
+ ], "write");
3714
+ }
3715
+ } catch (err) {
3716
+ process.stderr.write(
3717
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3718
+ `
3719
+ );
3720
+ }
3721
+ }
3722
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3723
+ if (memoryIds.length === 0) return;
3724
+ const run = () => {
3725
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3726
+ };
3727
+ if (typeof setImmediate === "function") setImmediate(run);
3728
+ else setTimeout(run, 0);
3729
+ }
3730
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3731
+ var init_memory_write_governor = __esm({
3732
+ "src/lib/memory-write-governor.ts"() {
3733
+ "use strict";
3734
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3735
+ "decision",
3736
+ "adr",
3737
+ "behavior",
3738
+ "procedure"
3739
+ ]);
3740
+ NOISE_DROP_PATTERNS = [
3741
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3742
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3743
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3744
+ /^\s*Intercom is a speedup, not delivery/im,
3745
+ /^\s*Context bar reads as USAGE not remaining/im
3746
+ ];
3747
+ SKIP_EMBED_PATTERNS = [
3748
+ /tmux capture-pane\b/i,
3749
+ /docker ps\b/i,
3750
+ /docker images\b/i,
3751
+ /git status\b/i,
3752
+ /grep .*node_modules/i,
3753
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3754
+ ];
3755
+ }
3756
+ });
3757
+
3560
3758
  // src/lib/shard-manager.ts
3561
3759
  var shard_manager_exports = {};
3562
3760
  __export(shard_manager_exports, {
@@ -3721,7 +3919,8 @@ async function ensureShardSchema(client) {
3721
3919
  }
3722
3920
  for (const idx of [
3723
3921
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3724
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3922
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3923
+ "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"
3725
3924
  ]) {
3726
3925
  try {
3727
3926
  await client.execute(idx);
@@ -4146,7 +4345,6 @@ __export(store_exports, {
4146
4345
  vectorToBlob: () => vectorToBlob,
4147
4346
  writeMemory: () => writeMemory
4148
4347
  });
4149
- import { createHash } from "crypto";
4150
4348
  function isBusyError2(err) {
4151
4349
  if (err instanceof Error) {
4152
4350
  const msg = err.message.toLowerCase();
@@ -4260,17 +4458,24 @@ async function writeMemory(record) {
4260
4458
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4261
4459
  );
4262
4460
  }
4263
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4264
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4461
+ const governed = governMemoryRecord(record);
4462
+ if (governed.shouldDrop) return;
4463
+ record = governed.record;
4464
+ const contentHash = governed.contentHash;
4465
+ const memoryType = record.memory_type ?? "raw";
4466
+ if (_pendingRecords.some(
4467
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4468
+ )) {
4265
4469
  return;
4266
4470
  }
4267
4471
  try {
4268
- const client = getClient();
4269
- const existing = await client.execute({
4270
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4271
- args: [contentHash, record.agent_id]
4472
+ const existing = await findScopedDuplicate({
4473
+ contentHash,
4474
+ agentId: record.agent_id,
4475
+ projectName: record.project_name,
4476
+ memoryType
4272
4477
  });
4273
- if (existing.rows.length > 0) return;
4478
+ if (existing) return;
4274
4479
  } catch {
4275
4480
  }
4276
4481
  const dbRow = {
@@ -4301,7 +4506,7 @@ async function writeMemory(record) {
4301
4506
  tier: record.tier ?? classifyTier(record),
4302
4507
  supersedes_id: record.supersedes_id ?? null,
4303
4508
  draft: record.draft ? 1 : 0,
4304
- memory_type: record.memory_type ?? "raw",
4509
+ memory_type: memoryType,
4305
4510
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4306
4511
  content_hash: contentHash,
4307
4512
  intent: record.intent ?? null,
@@ -4460,6 +4665,7 @@ async function flushBatch() {
4460
4665
  const globalClient = getClient();
4461
4666
  const globalStmts = batch.map(buildStmt);
4462
4667
  await globalClient.batch(globalStmts, "write");
4668
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4463
4669
  _pendingRecords.splice(0, batch.length);
4464
4670
  try {
4465
4671
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4718,6 +4924,7 @@ var init_store = __esm({
4718
4924
  init_keychain();
4719
4925
  init_config();
4720
4926
  init_state_bus();
4927
+ init_memory_write_governor();
4721
4928
  INIT_MAX_RETRIES = 3;
4722
4929
  INIT_RETRY_DELAY_MS = 1e3;
4723
4930
  _pendingRecords = [];
@@ -2037,6 +2037,14 @@ async function ensureSchema() {
2037
2037
  );
2038
2038
  } catch {
2039
2039
  }
2040
+ try {
2041
+ await client.execute(
2042
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2043
+ ON memories(content_hash, agent_id, project_name, memory_type)
2044
+ WHERE content_hash IS NOT NULL`
2045
+ );
2046
+ } catch {
2047
+ }
2040
2048
  await client.executeMultiple(`
2041
2049
  CREATE TABLE IF NOT EXISTS entities (
2042
2050
  id TEXT PRIMARY KEY,
@@ -2302,6 +2302,14 @@ async function ensureSchema() {
2302
2302
  );
2303
2303
  } catch {
2304
2304
  }
2305
+ try {
2306
+ await client.execute(
2307
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2308
+ ON memories(content_hash, agent_id, project_name, memory_type)
2309
+ WHERE content_hash IS NOT NULL`
2310
+ );
2311
+ } catch {
2312
+ }
2305
2313
  await client.executeMultiple(`
2306
2314
  CREATE TABLE IF NOT EXISTS entities (
2307
2315
  id TEXT PRIMARY KEY,
@@ -3047,6 +3055,196 @@ var init_state_bus = __esm({
3047
3055
  }
3048
3056
  });
3049
3057
 
3058
+ // src/lib/memory-write-governor.ts
3059
+ import { createHash } from "crypto";
3060
+ function normalizeMemoryText(text) {
3061
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3062
+ }
3063
+ function classifyMemoryType(input) {
3064
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3065
+ const tool = input.tool_name.toLowerCase();
3066
+ const text = input.raw_text.toLowerCase();
3067
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3068
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3069
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3070
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3071
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3072
+ if (tool === "store_memory" || tool === "manual") return "observation";
3073
+ return "raw";
3074
+ }
3075
+ function shouldDropMemory(text) {
3076
+ const normalized = normalizeMemoryText(text);
3077
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3078
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3079
+ return { drop: true, reason: "known_boilerplate_noise" };
3080
+ }
3081
+ return { drop: false };
3082
+ }
3083
+ function shouldSkipEmbedding(input) {
3084
+ const type = classifyMemoryType(input);
3085
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3086
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3087
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3088
+ return false;
3089
+ }
3090
+ function hashMemoryContent(text) {
3091
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3092
+ }
3093
+ function scopedDedupArgs(input) {
3094
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3095
+ }
3096
+ function governMemoryRecord(record) {
3097
+ const normalized = normalizeMemoryText(record.raw_text);
3098
+ const memoryType = classifyMemoryType({
3099
+ raw_text: normalized,
3100
+ agent_id: record.agent_id,
3101
+ project_name: record.project_name,
3102
+ tool_name: record.tool_name,
3103
+ memory_type: record.memory_type
3104
+ });
3105
+ const drop = shouldDropMemory(normalized);
3106
+ const skipEmbedding = shouldSkipEmbedding({
3107
+ raw_text: normalized,
3108
+ agent_id: record.agent_id,
3109
+ project_name: record.project_name,
3110
+ tool_name: record.tool_name,
3111
+ memory_type: memoryType
3112
+ });
3113
+ return {
3114
+ record: {
3115
+ ...record,
3116
+ raw_text: normalized,
3117
+ memory_type: memoryType,
3118
+ vector: skipEmbedding ? null : record.vector
3119
+ },
3120
+ contentHash: hashMemoryContent(normalized),
3121
+ shouldDrop: drop.drop,
3122
+ dropReason: drop.reason,
3123
+ skipEmbedding,
3124
+ hygiene: {
3125
+ dedup: true,
3126
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3127
+ }
3128
+ };
3129
+ }
3130
+ async function findScopedDuplicate(input) {
3131
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3132
+ const client = getClient2();
3133
+ const args = scopedDedupArgs(input);
3134
+ let sql = `SELECT id FROM memories
3135
+ WHERE content_hash = ?
3136
+ AND agent_id = ?
3137
+ AND project_name = ?
3138
+ AND COALESCE(memory_type, 'raw') = ?
3139
+ AND COALESCE(status, 'active') != 'deleted'`;
3140
+ if (input.excludeId) {
3141
+ sql += " AND id != ?";
3142
+ args.push(input.excludeId);
3143
+ }
3144
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3145
+ const result = await client.execute({ sql, args });
3146
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3147
+ }
3148
+ async function runPostWriteMemoryHygiene(memoryId) {
3149
+ try {
3150
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3151
+ const client = getClient2();
3152
+ const current = await client.execute({
3153
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3154
+ importance, timestamp
3155
+ FROM memories
3156
+ WHERE id = ?
3157
+ LIMIT 1`,
3158
+ args: [memoryId]
3159
+ });
3160
+ const row = current.rows[0];
3161
+ if (!row) return;
3162
+ const memoryType = String(row.memory_type ?? "raw");
3163
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3164
+ const agentId = String(row.agent_id);
3165
+ const projectName = String(row.project_name);
3166
+ if (contentHash) {
3167
+ await client.execute({
3168
+ sql: `UPDATE memories
3169
+ SET status = 'deleted',
3170
+ outcome = COALESCE(outcome, 'superseded')
3171
+ WHERE id != ?
3172
+ AND content_hash = ?
3173
+ AND agent_id = ?
3174
+ AND project_name = ?
3175
+ AND COALESCE(memory_type, 'raw') = ?
3176
+ AND COALESCE(status, 'active') = 'active'`,
3177
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3178
+ });
3179
+ }
3180
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3181
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3182
+ const old = await client.execute({
3183
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3184
+ args: [supersedesId]
3185
+ });
3186
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3187
+ const newImportance = Number(row.importance ?? 0);
3188
+ await client.batch([
3189
+ {
3190
+ sql: `UPDATE memories
3191
+ SET status = 'archived',
3192
+ outcome = COALESCE(outcome, 'superseded')
3193
+ WHERE id = ?`,
3194
+ args: [supersedesId]
3195
+ },
3196
+ {
3197
+ sql: `UPDATE memories
3198
+ SET importance = MAX(COALESCE(importance, 5), ?),
3199
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3200
+ WHERE id = ?`,
3201
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3202
+ }
3203
+ ], "write");
3204
+ }
3205
+ } catch (err) {
3206
+ process.stderr.write(
3207
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3208
+ `
3209
+ );
3210
+ }
3211
+ }
3212
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3213
+ if (memoryIds.length === 0) return;
3214
+ const run = () => {
3215
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3216
+ };
3217
+ if (typeof setImmediate === "function") setImmediate(run);
3218
+ else setTimeout(run, 0);
3219
+ }
3220
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3221
+ var init_memory_write_governor = __esm({
3222
+ "src/lib/memory-write-governor.ts"() {
3223
+ "use strict";
3224
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3225
+ "decision",
3226
+ "adr",
3227
+ "behavior",
3228
+ "procedure"
3229
+ ]);
3230
+ NOISE_DROP_PATTERNS = [
3231
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3232
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3233
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3234
+ /^\s*Intercom is a speedup, not delivery/im,
3235
+ /^\s*Context bar reads as USAGE not remaining/im
3236
+ ];
3237
+ SKIP_EMBED_PATTERNS = [
3238
+ /tmux capture-pane\b/i,
3239
+ /docker ps\b/i,
3240
+ /docker images\b/i,
3241
+ /git status\b/i,
3242
+ /grep .*node_modules/i,
3243
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3244
+ ];
3245
+ }
3246
+ });
3247
+
3050
3248
  // src/lib/shard-manager.ts
3051
3249
  var shard_manager_exports = {};
3052
3250
  __export(shard_manager_exports, {
@@ -3211,7 +3409,8 @@ async function ensureShardSchema(client) {
3211
3409
  }
3212
3410
  for (const idx of [
3213
3411
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3214
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3412
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3413
+ "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"
3215
3414
  ]) {
3216
3415
  try {
3217
3416
  await client.execute(idx);
@@ -3636,7 +3835,6 @@ __export(store_exports, {
3636
3835
  vectorToBlob: () => vectorToBlob,
3637
3836
  writeMemory: () => writeMemory
3638
3837
  });
3639
- import { createHash } from "crypto";
3640
3838
  function isBusyError2(err) {
3641
3839
  if (err instanceof Error) {
3642
3840
  const msg = err.message.toLowerCase();
@@ -3750,17 +3948,24 @@ async function writeMemory(record) {
3750
3948
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3751
3949
  );
3752
3950
  }
3753
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3754
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3951
+ const governed = governMemoryRecord(record);
3952
+ if (governed.shouldDrop) return;
3953
+ record = governed.record;
3954
+ const contentHash = governed.contentHash;
3955
+ const memoryType = record.memory_type ?? "raw";
3956
+ if (_pendingRecords.some(
3957
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3958
+ )) {
3755
3959
  return;
3756
3960
  }
3757
3961
  try {
3758
- const client = getClient();
3759
- const existing = await client.execute({
3760
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3761
- args: [contentHash, record.agent_id]
3962
+ const existing = await findScopedDuplicate({
3963
+ contentHash,
3964
+ agentId: record.agent_id,
3965
+ projectName: record.project_name,
3966
+ memoryType
3762
3967
  });
3763
- if (existing.rows.length > 0) return;
3968
+ if (existing) return;
3764
3969
  } catch {
3765
3970
  }
3766
3971
  const dbRow = {
@@ -3791,7 +3996,7 @@ async function writeMemory(record) {
3791
3996
  tier: record.tier ?? classifyTier(record),
3792
3997
  supersedes_id: record.supersedes_id ?? null,
3793
3998
  draft: record.draft ? 1 : 0,
3794
- memory_type: record.memory_type ?? "raw",
3999
+ memory_type: memoryType,
3795
4000
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3796
4001
  content_hash: contentHash,
3797
4002
  intent: record.intent ?? null,
@@ -3950,6 +4155,7 @@ async function flushBatch() {
3950
4155
  const globalClient = getClient();
3951
4156
  const globalStmts = batch.map(buildStmt);
3952
4157
  await globalClient.batch(globalStmts, "write");
4158
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3953
4159
  _pendingRecords.splice(0, batch.length);
3954
4160
  try {
3955
4161
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4208,6 +4414,7 @@ var init_store = __esm({
4208
4414
  init_keychain();
4209
4415
  init_config();
4210
4416
  init_state_bus();
4417
+ init_memory_write_governor();
4211
4418
  INIT_MAX_RETRIES = 3;
4212
4419
  INIT_RETRY_DELAY_MS = 1e3;
4213
4420
  _pendingRecords = [];