@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
@@ -2387,6 +2387,14 @@ async function ensureSchema() {
2387
2387
  );
2388
2388
  } catch {
2389
2389
  }
2390
+ try {
2391
+ await client.execute(
2392
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2393
+ ON memories(content_hash, agent_id, project_name, memory_type)
2394
+ WHERE content_hash IS NOT NULL`
2395
+ );
2396
+ } catch {
2397
+ }
2390
2398
  await client.executeMultiple(`
2391
2399
  CREATE TABLE IF NOT EXISTS entities (
2392
2400
  id TEXT PRIMARY KEY,
@@ -3132,6 +3140,196 @@ var init_state_bus = __esm({
3132
3140
  }
3133
3141
  });
3134
3142
 
3143
+ // src/lib/memory-write-governor.ts
3144
+ import { createHash } from "crypto";
3145
+ function normalizeMemoryText(text) {
3146
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3147
+ }
3148
+ function classifyMemoryType(input) {
3149
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3150
+ const tool = input.tool_name.toLowerCase();
3151
+ const text = input.raw_text.toLowerCase();
3152
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3153
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3154
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3155
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3156
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3157
+ if (tool === "store_memory" || tool === "manual") return "observation";
3158
+ return "raw";
3159
+ }
3160
+ function shouldDropMemory(text) {
3161
+ const normalized = normalizeMemoryText(text);
3162
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3163
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3164
+ return { drop: true, reason: "known_boilerplate_noise" };
3165
+ }
3166
+ return { drop: false };
3167
+ }
3168
+ function shouldSkipEmbedding(input) {
3169
+ const type = classifyMemoryType(input);
3170
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3171
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3172
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3173
+ return false;
3174
+ }
3175
+ function hashMemoryContent(text) {
3176
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3177
+ }
3178
+ function scopedDedupArgs(input) {
3179
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3180
+ }
3181
+ function governMemoryRecord(record) {
3182
+ const normalized = normalizeMemoryText(record.raw_text);
3183
+ const memoryType = classifyMemoryType({
3184
+ raw_text: normalized,
3185
+ agent_id: record.agent_id,
3186
+ project_name: record.project_name,
3187
+ tool_name: record.tool_name,
3188
+ memory_type: record.memory_type
3189
+ });
3190
+ const drop = shouldDropMemory(normalized);
3191
+ const skipEmbedding = shouldSkipEmbedding({
3192
+ raw_text: normalized,
3193
+ agent_id: record.agent_id,
3194
+ project_name: record.project_name,
3195
+ tool_name: record.tool_name,
3196
+ memory_type: memoryType
3197
+ });
3198
+ return {
3199
+ record: {
3200
+ ...record,
3201
+ raw_text: normalized,
3202
+ memory_type: memoryType,
3203
+ vector: skipEmbedding ? null : record.vector
3204
+ },
3205
+ contentHash: hashMemoryContent(normalized),
3206
+ shouldDrop: drop.drop,
3207
+ dropReason: drop.reason,
3208
+ skipEmbedding,
3209
+ hygiene: {
3210
+ dedup: true,
3211
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3212
+ }
3213
+ };
3214
+ }
3215
+ async function findScopedDuplicate(input) {
3216
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3217
+ const client = getClient2();
3218
+ const args = scopedDedupArgs(input);
3219
+ let sql = `SELECT id FROM memories
3220
+ WHERE content_hash = ?
3221
+ AND agent_id = ?
3222
+ AND project_name = ?
3223
+ AND COALESCE(memory_type, 'raw') = ?
3224
+ AND COALESCE(status, 'active') != 'deleted'`;
3225
+ if (input.excludeId) {
3226
+ sql += " AND id != ?";
3227
+ args.push(input.excludeId);
3228
+ }
3229
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3230
+ const result = await client.execute({ sql, args });
3231
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3232
+ }
3233
+ async function runPostWriteMemoryHygiene(memoryId) {
3234
+ try {
3235
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3236
+ const client = getClient2();
3237
+ const current = await client.execute({
3238
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3239
+ importance, timestamp
3240
+ FROM memories
3241
+ WHERE id = ?
3242
+ LIMIT 1`,
3243
+ args: [memoryId]
3244
+ });
3245
+ const row = current.rows[0];
3246
+ if (!row) return;
3247
+ const memoryType = String(row.memory_type ?? "raw");
3248
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3249
+ const agentId = String(row.agent_id);
3250
+ const projectName = String(row.project_name);
3251
+ if (contentHash) {
3252
+ await client.execute({
3253
+ sql: `UPDATE memories
3254
+ SET status = 'deleted',
3255
+ outcome = COALESCE(outcome, 'superseded')
3256
+ WHERE id != ?
3257
+ AND content_hash = ?
3258
+ AND agent_id = ?
3259
+ AND project_name = ?
3260
+ AND COALESCE(memory_type, 'raw') = ?
3261
+ AND COALESCE(status, 'active') = 'active'`,
3262
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3263
+ });
3264
+ }
3265
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3266
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3267
+ const old = await client.execute({
3268
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3269
+ args: [supersedesId]
3270
+ });
3271
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3272
+ const newImportance = Number(row.importance ?? 0);
3273
+ await client.batch([
3274
+ {
3275
+ sql: `UPDATE memories
3276
+ SET status = 'archived',
3277
+ outcome = COALESCE(outcome, 'superseded')
3278
+ WHERE id = ?`,
3279
+ args: [supersedesId]
3280
+ },
3281
+ {
3282
+ sql: `UPDATE memories
3283
+ SET importance = MAX(COALESCE(importance, 5), ?),
3284
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3285
+ WHERE id = ?`,
3286
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3287
+ }
3288
+ ], "write");
3289
+ }
3290
+ } catch (err) {
3291
+ process.stderr.write(
3292
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3293
+ `
3294
+ );
3295
+ }
3296
+ }
3297
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3298
+ if (memoryIds.length === 0) return;
3299
+ const run = () => {
3300
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3301
+ };
3302
+ if (typeof setImmediate === "function") setImmediate(run);
3303
+ else setTimeout(run, 0);
3304
+ }
3305
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3306
+ var init_memory_write_governor = __esm({
3307
+ "src/lib/memory-write-governor.ts"() {
3308
+ "use strict";
3309
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3310
+ "decision",
3311
+ "adr",
3312
+ "behavior",
3313
+ "procedure"
3314
+ ]);
3315
+ NOISE_DROP_PATTERNS = [
3316
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3317
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3318
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3319
+ /^\s*Intercom is a speedup, not delivery/im,
3320
+ /^\s*Context bar reads as USAGE not remaining/im
3321
+ ];
3322
+ SKIP_EMBED_PATTERNS = [
3323
+ /tmux capture-pane\b/i,
3324
+ /docker ps\b/i,
3325
+ /docker images\b/i,
3326
+ /git status\b/i,
3327
+ /grep .*node_modules/i,
3328
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3329
+ ];
3330
+ }
3331
+ });
3332
+
3135
3333
  // src/lib/shard-manager.ts
3136
3334
  var shard_manager_exports = {};
3137
3335
  __export(shard_manager_exports, {
@@ -3296,7 +3494,8 @@ async function ensureShardSchema(client) {
3296
3494
  }
3297
3495
  for (const idx of [
3298
3496
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3299
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3497
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3498
+ "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"
3300
3499
  ]) {
3301
3500
  try {
3302
3501
  await client.execute(idx);
@@ -3721,7 +3920,6 @@ __export(store_exports, {
3721
3920
  vectorToBlob: () => vectorToBlob,
3722
3921
  writeMemory: () => writeMemory
3723
3922
  });
3724
- import { createHash } from "crypto";
3725
3923
  function isBusyError2(err) {
3726
3924
  if (err instanceof Error) {
3727
3925
  const msg = err.message.toLowerCase();
@@ -3835,17 +4033,24 @@ async function writeMemory(record) {
3835
4033
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3836
4034
  );
3837
4035
  }
3838
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3839
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4036
+ const governed = governMemoryRecord(record);
4037
+ if (governed.shouldDrop) return;
4038
+ record = governed.record;
4039
+ const contentHash = governed.contentHash;
4040
+ const memoryType = record.memory_type ?? "raw";
4041
+ if (_pendingRecords.some(
4042
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4043
+ )) {
3840
4044
  return;
3841
4045
  }
3842
4046
  try {
3843
- const client = getClient();
3844
- const existing = await client.execute({
3845
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3846
- args: [contentHash, record.agent_id]
4047
+ const existing = await findScopedDuplicate({
4048
+ contentHash,
4049
+ agentId: record.agent_id,
4050
+ projectName: record.project_name,
4051
+ memoryType
3847
4052
  });
3848
- if (existing.rows.length > 0) return;
4053
+ if (existing) return;
3849
4054
  } catch {
3850
4055
  }
3851
4056
  const dbRow = {
@@ -3876,7 +4081,7 @@ async function writeMemory(record) {
3876
4081
  tier: record.tier ?? classifyTier(record),
3877
4082
  supersedes_id: record.supersedes_id ?? null,
3878
4083
  draft: record.draft ? 1 : 0,
3879
- memory_type: record.memory_type ?? "raw",
4084
+ memory_type: memoryType,
3880
4085
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3881
4086
  content_hash: contentHash,
3882
4087
  intent: record.intent ?? null,
@@ -4035,6 +4240,7 @@ async function flushBatch() {
4035
4240
  const globalClient = getClient();
4036
4241
  const globalStmts = batch.map(buildStmt);
4037
4242
  await globalClient.batch(globalStmts, "write");
4243
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4038
4244
  _pendingRecords.splice(0, batch.length);
4039
4245
  try {
4040
4246
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4293,6 +4499,7 @@ var init_store = __esm({
4293
4499
  init_keychain();
4294
4500
  init_config();
4295
4501
  init_state_bus();
4502
+ init_memory_write_governor();
4296
4503
  INIT_MAX_RETRIES = 3;
4297
4504
  INIT_RETRY_DELAY_MS = 1e3;
4298
4505
  _pendingRecords = [];
@@ -7451,7 +7658,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7451
7658
  }
7452
7659
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
7453
7660
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
7454
- envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
7661
+ let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
7662
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
7663
+ ccModel += "[1m]";
7664
+ }
7665
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
7455
7666
  }
7456
7667
  }
7457
7668
  let spawnCommand;
@@ -2879,6 +2879,14 @@ async function ensureSchema() {
2879
2879
  );
2880
2880
  } catch {
2881
2881
  }
2882
+ try {
2883
+ await client.execute(
2884
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2885
+ ON memories(content_hash, agent_id, project_name, memory_type)
2886
+ WHERE content_hash IS NOT NULL`
2887
+ );
2888
+ } catch {
2889
+ }
2882
2890
  await client.executeMultiple(`
2883
2891
  CREATE TABLE IF NOT EXISTS entities (
2884
2892
  id TEXT PRIMARY KEY,
@@ -6387,7 +6395,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6387
6395
  }
6388
6396
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
6389
6397
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
6390
- envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
6398
+ let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
6399
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
6400
+ ccModel += "[1m]";
6401
+ }
6402
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
6391
6403
  }
6392
6404
  }
6393
6405
  let spawnCommand;
@@ -6734,6 +6746,196 @@ var init_keychain = __esm({
6734
6746
  }
6735
6747
  });
6736
6748
 
6749
+ // src/lib/memory-write-governor.ts
6750
+ import { createHash } from "crypto";
6751
+ function normalizeMemoryText(text) {
6752
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
6753
+ }
6754
+ function classifyMemoryType(input) {
6755
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
6756
+ const tool = input.tool_name.toLowerCase();
6757
+ const text = input.raw_text.toLowerCase();
6758
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
6759
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
6760
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
6761
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
6762
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
6763
+ if (tool === "store_memory" || tool === "manual") return "observation";
6764
+ return "raw";
6765
+ }
6766
+ function shouldDropMemory(text) {
6767
+ const normalized = normalizeMemoryText(text);
6768
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
6769
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
6770
+ return { drop: true, reason: "known_boilerplate_noise" };
6771
+ }
6772
+ return { drop: false };
6773
+ }
6774
+ function shouldSkipEmbedding(input) {
6775
+ const type = classifyMemoryType(input);
6776
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
6777
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
6778
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
6779
+ return false;
6780
+ }
6781
+ function hashMemoryContent(text) {
6782
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
6783
+ }
6784
+ function scopedDedupArgs(input) {
6785
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
6786
+ }
6787
+ function governMemoryRecord(record) {
6788
+ const normalized = normalizeMemoryText(record.raw_text);
6789
+ const memoryType = classifyMemoryType({
6790
+ raw_text: normalized,
6791
+ agent_id: record.agent_id,
6792
+ project_name: record.project_name,
6793
+ tool_name: record.tool_name,
6794
+ memory_type: record.memory_type
6795
+ });
6796
+ const drop = shouldDropMemory(normalized);
6797
+ const skipEmbedding = shouldSkipEmbedding({
6798
+ raw_text: normalized,
6799
+ agent_id: record.agent_id,
6800
+ project_name: record.project_name,
6801
+ tool_name: record.tool_name,
6802
+ memory_type: memoryType
6803
+ });
6804
+ return {
6805
+ record: {
6806
+ ...record,
6807
+ raw_text: normalized,
6808
+ memory_type: memoryType,
6809
+ vector: skipEmbedding ? null : record.vector
6810
+ },
6811
+ contentHash: hashMemoryContent(normalized),
6812
+ shouldDrop: drop.drop,
6813
+ dropReason: drop.reason,
6814
+ skipEmbedding,
6815
+ hygiene: {
6816
+ dedup: true,
6817
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
6818
+ }
6819
+ };
6820
+ }
6821
+ async function findScopedDuplicate(input) {
6822
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
6823
+ const client = getClient2();
6824
+ const args = scopedDedupArgs(input);
6825
+ let sql = `SELECT id FROM memories
6826
+ WHERE content_hash = ?
6827
+ AND agent_id = ?
6828
+ AND project_name = ?
6829
+ AND COALESCE(memory_type, 'raw') = ?
6830
+ AND COALESCE(status, 'active') != 'deleted'`;
6831
+ if (input.excludeId) {
6832
+ sql += " AND id != ?";
6833
+ args.push(input.excludeId);
6834
+ }
6835
+ sql += " ORDER BY timestamp DESC LIMIT 1";
6836
+ const result = await client.execute({ sql, args });
6837
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
6838
+ }
6839
+ async function runPostWriteMemoryHygiene(memoryId) {
6840
+ try {
6841
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
6842
+ const client = getClient2();
6843
+ const current = await client.execute({
6844
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
6845
+ importance, timestamp
6846
+ FROM memories
6847
+ WHERE id = ?
6848
+ LIMIT 1`,
6849
+ args: [memoryId]
6850
+ });
6851
+ const row = current.rows[0];
6852
+ if (!row) return;
6853
+ const memoryType = String(row.memory_type ?? "raw");
6854
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
6855
+ const agentId = String(row.agent_id);
6856
+ const projectName = String(row.project_name);
6857
+ if (contentHash) {
6858
+ await client.execute({
6859
+ sql: `UPDATE memories
6860
+ SET status = 'deleted',
6861
+ outcome = COALESCE(outcome, 'superseded')
6862
+ WHERE id != ?
6863
+ AND content_hash = ?
6864
+ AND agent_id = ?
6865
+ AND project_name = ?
6866
+ AND COALESCE(memory_type, 'raw') = ?
6867
+ AND COALESCE(status, 'active') = 'active'`,
6868
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
6869
+ });
6870
+ }
6871
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
6872
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
6873
+ const old = await client.execute({
6874
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
6875
+ args: [supersedesId]
6876
+ });
6877
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
6878
+ const newImportance = Number(row.importance ?? 0);
6879
+ await client.batch([
6880
+ {
6881
+ sql: `UPDATE memories
6882
+ SET status = 'archived',
6883
+ outcome = COALESCE(outcome, 'superseded')
6884
+ WHERE id = ?`,
6885
+ args: [supersedesId]
6886
+ },
6887
+ {
6888
+ sql: `UPDATE memories
6889
+ SET importance = MAX(COALESCE(importance, 5), ?),
6890
+ parent_memory_id = COALESCE(parent_memory_id, ?)
6891
+ WHERE id = ?`,
6892
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
6893
+ }
6894
+ ], "write");
6895
+ }
6896
+ } catch (err) {
6897
+ process.stderr.write(
6898
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
6899
+ `
6900
+ );
6901
+ }
6902
+ }
6903
+ function schedulePostWriteMemoryHygiene(memoryIds) {
6904
+ if (memoryIds.length === 0) return;
6905
+ const run = () => {
6906
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
6907
+ };
6908
+ if (typeof setImmediate === "function") setImmediate(run);
6909
+ else setTimeout(run, 0);
6910
+ }
6911
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
6912
+ var init_memory_write_governor = __esm({
6913
+ "src/lib/memory-write-governor.ts"() {
6914
+ "use strict";
6915
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
6916
+ "decision",
6917
+ "adr",
6918
+ "behavior",
6919
+ "procedure"
6920
+ ]);
6921
+ NOISE_DROP_PATTERNS = [
6922
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
6923
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
6924
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
6925
+ /^\s*Intercom is a speedup, not delivery/im,
6926
+ /^\s*Context bar reads as USAGE not remaining/im
6927
+ ];
6928
+ SKIP_EMBED_PATTERNS = [
6929
+ /tmux capture-pane\b/i,
6930
+ /docker ps\b/i,
6931
+ /docker images\b/i,
6932
+ /git status\b/i,
6933
+ /grep .*node_modules/i,
6934
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
6935
+ ];
6936
+ }
6937
+ });
6938
+
6737
6939
  // src/lib/shard-manager.ts
6738
6940
  var shard_manager_exports = {};
6739
6941
  __export(shard_manager_exports, {
@@ -6898,7 +7100,8 @@ async function ensureShardSchema(client) {
6898
7100
  }
6899
7101
  for (const idx of [
6900
7102
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
6901
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
7103
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
7104
+ "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"
6902
7105
  ]) {
6903
7106
  try {
6904
7107
  await client.execute(idx);
@@ -7323,7 +7526,6 @@ __export(store_exports, {
7323
7526
  vectorToBlob: () => vectorToBlob,
7324
7527
  writeMemory: () => writeMemory
7325
7528
  });
7326
- import { createHash } from "crypto";
7327
7529
  function isBusyError2(err) {
7328
7530
  if (err instanceof Error) {
7329
7531
  const msg = err.message.toLowerCase();
@@ -7437,17 +7639,24 @@ async function writeMemory(record) {
7437
7639
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
7438
7640
  );
7439
7641
  }
7440
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
7441
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
7642
+ const governed = governMemoryRecord(record);
7643
+ if (governed.shouldDrop) return;
7644
+ record = governed.record;
7645
+ const contentHash = governed.contentHash;
7646
+ const memoryType = record.memory_type ?? "raw";
7647
+ if (_pendingRecords.some(
7648
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
7649
+ )) {
7442
7650
  return;
7443
7651
  }
7444
7652
  try {
7445
- const client = getClient();
7446
- const existing = await client.execute({
7447
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
7448
- args: [contentHash, record.agent_id]
7653
+ const existing = await findScopedDuplicate({
7654
+ contentHash,
7655
+ agentId: record.agent_id,
7656
+ projectName: record.project_name,
7657
+ memoryType
7449
7658
  });
7450
- if (existing.rows.length > 0) return;
7659
+ if (existing) return;
7451
7660
  } catch {
7452
7661
  }
7453
7662
  const dbRow = {
@@ -7478,7 +7687,7 @@ async function writeMemory(record) {
7478
7687
  tier: record.tier ?? classifyTier(record),
7479
7688
  supersedes_id: record.supersedes_id ?? null,
7480
7689
  draft: record.draft ? 1 : 0,
7481
- memory_type: record.memory_type ?? "raw",
7690
+ memory_type: memoryType,
7482
7691
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
7483
7692
  content_hash: contentHash,
7484
7693
  intent: record.intent ?? null,
@@ -7637,6 +7846,7 @@ async function flushBatch() {
7637
7846
  const globalClient = getClient();
7638
7847
  const globalStmts = batch.map(buildStmt);
7639
7848
  await globalClient.batch(globalStmts, "write");
7849
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
7640
7850
  _pendingRecords.splice(0, batch.length);
7641
7851
  try {
7642
7852
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -7895,6 +8105,7 @@ var init_store = __esm({
7895
8105
  init_keychain();
7896
8106
  init_config();
7897
8107
  init_state_bus();
8108
+ init_memory_write_governor();
7898
8109
  INIT_MAX_RETRIES = 3;
7899
8110
  INIT_RETRY_DELAY_MS = 1e3;
7900
8111
  _pendingRecords = [];
package/dist/bin/setup.js CHANGED
@@ -3504,6 +3504,14 @@ async function ensureSchema() {
3504
3504
  );
3505
3505
  } catch {
3506
3506
  }
3507
+ try {
3508
+ await client.execute(
3509
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
3510
+ ON memories(content_hash, agent_id, project_name, memory_type)
3511
+ WHERE content_hash IS NOT NULL`
3512
+ );
3513
+ } catch {
3514
+ }
3507
3515
  await client.executeMultiple(`
3508
3516
  CREATE TABLE IF NOT EXISTS entities (
3509
3517
  id TEXT PRIMARY KEY,