@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
@@ -2288,6 +2288,14 @@ async function ensureSchema() {
2288
2288
  );
2289
2289
  } catch {
2290
2290
  }
2291
+ try {
2292
+ await client.execute(
2293
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2294
+ ON memories(content_hash, agent_id, project_name, memory_type)
2295
+ WHERE content_hash IS NOT NULL`
2296
+ );
2297
+ } catch {
2298
+ }
2291
2299
  await client.executeMultiple(`
2292
2300
  CREATE TABLE IF NOT EXISTS entities (
2293
2301
  id TEXT PRIMARY KEY,
@@ -3033,6 +3041,196 @@ var init_state_bus = __esm({
3033
3041
  }
3034
3042
  });
3035
3043
 
3044
+ // src/lib/memory-write-governor.ts
3045
+ import { createHash } from "crypto";
3046
+ function normalizeMemoryText(text) {
3047
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3048
+ }
3049
+ function classifyMemoryType(input) {
3050
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3051
+ const tool = input.tool_name.toLowerCase();
3052
+ const text = input.raw_text.toLowerCase();
3053
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3054
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3055
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3056
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3057
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3058
+ if (tool === "store_memory" || tool === "manual") return "observation";
3059
+ return "raw";
3060
+ }
3061
+ function shouldDropMemory(text) {
3062
+ const normalized = normalizeMemoryText(text);
3063
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3064
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3065
+ return { drop: true, reason: "known_boilerplate_noise" };
3066
+ }
3067
+ return { drop: false };
3068
+ }
3069
+ function shouldSkipEmbedding(input) {
3070
+ const type = classifyMemoryType(input);
3071
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3072
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3073
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3074
+ return false;
3075
+ }
3076
+ function hashMemoryContent(text) {
3077
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3078
+ }
3079
+ function scopedDedupArgs(input) {
3080
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3081
+ }
3082
+ function governMemoryRecord(record) {
3083
+ const normalized = normalizeMemoryText(record.raw_text);
3084
+ const memoryType = classifyMemoryType({
3085
+ raw_text: normalized,
3086
+ agent_id: record.agent_id,
3087
+ project_name: record.project_name,
3088
+ tool_name: record.tool_name,
3089
+ memory_type: record.memory_type
3090
+ });
3091
+ const drop = shouldDropMemory(normalized);
3092
+ const skipEmbedding = shouldSkipEmbedding({
3093
+ raw_text: normalized,
3094
+ agent_id: record.agent_id,
3095
+ project_name: record.project_name,
3096
+ tool_name: record.tool_name,
3097
+ memory_type: memoryType
3098
+ });
3099
+ return {
3100
+ record: {
3101
+ ...record,
3102
+ raw_text: normalized,
3103
+ memory_type: memoryType,
3104
+ vector: skipEmbedding ? null : record.vector
3105
+ },
3106
+ contentHash: hashMemoryContent(normalized),
3107
+ shouldDrop: drop.drop,
3108
+ dropReason: drop.reason,
3109
+ skipEmbedding,
3110
+ hygiene: {
3111
+ dedup: true,
3112
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3113
+ }
3114
+ };
3115
+ }
3116
+ async function findScopedDuplicate(input) {
3117
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3118
+ const client = getClient2();
3119
+ const args = scopedDedupArgs(input);
3120
+ let sql = `SELECT id FROM memories
3121
+ WHERE content_hash = ?
3122
+ AND agent_id = ?
3123
+ AND project_name = ?
3124
+ AND COALESCE(memory_type, 'raw') = ?
3125
+ AND COALESCE(status, 'active') != 'deleted'`;
3126
+ if (input.excludeId) {
3127
+ sql += " AND id != ?";
3128
+ args.push(input.excludeId);
3129
+ }
3130
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3131
+ const result = await client.execute({ sql, args });
3132
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3133
+ }
3134
+ async function runPostWriteMemoryHygiene(memoryId) {
3135
+ try {
3136
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3137
+ const client = getClient2();
3138
+ const current = await client.execute({
3139
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3140
+ importance, timestamp
3141
+ FROM memories
3142
+ WHERE id = ?
3143
+ LIMIT 1`,
3144
+ args: [memoryId]
3145
+ });
3146
+ const row = current.rows[0];
3147
+ if (!row) return;
3148
+ const memoryType = String(row.memory_type ?? "raw");
3149
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3150
+ const agentId2 = String(row.agent_id);
3151
+ const projectName2 = String(row.project_name);
3152
+ if (contentHash) {
3153
+ await client.execute({
3154
+ sql: `UPDATE memories
3155
+ SET status = 'deleted',
3156
+ outcome = COALESCE(outcome, 'superseded')
3157
+ WHERE id != ?
3158
+ AND content_hash = ?
3159
+ AND agent_id = ?
3160
+ AND project_name = ?
3161
+ AND COALESCE(memory_type, 'raw') = ?
3162
+ AND COALESCE(status, 'active') = 'active'`,
3163
+ args: [memoryId, contentHash, agentId2, projectName2, memoryType]
3164
+ });
3165
+ }
3166
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3167
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3168
+ const old = await client.execute({
3169
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3170
+ args: [supersedesId]
3171
+ });
3172
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3173
+ const newImportance = Number(row.importance ?? 0);
3174
+ await client.batch([
3175
+ {
3176
+ sql: `UPDATE memories
3177
+ SET status = 'archived',
3178
+ outcome = COALESCE(outcome, 'superseded')
3179
+ WHERE id = ?`,
3180
+ args: [supersedesId]
3181
+ },
3182
+ {
3183
+ sql: `UPDATE memories
3184
+ SET importance = MAX(COALESCE(importance, 5), ?),
3185
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3186
+ WHERE id = ?`,
3187
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3188
+ }
3189
+ ], "write");
3190
+ }
3191
+ } catch (err) {
3192
+ process.stderr.write(
3193
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3194
+ `
3195
+ );
3196
+ }
3197
+ }
3198
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3199
+ if (memoryIds.length === 0) return;
3200
+ const run = () => {
3201
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3202
+ };
3203
+ if (typeof setImmediate === "function") setImmediate(run);
3204
+ else setTimeout(run, 0);
3205
+ }
3206
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3207
+ var init_memory_write_governor = __esm({
3208
+ "src/lib/memory-write-governor.ts"() {
3209
+ "use strict";
3210
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3211
+ "decision",
3212
+ "adr",
3213
+ "behavior",
3214
+ "procedure"
3215
+ ]);
3216
+ NOISE_DROP_PATTERNS = [
3217
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3218
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3219
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3220
+ /^\s*Intercom is a speedup, not delivery/im,
3221
+ /^\s*Context bar reads as USAGE not remaining/im
3222
+ ];
3223
+ SKIP_EMBED_PATTERNS = [
3224
+ /tmux capture-pane\b/i,
3225
+ /docker ps\b/i,
3226
+ /docker images\b/i,
3227
+ /git status\b/i,
3228
+ /grep .*node_modules/i,
3229
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3230
+ ];
3231
+ }
3232
+ });
3233
+
3036
3234
  // src/lib/shard-manager.ts
3037
3235
  var shard_manager_exports = {};
3038
3236
  __export(shard_manager_exports, {
@@ -3197,7 +3395,8 @@ async function ensureShardSchema(client) {
3197
3395
  }
3198
3396
  for (const idx of [
3199
3397
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3200
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3398
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3399
+ "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"
3201
3400
  ]) {
3202
3401
  try {
3203
3402
  await client.execute(idx);
@@ -3622,7 +3821,6 @@ __export(store_exports, {
3622
3821
  vectorToBlob: () => vectorToBlob,
3623
3822
  writeMemory: () => writeMemory
3624
3823
  });
3625
- import { createHash } from "crypto";
3626
3824
  function isBusyError2(err) {
3627
3825
  if (err instanceof Error) {
3628
3826
  const msg = err.message.toLowerCase();
@@ -3736,17 +3934,24 @@ async function writeMemory(record) {
3736
3934
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3737
3935
  );
3738
3936
  }
3739
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3740
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3937
+ const governed = governMemoryRecord(record);
3938
+ if (governed.shouldDrop) return;
3939
+ record = governed.record;
3940
+ const contentHash = governed.contentHash;
3941
+ const memoryType = record.memory_type ?? "raw";
3942
+ if (_pendingRecords.some(
3943
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3944
+ )) {
3741
3945
  return;
3742
3946
  }
3743
3947
  try {
3744
- const client = getClient();
3745
- const existing = await client.execute({
3746
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3747
- args: [contentHash, record.agent_id]
3948
+ const existing = await findScopedDuplicate({
3949
+ contentHash,
3950
+ agentId: record.agent_id,
3951
+ projectName: record.project_name,
3952
+ memoryType
3748
3953
  });
3749
- if (existing.rows.length > 0) return;
3954
+ if (existing) return;
3750
3955
  } catch {
3751
3956
  }
3752
3957
  const dbRow = {
@@ -3777,7 +3982,7 @@ async function writeMemory(record) {
3777
3982
  tier: record.tier ?? classifyTier(record),
3778
3983
  supersedes_id: record.supersedes_id ?? null,
3779
3984
  draft: record.draft ? 1 : 0,
3780
- memory_type: record.memory_type ?? "raw",
3985
+ memory_type: memoryType,
3781
3986
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3782
3987
  content_hash: contentHash,
3783
3988
  intent: record.intent ?? null,
@@ -3936,6 +4141,7 @@ async function flushBatch() {
3936
4141
  const globalClient = getClient();
3937
4142
  const globalStmts = batch.map(buildStmt);
3938
4143
  await globalClient.batch(globalStmts, "write");
4144
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3939
4145
  _pendingRecords.splice(0, batch.length);
3940
4146
  try {
3941
4147
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4194,6 +4400,7 @@ var init_store = __esm({
4194
4400
  init_keychain();
4195
4401
  init_config();
4196
4402
  init_state_bus();
4403
+ init_memory_write_governor();
4197
4404
  INIT_MAX_RETRIES = 3;
4198
4405
  INIT_RETRY_DELAY_MS = 1e3;
4199
4406
  _pendingRecords = [];
@@ -2288,6 +2288,14 @@ async function ensureSchema() {
2288
2288
  );
2289
2289
  } catch {
2290
2290
  }
2291
+ try {
2292
+ await client.execute(
2293
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2294
+ ON memories(content_hash, agent_id, project_name, memory_type)
2295
+ WHERE content_hash IS NOT NULL`
2296
+ );
2297
+ } catch {
2298
+ }
2291
2299
  await client.executeMultiple(`
2292
2300
  CREATE TABLE IF NOT EXISTS entities (
2293
2301
  id TEXT PRIMARY KEY,
@@ -3033,6 +3041,196 @@ var init_state_bus = __esm({
3033
3041
  }
3034
3042
  });
3035
3043
 
3044
+ // src/lib/memory-write-governor.ts
3045
+ import { createHash } from "crypto";
3046
+ function normalizeMemoryText(text) {
3047
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3048
+ }
3049
+ function classifyMemoryType(input) {
3050
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3051
+ const tool = input.tool_name.toLowerCase();
3052
+ const text = input.raw_text.toLowerCase();
3053
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3054
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3055
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3056
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3057
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3058
+ if (tool === "store_memory" || tool === "manual") return "observation";
3059
+ return "raw";
3060
+ }
3061
+ function shouldDropMemory(text) {
3062
+ const normalized = normalizeMemoryText(text);
3063
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3064
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3065
+ return { drop: true, reason: "known_boilerplate_noise" };
3066
+ }
3067
+ return { drop: false };
3068
+ }
3069
+ function shouldSkipEmbedding(input) {
3070
+ const type = classifyMemoryType(input);
3071
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3072
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3073
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3074
+ return false;
3075
+ }
3076
+ function hashMemoryContent(text) {
3077
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3078
+ }
3079
+ function scopedDedupArgs(input) {
3080
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3081
+ }
3082
+ function governMemoryRecord(record) {
3083
+ const normalized = normalizeMemoryText(record.raw_text);
3084
+ const memoryType = classifyMemoryType({
3085
+ raw_text: normalized,
3086
+ agent_id: record.agent_id,
3087
+ project_name: record.project_name,
3088
+ tool_name: record.tool_name,
3089
+ memory_type: record.memory_type
3090
+ });
3091
+ const drop = shouldDropMemory(normalized);
3092
+ const skipEmbedding = shouldSkipEmbedding({
3093
+ raw_text: normalized,
3094
+ agent_id: record.agent_id,
3095
+ project_name: record.project_name,
3096
+ tool_name: record.tool_name,
3097
+ memory_type: memoryType
3098
+ });
3099
+ return {
3100
+ record: {
3101
+ ...record,
3102
+ raw_text: normalized,
3103
+ memory_type: memoryType,
3104
+ vector: skipEmbedding ? null : record.vector
3105
+ },
3106
+ contentHash: hashMemoryContent(normalized),
3107
+ shouldDrop: drop.drop,
3108
+ dropReason: drop.reason,
3109
+ skipEmbedding,
3110
+ hygiene: {
3111
+ dedup: true,
3112
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3113
+ }
3114
+ };
3115
+ }
3116
+ async function findScopedDuplicate(input) {
3117
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3118
+ const client = getClient2();
3119
+ const args = scopedDedupArgs(input);
3120
+ let sql = `SELECT id FROM memories
3121
+ WHERE content_hash = ?
3122
+ AND agent_id = ?
3123
+ AND project_name = ?
3124
+ AND COALESCE(memory_type, 'raw') = ?
3125
+ AND COALESCE(status, 'active') != 'deleted'`;
3126
+ if (input.excludeId) {
3127
+ sql += " AND id != ?";
3128
+ args.push(input.excludeId);
3129
+ }
3130
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3131
+ const result = await client.execute({ sql, args });
3132
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3133
+ }
3134
+ async function runPostWriteMemoryHygiene(memoryId) {
3135
+ try {
3136
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3137
+ const client = getClient2();
3138
+ const current = await client.execute({
3139
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3140
+ importance, timestamp
3141
+ FROM memories
3142
+ WHERE id = ?
3143
+ LIMIT 1`,
3144
+ args: [memoryId]
3145
+ });
3146
+ const row = current.rows[0];
3147
+ if (!row) return;
3148
+ const memoryType = String(row.memory_type ?? "raw");
3149
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3150
+ const agentId = String(row.agent_id);
3151
+ const projectName = String(row.project_name);
3152
+ if (contentHash) {
3153
+ await client.execute({
3154
+ sql: `UPDATE memories
3155
+ SET status = 'deleted',
3156
+ outcome = COALESCE(outcome, 'superseded')
3157
+ WHERE id != ?
3158
+ AND content_hash = ?
3159
+ AND agent_id = ?
3160
+ AND project_name = ?
3161
+ AND COALESCE(memory_type, 'raw') = ?
3162
+ AND COALESCE(status, 'active') = 'active'`,
3163
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3164
+ });
3165
+ }
3166
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3167
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3168
+ const old = await client.execute({
3169
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3170
+ args: [supersedesId]
3171
+ });
3172
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3173
+ const newImportance = Number(row.importance ?? 0);
3174
+ await client.batch([
3175
+ {
3176
+ sql: `UPDATE memories
3177
+ SET status = 'archived',
3178
+ outcome = COALESCE(outcome, 'superseded')
3179
+ WHERE id = ?`,
3180
+ args: [supersedesId]
3181
+ },
3182
+ {
3183
+ sql: `UPDATE memories
3184
+ SET importance = MAX(COALESCE(importance, 5), ?),
3185
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3186
+ WHERE id = ?`,
3187
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3188
+ }
3189
+ ], "write");
3190
+ }
3191
+ } catch (err) {
3192
+ process.stderr.write(
3193
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3194
+ `
3195
+ );
3196
+ }
3197
+ }
3198
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3199
+ if (memoryIds.length === 0) return;
3200
+ const run = () => {
3201
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3202
+ };
3203
+ if (typeof setImmediate === "function") setImmediate(run);
3204
+ else setTimeout(run, 0);
3205
+ }
3206
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3207
+ var init_memory_write_governor = __esm({
3208
+ "src/lib/memory-write-governor.ts"() {
3209
+ "use strict";
3210
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3211
+ "decision",
3212
+ "adr",
3213
+ "behavior",
3214
+ "procedure"
3215
+ ]);
3216
+ NOISE_DROP_PATTERNS = [
3217
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3218
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3219
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3220
+ /^\s*Intercom is a speedup, not delivery/im,
3221
+ /^\s*Context bar reads as USAGE not remaining/im
3222
+ ];
3223
+ SKIP_EMBED_PATTERNS = [
3224
+ /tmux capture-pane\b/i,
3225
+ /docker ps\b/i,
3226
+ /docker images\b/i,
3227
+ /git status\b/i,
3228
+ /grep .*node_modules/i,
3229
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3230
+ ];
3231
+ }
3232
+ });
3233
+
3036
3234
  // src/lib/shard-manager.ts
3037
3235
  var shard_manager_exports = {};
3038
3236
  __export(shard_manager_exports, {
@@ -3197,7 +3395,8 @@ async function ensureShardSchema(client) {
3197
3395
  }
3198
3396
  for (const idx of [
3199
3397
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3200
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3398
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3399
+ "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"
3201
3400
  ]) {
3202
3401
  try {
3203
3402
  await client.execute(idx);
@@ -3622,7 +3821,6 @@ __export(store_exports, {
3622
3821
  vectorToBlob: () => vectorToBlob,
3623
3822
  writeMemory: () => writeMemory
3624
3823
  });
3625
- import { createHash } from "crypto";
3626
3824
  function isBusyError2(err) {
3627
3825
  if (err instanceof Error) {
3628
3826
  const msg = err.message.toLowerCase();
@@ -3736,17 +3934,24 @@ async function writeMemory(record) {
3736
3934
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3737
3935
  );
3738
3936
  }
3739
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3740
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3937
+ const governed = governMemoryRecord(record);
3938
+ if (governed.shouldDrop) return;
3939
+ record = governed.record;
3940
+ const contentHash = governed.contentHash;
3941
+ const memoryType = record.memory_type ?? "raw";
3942
+ if (_pendingRecords.some(
3943
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3944
+ )) {
3741
3945
  return;
3742
3946
  }
3743
3947
  try {
3744
- const client = getClient();
3745
- const existing = await client.execute({
3746
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3747
- args: [contentHash, record.agent_id]
3948
+ const existing = await findScopedDuplicate({
3949
+ contentHash,
3950
+ agentId: record.agent_id,
3951
+ projectName: record.project_name,
3952
+ memoryType
3748
3953
  });
3749
- if (existing.rows.length > 0) return;
3954
+ if (existing) return;
3750
3955
  } catch {
3751
3956
  }
3752
3957
  const dbRow = {
@@ -3777,7 +3982,7 @@ async function writeMemory(record) {
3777
3982
  tier: record.tier ?? classifyTier(record),
3778
3983
  supersedes_id: record.supersedes_id ?? null,
3779
3984
  draft: record.draft ? 1 : 0,
3780
- memory_type: record.memory_type ?? "raw",
3985
+ memory_type: memoryType,
3781
3986
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3782
3987
  content_hash: contentHash,
3783
3988
  intent: record.intent ?? null,
@@ -3936,6 +4141,7 @@ async function flushBatch() {
3936
4141
  const globalClient = getClient();
3937
4142
  const globalStmts = batch.map(buildStmt);
3938
4143
  await globalClient.batch(globalStmts, "write");
4144
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3939
4145
  _pendingRecords.splice(0, batch.length);
3940
4146
  try {
3941
4147
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4194,6 +4400,7 @@ var init_store = __esm({
4194
4400
  init_keychain();
4195
4401
  init_config();
4196
4402
  init_state_bus();
4403
+ init_memory_write_governor();
4197
4404
  INIT_MAX_RETRIES = 3;
4198
4405
  INIT_RETRY_DELAY_MS = 1e3;
4199
4406
  _pendingRecords = [];