@askexenow/exe-os 0.9.33 → 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 (76) hide show
  1. package/dist/bin/backfill-conversations.js +228 -12
  2. package/dist/bin/backfill-responses.js +228 -12
  3. package/dist/bin/backfill-vectors.js +31 -4
  4. package/dist/bin/cleanup-stale-review-tasks.js +235 -12
  5. package/dist/bin/cli.js +265 -22
  6. package/dist/bin/exe-assign.js +228 -12
  7. package/dist/bin/exe-boot.js +56 -13
  8. package/dist/bin/exe-cloud.js +3 -3
  9. package/dist/bin/exe-dispatch.js +243 -15
  10. package/dist/bin/exe-doctor.js +34 -9
  11. package/dist/bin/exe-export-behaviors.js +235 -12
  12. package/dist/bin/exe-forget.js +244 -18
  13. package/dist/bin/exe-gateway.js +243 -15
  14. package/dist/bin/exe-heartbeat.js +235 -12
  15. package/dist/bin/exe-kill.js +235 -12
  16. package/dist/bin/exe-launch-agent.js +126 -4
  17. package/dist/bin/exe-link.js +28 -5
  18. package/dist/bin/exe-pending-messages.js +235 -12
  19. package/dist/bin/exe-pending-notifications.js +235 -12
  20. package/dist/bin/exe-pending-reviews.js +235 -12
  21. package/dist/bin/exe-rename.js +26 -2
  22. package/dist/bin/exe-review.js +235 -12
  23. package/dist/bin/exe-search.js +235 -12
  24. package/dist/bin/exe-session-cleanup.js +243 -15
  25. package/dist/bin/exe-settings.js +1 -1
  26. package/dist/bin/exe-start-codex.js +126 -4
  27. package/dist/bin/exe-start-opencode.js +126 -4
  28. package/dist/bin/exe-status.js +235 -12
  29. package/dist/bin/exe-team.js +235 -12
  30. package/dist/bin/git-sweep.js +243 -15
  31. package/dist/bin/graph-backfill.js +110 -4
  32. package/dist/bin/graph-export.js +235 -12
  33. package/dist/bin/intercom-check.js +243 -15
  34. package/dist/bin/scan-tasks.js +243 -15
  35. package/dist/bin/setup.js +32 -9
  36. package/dist/bin/shard-migrate.js +110 -4
  37. package/dist/gateway/index.js +243 -15
  38. package/dist/hooks/bug-report-worker.js +243 -15
  39. package/dist/hooks/codex-stop-task-finalizer.js +238 -14
  40. package/dist/hooks/commit-complete.js +243 -15
  41. package/dist/hooks/error-recall.js +244 -12
  42. package/dist/hooks/exe-heartbeat-hook.js +9 -0
  43. package/dist/hooks/ingest.js +244 -12
  44. package/dist/hooks/instructions-loaded.js +244 -12
  45. package/dist/hooks/notification.js +244 -12
  46. package/dist/hooks/post-compact.js +247 -13
  47. package/dist/hooks/post-tool-combined.js +251 -12
  48. package/dist/hooks/pre-compact.js +255 -16
  49. package/dist/hooks/pre-tool-use.js +247 -13
  50. package/dist/hooks/prompt-submit.js +255 -16
  51. package/dist/hooks/session-end.js +255 -16
  52. package/dist/hooks/session-start.js +251 -12
  53. package/dist/hooks/stop.js +247 -13
  54. package/dist/hooks/subagent-stop.js +247 -13
  55. package/dist/hooks/summary-worker.js +36 -8
  56. package/dist/index.js +243 -15
  57. package/dist/lib/cloud-sync.js +28 -5
  58. package/dist/lib/consolidation.js +3 -1
  59. package/dist/lib/database.js +25 -1
  60. package/dist/lib/db.js +25 -1
  61. package/dist/lib/device-registry.js +26 -2
  62. package/dist/lib/exe-daemon.js +22905 -9125
  63. package/dist/lib/hybrid-search.js +235 -12
  64. package/dist/lib/schedules.js +31 -4
  65. package/dist/lib/store.js +228 -12
  66. package/dist/lib/tasks.js +8 -3
  67. package/dist/lib/tmux-routing.js +8 -3
  68. package/dist/mcp/server.js +411 -164
  69. package/dist/mcp/tools/create-task.js +20 -4
  70. package/dist/mcp/tools/deactivate-behavior.js +9 -0
  71. package/dist/mcp/tools/list-tasks.js +12 -1
  72. package/dist/mcp/tools/send-message.js +12 -1
  73. package/dist/mcp/tools/update-task.js +24 -3
  74. package/dist/runtime/index.js +243 -15
  75. package/dist/tui/App.js +243 -15
  76. package/package.json +1 -1
@@ -1582,6 +1582,7 @@ var init_db_daemon_client = __esm({
1582
1582
  // src/lib/database.ts
1583
1583
  var database_exports = {};
1584
1584
  __export(database_exports, {
1585
+ SOFT_DELETE_RETENTION_DAYS: () => SOFT_DELETE_RETENTION_DAYS,
1585
1586
  disposeDatabase: () => disposeDatabase,
1586
1587
  disposeTurso: () => disposeTurso,
1587
1588
  ensureSchema: () => ensureSchema,
@@ -1738,10 +1739,17 @@ async function ensureSchema() {
1738
1739
  INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1739
1740
  END;
1740
1741
 
1741
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
1742
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories
1743
+ WHEN new.status IS NULL OR new.status != 'deleted' BEGIN
1742
1744
  INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1743
1745
  INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1744
1746
  END;
1747
+
1748
+ -- Soft-delete trigger: remove from FTS when status changes to 'deleted'
1749
+ CREATE TRIGGER IF NOT EXISTS memories_fts_soft_delete AFTER UPDATE ON memories
1750
+ WHEN new.status = 'deleted' AND (old.status IS NULL OR old.status != 'deleted') BEGIN
1751
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1752
+ END;
1745
1753
  `);
1746
1754
  await client.executeMultiple(`
1747
1755
  CREATE TABLE IF NOT EXISTS sync_meta (
@@ -2144,6 +2152,13 @@ async function ensureSchema() {
2144
2152
  });
2145
2153
  } catch {
2146
2154
  }
2155
+ try {
2156
+ await client.execute({
2157
+ sql: `ALTER TABLE memories ADD COLUMN deleted_at TEXT`,
2158
+ args: []
2159
+ });
2160
+ } catch {
2161
+ }
2147
2162
  try {
2148
2163
  await client.execute({
2149
2164
  sql: `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`,
@@ -2194,6 +2209,14 @@ async function ensureSchema() {
2194
2209
  );
2195
2210
  } catch {
2196
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
+ }
2197
2220
  await client.executeMultiple(`
2198
2221
  CREATE TABLE IF NOT EXISTS entities (
2199
2222
  id TEXT PRIMARY KEY,
@@ -2680,7 +2703,7 @@ async function disposeDatabase() {
2680
2703
  _resilientClient = null;
2681
2704
  }
2682
2705
  }
2683
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2706
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
2684
2707
  var init_database = __esm({
2685
2708
  "src/lib/database.ts"() {
2686
2709
  "use strict";
@@ -2694,6 +2717,7 @@ var init_database = __esm({
2694
2717
  _daemonClient = null;
2695
2718
  _adapterClient = null;
2696
2719
  initTurso = initDatabase;
2720
+ SOFT_DELETE_RETENTION_DAYS = 7;
2697
2721
  disposeTurso = disposeDatabase;
2698
2722
  }
2699
2723
  });
@@ -2862,7 +2886,8 @@ async function ensureShardSchema(client) {
2862
2886
  }
2863
2887
  for (const idx of [
2864
2888
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2865
- "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"
2866
2891
  ]) {
2867
2892
  try {
2868
2893
  await client.execute(idx);
@@ -3272,7 +3297,6 @@ ${p.content}`).join("\n\n");
3272
3297
  // src/lib/store.ts
3273
3298
  init_memory();
3274
3299
  init_database();
3275
- import { createHash } from "crypto";
3276
3300
 
3277
3301
  // src/lib/keychain.ts
3278
3302
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -3505,6 +3529,9 @@ var StateBus = class {
3505
3529
  };
3506
3530
  var orgBus = new StateBus();
3507
3531
 
3532
+ // src/lib/memory-write-governor.ts
3533
+ import { createHash } from "crypto";
3534
+
3508
3535
  // src/lib/store.ts
3509
3536
  var INIT_MAX_RETRIES = 3;
3510
3537
  var INIT_RETRY_DELAY_MS = 1e3;
@@ -1672,6 +1672,7 @@ var init_db_daemon_client = __esm({
1672
1672
  // src/lib/database.ts
1673
1673
  var database_exports = {};
1674
1674
  __export(database_exports, {
1675
+ SOFT_DELETE_RETENTION_DAYS: () => SOFT_DELETE_RETENTION_DAYS,
1675
1676
  disposeDatabase: () => disposeDatabase,
1676
1677
  disposeTurso: () => disposeTurso,
1677
1678
  ensureSchema: () => ensureSchema,
@@ -1828,10 +1829,17 @@ async function ensureSchema() {
1828
1829
  INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1829
1830
  END;
1830
1831
 
1831
- CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories BEGIN
1832
+ CREATE TRIGGER IF NOT EXISTS memories_fts_au AFTER UPDATE ON memories
1833
+ WHEN new.status IS NULL OR new.status != 'deleted' BEGIN
1832
1834
  INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1833
1835
  INSERT INTO memories_fts(rowid, raw_text) VALUES (new.rowid, new.raw_text);
1834
1836
  END;
1837
+
1838
+ -- Soft-delete trigger: remove from FTS when status changes to 'deleted'
1839
+ CREATE TRIGGER IF NOT EXISTS memories_fts_soft_delete AFTER UPDATE ON memories
1840
+ WHEN new.status = 'deleted' AND (old.status IS NULL OR old.status != 'deleted') BEGIN
1841
+ INSERT INTO memories_fts(memories_fts, rowid, raw_text) VALUES('delete', old.rowid, old.raw_text);
1842
+ END;
1835
1843
  `);
1836
1844
  await client.executeMultiple(`
1837
1845
  CREATE TABLE IF NOT EXISTS sync_meta (
@@ -2234,6 +2242,13 @@ async function ensureSchema() {
2234
2242
  });
2235
2243
  } catch {
2236
2244
  }
2245
+ try {
2246
+ await client.execute({
2247
+ sql: `ALTER TABLE memories ADD COLUMN deleted_at TEXT`,
2248
+ args: []
2249
+ });
2250
+ } catch {
2251
+ }
2237
2252
  try {
2238
2253
  await client.execute({
2239
2254
  sql: `ALTER TABLE memories ADD COLUMN confidence REAL DEFAULT 0.7`,
@@ -2284,6 +2299,14 @@ async function ensureSchema() {
2284
2299
  );
2285
2300
  } catch {
2286
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
+ }
2287
2310
  await client.executeMultiple(`
2288
2311
  CREATE TABLE IF NOT EXISTS entities (
2289
2312
  id TEXT PRIMARY KEY,
@@ -2770,7 +2793,7 @@ async function disposeDatabase() {
2770
2793
  _resilientClient = null;
2771
2794
  }
2772
2795
  }
2773
- var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, disposeTurso;
2796
+ var _client, _resilientClient, _walCheckpointTimer, _daemonClient, _adapterClient, initTurso, SOFT_DELETE_RETENTION_DAYS, disposeTurso;
2774
2797
  var init_database = __esm({
2775
2798
  "src/lib/database.ts"() {
2776
2799
  "use strict";
@@ -2784,6 +2807,7 @@ var init_database = __esm({
2784
2807
  _daemonClient = null;
2785
2808
  _adapterClient = null;
2786
2809
  initTurso = initDatabase;
2810
+ SOFT_DELETE_RETENTION_DAYS = 7;
2787
2811
  disposeTurso = disposeDatabase;
2788
2812
  }
2789
2813
  });
@@ -3028,6 +3052,196 @@ var init_state_bus = __esm({
3028
3052
  }
3029
3053
  });
3030
3054
 
3055
+ // src/lib/memory-write-governor.ts
3056
+ import { createHash } from "crypto";
3057
+ function normalizeMemoryText(text) {
3058
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3059
+ }
3060
+ function classifyMemoryType(input) {
3061
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3062
+ const tool = input.tool_name.toLowerCase();
3063
+ const text = input.raw_text.toLowerCase();
3064
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3065
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3066
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3067
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3068
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3069
+ if (tool === "store_memory" || tool === "manual") return "observation";
3070
+ return "raw";
3071
+ }
3072
+ function shouldDropMemory(text) {
3073
+ const normalized = normalizeMemoryText(text);
3074
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3075
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3076
+ return { drop: true, reason: "known_boilerplate_noise" };
3077
+ }
3078
+ return { drop: false };
3079
+ }
3080
+ function shouldSkipEmbedding(input) {
3081
+ const type = classifyMemoryType(input);
3082
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3083
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3084
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3085
+ return false;
3086
+ }
3087
+ function hashMemoryContent(text) {
3088
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3089
+ }
3090
+ function scopedDedupArgs(input) {
3091
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3092
+ }
3093
+ function governMemoryRecord(record) {
3094
+ const normalized = normalizeMemoryText(record.raw_text);
3095
+ const memoryType = classifyMemoryType({
3096
+ raw_text: normalized,
3097
+ agent_id: record.agent_id,
3098
+ project_name: record.project_name,
3099
+ tool_name: record.tool_name,
3100
+ memory_type: record.memory_type
3101
+ });
3102
+ const drop = shouldDropMemory(normalized);
3103
+ const skipEmbedding = shouldSkipEmbedding({
3104
+ raw_text: normalized,
3105
+ agent_id: record.agent_id,
3106
+ project_name: record.project_name,
3107
+ tool_name: record.tool_name,
3108
+ memory_type: memoryType
3109
+ });
3110
+ return {
3111
+ record: {
3112
+ ...record,
3113
+ raw_text: normalized,
3114
+ memory_type: memoryType,
3115
+ vector: skipEmbedding ? null : record.vector
3116
+ },
3117
+ contentHash: hashMemoryContent(normalized),
3118
+ shouldDrop: drop.drop,
3119
+ dropReason: drop.reason,
3120
+ skipEmbedding,
3121
+ hygiene: {
3122
+ dedup: true,
3123
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3124
+ }
3125
+ };
3126
+ }
3127
+ async function findScopedDuplicate(input) {
3128
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3129
+ const client = getClient2();
3130
+ const args = scopedDedupArgs(input);
3131
+ let sql = `SELECT id FROM memories
3132
+ WHERE content_hash = ?
3133
+ AND agent_id = ?
3134
+ AND project_name = ?
3135
+ AND COALESCE(memory_type, 'raw') = ?
3136
+ AND COALESCE(status, 'active') != 'deleted'`;
3137
+ if (input.excludeId) {
3138
+ sql += " AND id != ?";
3139
+ args.push(input.excludeId);
3140
+ }
3141
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3142
+ const result = await client.execute({ sql, args });
3143
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3144
+ }
3145
+ async function runPostWriteMemoryHygiene(memoryId) {
3146
+ try {
3147
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3148
+ const client = getClient2();
3149
+ const current = await client.execute({
3150
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3151
+ importance, timestamp
3152
+ FROM memories
3153
+ WHERE id = ?
3154
+ LIMIT 1`,
3155
+ args: [memoryId]
3156
+ });
3157
+ const row = current.rows[0];
3158
+ if (!row) return;
3159
+ const memoryType = String(row.memory_type ?? "raw");
3160
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3161
+ const agentId = String(row.agent_id);
3162
+ const projectName = String(row.project_name);
3163
+ if (contentHash) {
3164
+ await client.execute({
3165
+ sql: `UPDATE memories
3166
+ SET status = 'deleted',
3167
+ outcome = COALESCE(outcome, 'superseded')
3168
+ WHERE id != ?
3169
+ AND content_hash = ?
3170
+ AND agent_id = ?
3171
+ AND project_name = ?
3172
+ AND COALESCE(memory_type, 'raw') = ?
3173
+ AND COALESCE(status, 'active') = 'active'`,
3174
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3175
+ });
3176
+ }
3177
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3178
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3179
+ const old = await client.execute({
3180
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3181
+ args: [supersedesId]
3182
+ });
3183
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3184
+ const newImportance = Number(row.importance ?? 0);
3185
+ await client.batch([
3186
+ {
3187
+ sql: `UPDATE memories
3188
+ SET status = 'archived',
3189
+ outcome = COALESCE(outcome, 'superseded')
3190
+ WHERE id = ?`,
3191
+ args: [supersedesId]
3192
+ },
3193
+ {
3194
+ sql: `UPDATE memories
3195
+ SET importance = MAX(COALESCE(importance, 5), ?),
3196
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3197
+ WHERE id = ?`,
3198
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3199
+ }
3200
+ ], "write");
3201
+ }
3202
+ } catch (err) {
3203
+ process.stderr.write(
3204
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3205
+ `
3206
+ );
3207
+ }
3208
+ }
3209
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3210
+ if (memoryIds.length === 0) return;
3211
+ const run = () => {
3212
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3213
+ };
3214
+ if (typeof setImmediate === "function") setImmediate(run);
3215
+ else setTimeout(run, 0);
3216
+ }
3217
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3218
+ var init_memory_write_governor = __esm({
3219
+ "src/lib/memory-write-governor.ts"() {
3220
+ "use strict";
3221
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3222
+ "decision",
3223
+ "adr",
3224
+ "behavior",
3225
+ "procedure"
3226
+ ]);
3227
+ NOISE_DROP_PATTERNS = [
3228
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3229
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3230
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3231
+ /^\s*Intercom is a speedup, not delivery/im,
3232
+ /^\s*Context bar reads as USAGE not remaining/im
3233
+ ];
3234
+ SKIP_EMBED_PATTERNS = [
3235
+ /tmux capture-pane\b/i,
3236
+ /docker ps\b/i,
3237
+ /docker images\b/i,
3238
+ /git status\b/i,
3239
+ /grep .*node_modules/i,
3240
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3241
+ ];
3242
+ }
3243
+ });
3244
+
3031
3245
  // src/lib/shard-manager.ts
3032
3246
  var shard_manager_exports = {};
3033
3247
  __export(shard_manager_exports, {
@@ -3192,7 +3406,8 @@ async function ensureShardSchema(client) {
3192
3406
  }
3193
3407
  for (const idx of [
3194
3408
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3195
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3409
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3410
+ "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"
3196
3411
  ]) {
3197
3412
  try {
3198
3413
  await client.execute(idx);
@@ -3617,7 +3832,6 @@ __export(store_exports, {
3617
3832
  vectorToBlob: () => vectorToBlob,
3618
3833
  writeMemory: () => writeMemory
3619
3834
  });
3620
- import { createHash } from "crypto";
3621
3835
  function isBusyError2(err) {
3622
3836
  if (err instanceof Error) {
3623
3837
  const msg = err.message.toLowerCase();
@@ -3731,17 +3945,24 @@ async function writeMemory(record) {
3731
3945
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3732
3946
  );
3733
3947
  }
3734
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3735
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3948
+ const governed = governMemoryRecord(record);
3949
+ if (governed.shouldDrop) return;
3950
+ record = governed.record;
3951
+ const contentHash = governed.contentHash;
3952
+ const memoryType = record.memory_type ?? "raw";
3953
+ if (_pendingRecords.some(
3954
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3955
+ )) {
3736
3956
  return;
3737
3957
  }
3738
3958
  try {
3739
- const client = getClient();
3740
- const existing = await client.execute({
3741
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3742
- args: [contentHash, record.agent_id]
3959
+ const existing = await findScopedDuplicate({
3960
+ contentHash,
3961
+ agentId: record.agent_id,
3962
+ projectName: record.project_name,
3963
+ memoryType
3743
3964
  });
3744
- if (existing.rows.length > 0) return;
3965
+ if (existing) return;
3745
3966
  } catch {
3746
3967
  }
3747
3968
  const dbRow = {
@@ -3772,7 +3993,7 @@ async function writeMemory(record) {
3772
3993
  tier: record.tier ?? classifyTier(record),
3773
3994
  supersedes_id: record.supersedes_id ?? null,
3774
3995
  draft: record.draft ? 1 : 0,
3775
- memory_type: record.memory_type ?? "raw",
3996
+ memory_type: memoryType,
3776
3997
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3777
3998
  content_hash: contentHash,
3778
3999
  intent: record.intent ?? null,
@@ -3931,6 +4152,7 @@ async function flushBatch() {
3931
4152
  const globalClient = getClient();
3932
4153
  const globalStmts = batch.map(buildStmt);
3933
4154
  await globalClient.batch(globalStmts, "write");
4155
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3934
4156
  _pendingRecords.splice(0, batch.length);
3935
4157
  try {
3936
4158
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4189,6 +4411,7 @@ var init_store = __esm({
4189
4411
  init_keychain();
4190
4412
  init_config();
4191
4413
  init_state_bus();
4414
+ init_memory_write_governor();
4192
4415
  INIT_MAX_RETRIES = 3;
4193
4416
  INIT_RETRY_DELAY_MS = 1e3;
4194
4417
  _pendingRecords = [];