@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
@@ -2455,6 +2455,14 @@ async function ensureSchema() {
2455
2455
  );
2456
2456
  } catch {
2457
2457
  }
2458
+ try {
2459
+ await client.execute(
2460
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2461
+ ON memories(content_hash, agent_id, project_name, memory_type)
2462
+ WHERE content_hash IS NOT NULL`
2463
+ );
2464
+ } catch {
2465
+ }
2458
2466
  await client.executeMultiple(`
2459
2467
  CREATE TABLE IF NOT EXISTS entities (
2460
2468
  id TEXT PRIMARY KEY,
@@ -3200,6 +3208,196 @@ var init_state_bus = __esm({
3200
3208
  }
3201
3209
  });
3202
3210
 
3211
+ // src/lib/memory-write-governor.ts
3212
+ import { createHash } from "crypto";
3213
+ function normalizeMemoryText(text) {
3214
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3215
+ }
3216
+ function classifyMemoryType(input2) {
3217
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3218
+ const tool = input2.tool_name.toLowerCase();
3219
+ const text = input2.raw_text.toLowerCase();
3220
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3221
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3222
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3223
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3224
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3225
+ if (tool === "store_memory" || tool === "manual") return "observation";
3226
+ return "raw";
3227
+ }
3228
+ function shouldDropMemory(text) {
3229
+ const normalized = normalizeMemoryText(text);
3230
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3231
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3232
+ return { drop: true, reason: "known_boilerplate_noise" };
3233
+ }
3234
+ return { drop: false };
3235
+ }
3236
+ function shouldSkipEmbedding(input2) {
3237
+ const type = classifyMemoryType(input2);
3238
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3239
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3240
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3241
+ return false;
3242
+ }
3243
+ function hashMemoryContent(text) {
3244
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3245
+ }
3246
+ function scopedDedupArgs(input2) {
3247
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3248
+ }
3249
+ function governMemoryRecord(record) {
3250
+ const normalized = normalizeMemoryText(record.raw_text);
3251
+ const memoryType = classifyMemoryType({
3252
+ raw_text: normalized,
3253
+ agent_id: record.agent_id,
3254
+ project_name: record.project_name,
3255
+ tool_name: record.tool_name,
3256
+ memory_type: record.memory_type
3257
+ });
3258
+ const drop = shouldDropMemory(normalized);
3259
+ const skipEmbedding = shouldSkipEmbedding({
3260
+ raw_text: normalized,
3261
+ agent_id: record.agent_id,
3262
+ project_name: record.project_name,
3263
+ tool_name: record.tool_name,
3264
+ memory_type: memoryType
3265
+ });
3266
+ return {
3267
+ record: {
3268
+ ...record,
3269
+ raw_text: normalized,
3270
+ memory_type: memoryType,
3271
+ vector: skipEmbedding ? null : record.vector
3272
+ },
3273
+ contentHash: hashMemoryContent(normalized),
3274
+ shouldDrop: drop.drop,
3275
+ dropReason: drop.reason,
3276
+ skipEmbedding,
3277
+ hygiene: {
3278
+ dedup: true,
3279
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3280
+ }
3281
+ };
3282
+ }
3283
+ async function findScopedDuplicate(input2) {
3284
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3285
+ const client = getClient2();
3286
+ const args = scopedDedupArgs(input2);
3287
+ let sql = `SELECT id FROM memories
3288
+ WHERE content_hash = ?
3289
+ AND agent_id = ?
3290
+ AND project_name = ?
3291
+ AND COALESCE(memory_type, 'raw') = ?
3292
+ AND COALESCE(status, 'active') != 'deleted'`;
3293
+ if (input2.excludeId) {
3294
+ sql += " AND id != ?";
3295
+ args.push(input2.excludeId);
3296
+ }
3297
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3298
+ const result = await client.execute({ sql, args });
3299
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3300
+ }
3301
+ async function runPostWriteMemoryHygiene(memoryId) {
3302
+ try {
3303
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3304
+ const client = getClient2();
3305
+ const current = await client.execute({
3306
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3307
+ importance, timestamp
3308
+ FROM memories
3309
+ WHERE id = ?
3310
+ LIMIT 1`,
3311
+ args: [memoryId]
3312
+ });
3313
+ const row = current.rows[0];
3314
+ if (!row) return;
3315
+ const memoryType = String(row.memory_type ?? "raw");
3316
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3317
+ const agentId = String(row.agent_id);
3318
+ const projectName = String(row.project_name);
3319
+ if (contentHash) {
3320
+ await client.execute({
3321
+ sql: `UPDATE memories
3322
+ SET status = 'deleted',
3323
+ outcome = COALESCE(outcome, 'superseded')
3324
+ WHERE id != ?
3325
+ AND content_hash = ?
3326
+ AND agent_id = ?
3327
+ AND project_name = ?
3328
+ AND COALESCE(memory_type, 'raw') = ?
3329
+ AND COALESCE(status, 'active') = 'active'`,
3330
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3331
+ });
3332
+ }
3333
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3334
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3335
+ const old = await client.execute({
3336
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3337
+ args: [supersedesId]
3338
+ });
3339
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3340
+ const newImportance = Number(row.importance ?? 0);
3341
+ await client.batch([
3342
+ {
3343
+ sql: `UPDATE memories
3344
+ SET status = 'archived',
3345
+ outcome = COALESCE(outcome, 'superseded')
3346
+ WHERE id = ?`,
3347
+ args: [supersedesId]
3348
+ },
3349
+ {
3350
+ sql: `UPDATE memories
3351
+ SET importance = MAX(COALESCE(importance, 5), ?),
3352
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3353
+ WHERE id = ?`,
3354
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3355
+ }
3356
+ ], "write");
3357
+ }
3358
+ } catch (err) {
3359
+ process.stderr.write(
3360
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3361
+ `
3362
+ );
3363
+ }
3364
+ }
3365
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3366
+ if (memoryIds.length === 0) return;
3367
+ const run = () => {
3368
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3369
+ };
3370
+ if (typeof setImmediate === "function") setImmediate(run);
3371
+ else setTimeout(run, 0);
3372
+ }
3373
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3374
+ var init_memory_write_governor = __esm({
3375
+ "src/lib/memory-write-governor.ts"() {
3376
+ "use strict";
3377
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3378
+ "decision",
3379
+ "adr",
3380
+ "behavior",
3381
+ "procedure"
3382
+ ]);
3383
+ NOISE_DROP_PATTERNS = [
3384
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3385
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3386
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3387
+ /^\s*Intercom is a speedup, not delivery/im,
3388
+ /^\s*Context bar reads as USAGE not remaining/im
3389
+ ];
3390
+ SKIP_EMBED_PATTERNS = [
3391
+ /tmux capture-pane\b/i,
3392
+ /docker ps\b/i,
3393
+ /docker images\b/i,
3394
+ /git status\b/i,
3395
+ /grep .*node_modules/i,
3396
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3397
+ ];
3398
+ }
3399
+ });
3400
+
3203
3401
  // src/lib/shard-manager.ts
3204
3402
  var shard_manager_exports = {};
3205
3403
  __export(shard_manager_exports, {
@@ -3364,7 +3562,8 @@ async function ensureShardSchema(client) {
3364
3562
  }
3365
3563
  for (const idx of [
3366
3564
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3367
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3565
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3566
+ "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"
3368
3567
  ]) {
3369
3568
  try {
3370
3569
  await client.execute(idx);
@@ -3789,7 +3988,6 @@ __export(store_exports, {
3789
3988
  vectorToBlob: () => vectorToBlob,
3790
3989
  writeMemory: () => writeMemory
3791
3990
  });
3792
- import { createHash } from "crypto";
3793
3991
  function isBusyError2(err) {
3794
3992
  if (err instanceof Error) {
3795
3993
  const msg = err.message.toLowerCase();
@@ -3903,17 +4101,24 @@ async function writeMemory(record) {
3903
4101
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3904
4102
  );
3905
4103
  }
3906
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3907
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4104
+ const governed = governMemoryRecord(record);
4105
+ if (governed.shouldDrop) return;
4106
+ record = governed.record;
4107
+ const contentHash = governed.contentHash;
4108
+ const memoryType = record.memory_type ?? "raw";
4109
+ if (_pendingRecords.some(
4110
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4111
+ )) {
3908
4112
  return;
3909
4113
  }
3910
4114
  try {
3911
- const client = getClient();
3912
- const existing = await client.execute({
3913
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3914
- args: [contentHash, record.agent_id]
4115
+ const existing = await findScopedDuplicate({
4116
+ contentHash,
4117
+ agentId: record.agent_id,
4118
+ projectName: record.project_name,
4119
+ memoryType
3915
4120
  });
3916
- if (existing.rows.length > 0) return;
4121
+ if (existing) return;
3917
4122
  } catch {
3918
4123
  }
3919
4124
  const dbRow = {
@@ -3944,7 +4149,7 @@ async function writeMemory(record) {
3944
4149
  tier: record.tier ?? classifyTier(record),
3945
4150
  supersedes_id: record.supersedes_id ?? null,
3946
4151
  draft: record.draft ? 1 : 0,
3947
- memory_type: record.memory_type ?? "raw",
4152
+ memory_type: memoryType,
3948
4153
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3949
4154
  content_hash: contentHash,
3950
4155
  intent: record.intent ?? null,
@@ -4103,6 +4308,7 @@ async function flushBatch() {
4103
4308
  const globalClient = getClient();
4104
4309
  const globalStmts = batch.map(buildStmt);
4105
4310
  await globalClient.batch(globalStmts, "write");
4311
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4106
4312
  _pendingRecords.splice(0, batch.length);
4107
4313
  try {
4108
4314
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4361,6 +4567,7 @@ var init_store = __esm({
4361
4567
  init_keychain();
4362
4568
  init_config();
4363
4569
  init_state_bus();
4570
+ init_memory_write_governor();
4364
4571
  INIT_MAX_RETRIES = 3;
4365
4572
  INIT_RETRY_DELAY_MS = 1e3;
4366
4573
  _pendingRecords = [];
@@ -2290,6 +2290,14 @@ async function ensureSchema() {
2290
2290
  );
2291
2291
  } catch {
2292
2292
  }
2293
+ try {
2294
+ await client.execute(
2295
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2296
+ ON memories(content_hash, agent_id, project_name, memory_type)
2297
+ WHERE content_hash IS NOT NULL`
2298
+ );
2299
+ } catch {
2300
+ }
2293
2301
  await client.executeMultiple(`
2294
2302
  CREATE TABLE IF NOT EXISTS entities (
2295
2303
  id TEXT PRIMARY KEY,
@@ -3035,6 +3043,196 @@ var init_state_bus = __esm({
3035
3043
  }
3036
3044
  });
3037
3045
 
3046
+ // src/lib/memory-write-governor.ts
3047
+ import { createHash as createHash2 } from "crypto";
3048
+ function normalizeMemoryText(text) {
3049
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3050
+ }
3051
+ function classifyMemoryType(input2) {
3052
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3053
+ const tool = input2.tool_name.toLowerCase();
3054
+ const text = input2.raw_text.toLowerCase();
3055
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3056
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3057
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3058
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3059
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3060
+ if (tool === "store_memory" || tool === "manual") return "observation";
3061
+ return "raw";
3062
+ }
3063
+ function shouldDropMemory(text) {
3064
+ const normalized = normalizeMemoryText(text);
3065
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3066
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3067
+ return { drop: true, reason: "known_boilerplate_noise" };
3068
+ }
3069
+ return { drop: false };
3070
+ }
3071
+ function shouldSkipEmbedding(input2) {
3072
+ const type = classifyMemoryType(input2);
3073
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3074
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3075
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3076
+ return false;
3077
+ }
3078
+ function hashMemoryContent(text) {
3079
+ return createHash2("sha256").update(normalizeMemoryText(text)).digest("hex");
3080
+ }
3081
+ function scopedDedupArgs(input2) {
3082
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3083
+ }
3084
+ function governMemoryRecord(record) {
3085
+ const normalized = normalizeMemoryText(record.raw_text);
3086
+ const memoryType = classifyMemoryType({
3087
+ raw_text: normalized,
3088
+ agent_id: record.agent_id,
3089
+ project_name: record.project_name,
3090
+ tool_name: record.tool_name,
3091
+ memory_type: record.memory_type
3092
+ });
3093
+ const drop = shouldDropMemory(normalized);
3094
+ const skipEmbedding = shouldSkipEmbedding({
3095
+ raw_text: normalized,
3096
+ agent_id: record.agent_id,
3097
+ project_name: record.project_name,
3098
+ tool_name: record.tool_name,
3099
+ memory_type: memoryType
3100
+ });
3101
+ return {
3102
+ record: {
3103
+ ...record,
3104
+ raw_text: normalized,
3105
+ memory_type: memoryType,
3106
+ vector: skipEmbedding ? null : record.vector
3107
+ },
3108
+ contentHash: hashMemoryContent(normalized),
3109
+ shouldDrop: drop.drop,
3110
+ dropReason: drop.reason,
3111
+ skipEmbedding,
3112
+ hygiene: {
3113
+ dedup: true,
3114
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3115
+ }
3116
+ };
3117
+ }
3118
+ async function findScopedDuplicate(input2) {
3119
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3120
+ const client = getClient2();
3121
+ const args = scopedDedupArgs(input2);
3122
+ let sql = `SELECT id FROM memories
3123
+ WHERE content_hash = ?
3124
+ AND agent_id = ?
3125
+ AND project_name = ?
3126
+ AND COALESCE(memory_type, 'raw') = ?
3127
+ AND COALESCE(status, 'active') != 'deleted'`;
3128
+ if (input2.excludeId) {
3129
+ sql += " AND id != ?";
3130
+ args.push(input2.excludeId);
3131
+ }
3132
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3133
+ const result = await client.execute({ sql, args });
3134
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3135
+ }
3136
+ async function runPostWriteMemoryHygiene(memoryId) {
3137
+ try {
3138
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3139
+ const client = getClient2();
3140
+ const current = await client.execute({
3141
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3142
+ importance, timestamp
3143
+ FROM memories
3144
+ WHERE id = ?
3145
+ LIMIT 1`,
3146
+ args: [memoryId]
3147
+ });
3148
+ const row = current.rows[0];
3149
+ if (!row) return;
3150
+ const memoryType = String(row.memory_type ?? "raw");
3151
+ const contentHash2 = row.content_hash ? String(row.content_hash) : null;
3152
+ const agentId = String(row.agent_id);
3153
+ const projectName = String(row.project_name);
3154
+ if (contentHash2) {
3155
+ await client.execute({
3156
+ sql: `UPDATE memories
3157
+ SET status = 'deleted',
3158
+ outcome = COALESCE(outcome, 'superseded')
3159
+ WHERE id != ?
3160
+ AND content_hash = ?
3161
+ AND agent_id = ?
3162
+ AND project_name = ?
3163
+ AND COALESCE(memory_type, 'raw') = ?
3164
+ AND COALESCE(status, 'active') = 'active'`,
3165
+ args: [memoryId, contentHash2, agentId, projectName, memoryType]
3166
+ });
3167
+ }
3168
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3169
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3170
+ const old = await client.execute({
3171
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3172
+ args: [supersedesId]
3173
+ });
3174
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3175
+ const newImportance = Number(row.importance ?? 0);
3176
+ await client.batch([
3177
+ {
3178
+ sql: `UPDATE memories
3179
+ SET status = 'archived',
3180
+ outcome = COALESCE(outcome, 'superseded')
3181
+ WHERE id = ?`,
3182
+ args: [supersedesId]
3183
+ },
3184
+ {
3185
+ sql: `UPDATE memories
3186
+ SET importance = MAX(COALESCE(importance, 5), ?),
3187
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3188
+ WHERE id = ?`,
3189
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3190
+ }
3191
+ ], "write");
3192
+ }
3193
+ } catch (err) {
3194
+ process.stderr.write(
3195
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3196
+ `
3197
+ );
3198
+ }
3199
+ }
3200
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3201
+ if (memoryIds.length === 0) return;
3202
+ const run = () => {
3203
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3204
+ };
3205
+ if (typeof setImmediate === "function") setImmediate(run);
3206
+ else setTimeout(run, 0);
3207
+ }
3208
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3209
+ var init_memory_write_governor = __esm({
3210
+ "src/lib/memory-write-governor.ts"() {
3211
+ "use strict";
3212
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3213
+ "decision",
3214
+ "adr",
3215
+ "behavior",
3216
+ "procedure"
3217
+ ]);
3218
+ NOISE_DROP_PATTERNS = [
3219
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3220
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3221
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3222
+ /^\s*Intercom is a speedup, not delivery/im,
3223
+ /^\s*Context bar reads as USAGE not remaining/im
3224
+ ];
3225
+ SKIP_EMBED_PATTERNS = [
3226
+ /tmux capture-pane\b/i,
3227
+ /docker ps\b/i,
3228
+ /docker images\b/i,
3229
+ /git status\b/i,
3230
+ /grep .*node_modules/i,
3231
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3232
+ ];
3233
+ }
3234
+ });
3235
+
3038
3236
  // src/lib/shard-manager.ts
3039
3237
  var shard_manager_exports = {};
3040
3238
  __export(shard_manager_exports, {
@@ -3199,7 +3397,8 @@ async function ensureShardSchema(client) {
3199
3397
  }
3200
3398
  for (const idx of [
3201
3399
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3202
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3400
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3401
+ "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"
3203
3402
  ]) {
3204
3403
  try {
3205
3404
  await client.execute(idx);
@@ -3624,7 +3823,6 @@ __export(store_exports, {
3624
3823
  vectorToBlob: () => vectorToBlob,
3625
3824
  writeMemory: () => writeMemory
3626
3825
  });
3627
- import { createHash as createHash2 } from "crypto";
3628
3826
  function isBusyError2(err) {
3629
3827
  if (err instanceof Error) {
3630
3828
  const msg = err.message.toLowerCase();
@@ -3738,17 +3936,24 @@ async function writeMemory(record) {
3738
3936
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3739
3937
  );
3740
3938
  }
3741
- const contentHash2 = createHash2("md5").update(record.raw_text).digest("hex");
3742
- if (_pendingRecords.some((r) => r.content_hash === contentHash2 && r.agent_id === record.agent_id)) {
3939
+ const governed = governMemoryRecord(record);
3940
+ if (governed.shouldDrop) return;
3941
+ record = governed.record;
3942
+ const contentHash2 = governed.contentHash;
3943
+ const memoryType = record.memory_type ?? "raw";
3944
+ if (_pendingRecords.some(
3945
+ (r) => r.content_hash === contentHash2 && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3946
+ )) {
3743
3947
  return;
3744
3948
  }
3745
3949
  try {
3746
- const client = getClient();
3747
- const existing = await client.execute({
3748
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3749
- args: [contentHash2, record.agent_id]
3950
+ const existing = await findScopedDuplicate({
3951
+ contentHash: contentHash2,
3952
+ agentId: record.agent_id,
3953
+ projectName: record.project_name,
3954
+ memoryType
3750
3955
  });
3751
- if (existing.rows.length > 0) return;
3956
+ if (existing) return;
3752
3957
  } catch {
3753
3958
  }
3754
3959
  const dbRow = {
@@ -3779,7 +3984,7 @@ async function writeMemory(record) {
3779
3984
  tier: record.tier ?? classifyTier(record),
3780
3985
  supersedes_id: record.supersedes_id ?? null,
3781
3986
  draft: record.draft ? 1 : 0,
3782
- memory_type: record.memory_type ?? "raw",
3987
+ memory_type: memoryType,
3783
3988
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3784
3989
  content_hash: contentHash2,
3785
3990
  intent: record.intent ?? null,
@@ -3938,6 +4143,7 @@ async function flushBatch() {
3938
4143
  const globalClient = getClient();
3939
4144
  const globalStmts = batch.map(buildStmt);
3940
4145
  await globalClient.batch(globalStmts, "write");
4146
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3941
4147
  _pendingRecords.splice(0, batch.length);
3942
4148
  try {
3943
4149
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4196,6 +4402,7 @@ var init_store = __esm({
4196
4402
  init_keychain();
4197
4403
  init_config();
4198
4404
  init_state_bus();
4405
+ init_memory_write_governor();
4199
4406
  INIT_MAX_RETRIES = 3;
4200
4407
  INIT_RETRY_DELAY_MS = 1e3;
4201
4408
  _pendingRecords = [];