@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
@@ -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 } 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 createHash("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 contentHash = row.content_hash ? String(row.content_hash) : null;
3152
+ const agentId = String(row.agent_id);
3153
+ const projectName = String(row.project_name);
3154
+ if (contentHash) {
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, contentHash, 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 } 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 contentHash = createHash("md5").update(record.raw_text).digest("hex");
3742
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3939
+ const governed = governMemoryRecord(record);
3940
+ if (governed.shouldDrop) return;
3941
+ record = governed.record;
3942
+ const contentHash = governed.contentHash;
3943
+ const memoryType = record.memory_type ?? "raw";
3944
+ if (_pendingRecords.some(
3945
+ (r) => r.content_hash === contentHash && 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: [contentHash, record.agent_id]
3950
+ const existing = await findScopedDuplicate({
3951
+ contentHash,
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: contentHash,
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 = [];
@@ -2585,6 +2585,14 @@ async function ensureSchema() {
2585
2585
  );
2586
2586
  } catch {
2587
2587
  }
2588
+ try {
2589
+ await client.execute(
2590
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2591
+ ON memories(content_hash, agent_id, project_name, memory_type)
2592
+ WHERE content_hash IS NOT NULL`
2593
+ );
2594
+ } catch {
2595
+ }
2588
2596
  await client.executeMultiple(`
2589
2597
  CREATE TABLE IF NOT EXISTS entities (
2590
2598
  id TEXT PRIMARY KEY,
@@ -3455,6 +3463,196 @@ var init_state_bus = __esm({
3455
3463
  }
3456
3464
  });
3457
3465
 
3466
+ // src/lib/memory-write-governor.ts
3467
+ import { createHash } from "crypto";
3468
+ function normalizeMemoryText(text) {
3469
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3470
+ }
3471
+ function classifyMemoryType(input2) {
3472
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3473
+ const tool = input2.tool_name.toLowerCase();
3474
+ const text = input2.raw_text.toLowerCase();
3475
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3476
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3477
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3478
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3479
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3480
+ if (tool === "store_memory" || tool === "manual") return "observation";
3481
+ return "raw";
3482
+ }
3483
+ function shouldDropMemory(text) {
3484
+ const normalized = normalizeMemoryText(text);
3485
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3486
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3487
+ return { drop: true, reason: "known_boilerplate_noise" };
3488
+ }
3489
+ return { drop: false };
3490
+ }
3491
+ function shouldSkipEmbedding(input2) {
3492
+ const type = classifyMemoryType(input2);
3493
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3494
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3495
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3496
+ return false;
3497
+ }
3498
+ function hashMemoryContent(text) {
3499
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3500
+ }
3501
+ function scopedDedupArgs(input2) {
3502
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3503
+ }
3504
+ function governMemoryRecord(record) {
3505
+ const normalized = normalizeMemoryText(record.raw_text);
3506
+ const memoryType = classifyMemoryType({
3507
+ raw_text: normalized,
3508
+ agent_id: record.agent_id,
3509
+ project_name: record.project_name,
3510
+ tool_name: record.tool_name,
3511
+ memory_type: record.memory_type
3512
+ });
3513
+ const drop = shouldDropMemory(normalized);
3514
+ const skipEmbedding = shouldSkipEmbedding({
3515
+ raw_text: normalized,
3516
+ agent_id: record.agent_id,
3517
+ project_name: record.project_name,
3518
+ tool_name: record.tool_name,
3519
+ memory_type: memoryType
3520
+ });
3521
+ return {
3522
+ record: {
3523
+ ...record,
3524
+ raw_text: normalized,
3525
+ memory_type: memoryType,
3526
+ vector: skipEmbedding ? null : record.vector
3527
+ },
3528
+ contentHash: hashMemoryContent(normalized),
3529
+ shouldDrop: drop.drop,
3530
+ dropReason: drop.reason,
3531
+ skipEmbedding,
3532
+ hygiene: {
3533
+ dedup: true,
3534
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3535
+ }
3536
+ };
3537
+ }
3538
+ async function findScopedDuplicate(input2) {
3539
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3540
+ const client = getClient2();
3541
+ const args = scopedDedupArgs(input2);
3542
+ let sql = `SELECT id FROM memories
3543
+ WHERE content_hash = ?
3544
+ AND agent_id = ?
3545
+ AND project_name = ?
3546
+ AND COALESCE(memory_type, 'raw') = ?
3547
+ AND COALESCE(status, 'active') != 'deleted'`;
3548
+ if (input2.excludeId) {
3549
+ sql += " AND id != ?";
3550
+ args.push(input2.excludeId);
3551
+ }
3552
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3553
+ const result = await client.execute({ sql, args });
3554
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3555
+ }
3556
+ async function runPostWriteMemoryHygiene(memoryId) {
3557
+ try {
3558
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3559
+ const client = getClient2();
3560
+ const current = await client.execute({
3561
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3562
+ importance, timestamp
3563
+ FROM memories
3564
+ WHERE id = ?
3565
+ LIMIT 1`,
3566
+ args: [memoryId]
3567
+ });
3568
+ const row = current.rows[0];
3569
+ if (!row) return;
3570
+ const memoryType = String(row.memory_type ?? "raw");
3571
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3572
+ const agentId = String(row.agent_id);
3573
+ const projectName = String(row.project_name);
3574
+ if (contentHash) {
3575
+ await client.execute({
3576
+ sql: `UPDATE memories
3577
+ SET status = 'deleted',
3578
+ outcome = COALESCE(outcome, 'superseded')
3579
+ WHERE id != ?
3580
+ AND content_hash = ?
3581
+ AND agent_id = ?
3582
+ AND project_name = ?
3583
+ AND COALESCE(memory_type, 'raw') = ?
3584
+ AND COALESCE(status, 'active') = 'active'`,
3585
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3586
+ });
3587
+ }
3588
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3589
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3590
+ const old = await client.execute({
3591
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3592
+ args: [supersedesId]
3593
+ });
3594
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3595
+ const newImportance = Number(row.importance ?? 0);
3596
+ await client.batch([
3597
+ {
3598
+ sql: `UPDATE memories
3599
+ SET status = 'archived',
3600
+ outcome = COALESCE(outcome, 'superseded')
3601
+ WHERE id = ?`,
3602
+ args: [supersedesId]
3603
+ },
3604
+ {
3605
+ sql: `UPDATE memories
3606
+ SET importance = MAX(COALESCE(importance, 5), ?),
3607
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3608
+ WHERE id = ?`,
3609
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3610
+ }
3611
+ ], "write");
3612
+ }
3613
+ } catch (err) {
3614
+ process.stderr.write(
3615
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3616
+ `
3617
+ );
3618
+ }
3619
+ }
3620
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3621
+ if (memoryIds.length === 0) return;
3622
+ const run = () => {
3623
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3624
+ };
3625
+ if (typeof setImmediate === "function") setImmediate(run);
3626
+ else setTimeout(run, 0);
3627
+ }
3628
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3629
+ var init_memory_write_governor = __esm({
3630
+ "src/lib/memory-write-governor.ts"() {
3631
+ "use strict";
3632
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3633
+ "decision",
3634
+ "adr",
3635
+ "behavior",
3636
+ "procedure"
3637
+ ]);
3638
+ NOISE_DROP_PATTERNS = [
3639
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3640
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3641
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3642
+ /^\s*Intercom is a speedup, not delivery/im,
3643
+ /^\s*Context bar reads as USAGE not remaining/im
3644
+ ];
3645
+ SKIP_EMBED_PATTERNS = [
3646
+ /tmux capture-pane\b/i,
3647
+ /docker ps\b/i,
3648
+ /docker images\b/i,
3649
+ /git status\b/i,
3650
+ /grep .*node_modules/i,
3651
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3652
+ ];
3653
+ }
3654
+ });
3655
+
3458
3656
  // src/lib/shard-manager.ts
3459
3657
  var shard_manager_exports = {};
3460
3658
  __export(shard_manager_exports, {
@@ -3619,7 +3817,8 @@ async function ensureShardSchema(client) {
3619
3817
  }
3620
3818
  for (const idx of [
3621
3819
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3622
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3820
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3821
+ "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"
3623
3822
  ]) {
3624
3823
  try {
3625
3824
  await client.execute(idx);
@@ -4044,7 +4243,6 @@ __export(store_exports, {
4044
4243
  vectorToBlob: () => vectorToBlob,
4045
4244
  writeMemory: () => writeMemory
4046
4245
  });
4047
- import { createHash } from "crypto";
4048
4246
  function isBusyError2(err) {
4049
4247
  if (err instanceof Error) {
4050
4248
  const msg = err.message.toLowerCase();
@@ -4158,17 +4356,24 @@ async function writeMemory(record) {
4158
4356
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4159
4357
  );
4160
4358
  }
4161
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4162
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4359
+ const governed = governMemoryRecord(record);
4360
+ if (governed.shouldDrop) return;
4361
+ record = governed.record;
4362
+ const contentHash = governed.contentHash;
4363
+ const memoryType = record.memory_type ?? "raw";
4364
+ if (_pendingRecords.some(
4365
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4366
+ )) {
4163
4367
  return;
4164
4368
  }
4165
4369
  try {
4166
- const client = getClient();
4167
- const existing = await client.execute({
4168
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4169
- args: [contentHash, record.agent_id]
4370
+ const existing = await findScopedDuplicate({
4371
+ contentHash,
4372
+ agentId: record.agent_id,
4373
+ projectName: record.project_name,
4374
+ memoryType
4170
4375
  });
4171
- if (existing.rows.length > 0) return;
4376
+ if (existing) return;
4172
4377
  } catch {
4173
4378
  }
4174
4379
  const dbRow = {
@@ -4199,7 +4404,7 @@ async function writeMemory(record) {
4199
4404
  tier: record.tier ?? classifyTier(record),
4200
4405
  supersedes_id: record.supersedes_id ?? null,
4201
4406
  draft: record.draft ? 1 : 0,
4202
- memory_type: record.memory_type ?? "raw",
4407
+ memory_type: memoryType,
4203
4408
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4204
4409
  content_hash: contentHash,
4205
4410
  intent: record.intent ?? null,
@@ -4358,6 +4563,7 @@ async function flushBatch() {
4358
4563
  const globalClient = getClient();
4359
4564
  const globalStmts = batch.map(buildStmt);
4360
4565
  await globalClient.batch(globalStmts, "write");
4566
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4361
4567
  _pendingRecords.splice(0, batch.length);
4362
4568
  try {
4363
4569
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4616,6 +4822,7 @@ var init_store = __esm({
4616
4822
  init_keychain();
4617
4823
  init_config();
4618
4824
  init_state_bus();
4825
+ init_memory_write_governor();
4619
4826
  INIT_MAX_RETRIES = 3;
4620
4827
  INIT_RETRY_DELAY_MS = 1e3;
4621
4828
  _pendingRecords = [];