@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
@@ -2933,6 +2933,14 @@ async function ensureSchema() {
2933
2933
  );
2934
2934
  } catch {
2935
2935
  }
2936
+ try {
2937
+ await client.execute(
2938
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2939
+ ON memories(content_hash, agent_id, project_name, memory_type)
2940
+ WHERE content_hash IS NOT NULL`
2941
+ );
2942
+ } catch {
2943
+ }
2936
2944
  await client.executeMultiple(`
2937
2945
  CREATE TABLE IF NOT EXISTS entities (
2938
2946
  id TEXT PRIMARY KEY,
@@ -3691,6 +3699,196 @@ var init_keychain = __esm({
3691
3699
  }
3692
3700
  });
3693
3701
 
3702
+ // src/lib/memory-write-governor.ts
3703
+ import { createHash } from "crypto";
3704
+ function normalizeMemoryText(text) {
3705
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3706
+ }
3707
+ function classifyMemoryType(input) {
3708
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3709
+ const tool = input.tool_name.toLowerCase();
3710
+ const text = input.raw_text.toLowerCase();
3711
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3712
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3713
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3714
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3715
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3716
+ if (tool === "store_memory" || tool === "manual") return "observation";
3717
+ return "raw";
3718
+ }
3719
+ function shouldDropMemory(text) {
3720
+ const normalized = normalizeMemoryText(text);
3721
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3722
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3723
+ return { drop: true, reason: "known_boilerplate_noise" };
3724
+ }
3725
+ return { drop: false };
3726
+ }
3727
+ function shouldSkipEmbedding(input) {
3728
+ const type = classifyMemoryType(input);
3729
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3730
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3731
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3732
+ return false;
3733
+ }
3734
+ function hashMemoryContent(text) {
3735
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3736
+ }
3737
+ function scopedDedupArgs(input) {
3738
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3739
+ }
3740
+ function governMemoryRecord(record) {
3741
+ const normalized = normalizeMemoryText(record.raw_text);
3742
+ const memoryType = classifyMemoryType({
3743
+ raw_text: normalized,
3744
+ agent_id: record.agent_id,
3745
+ project_name: record.project_name,
3746
+ tool_name: record.tool_name,
3747
+ memory_type: record.memory_type
3748
+ });
3749
+ const drop = shouldDropMemory(normalized);
3750
+ const skipEmbedding = shouldSkipEmbedding({
3751
+ raw_text: normalized,
3752
+ agent_id: record.agent_id,
3753
+ project_name: record.project_name,
3754
+ tool_name: record.tool_name,
3755
+ memory_type: memoryType
3756
+ });
3757
+ return {
3758
+ record: {
3759
+ ...record,
3760
+ raw_text: normalized,
3761
+ memory_type: memoryType,
3762
+ vector: skipEmbedding ? null : record.vector
3763
+ },
3764
+ contentHash: hashMemoryContent(normalized),
3765
+ shouldDrop: drop.drop,
3766
+ dropReason: drop.reason,
3767
+ skipEmbedding,
3768
+ hygiene: {
3769
+ dedup: true,
3770
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3771
+ }
3772
+ };
3773
+ }
3774
+ async function findScopedDuplicate(input) {
3775
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3776
+ const client = getClient2();
3777
+ const args = scopedDedupArgs(input);
3778
+ let sql = `SELECT id FROM memories
3779
+ WHERE content_hash = ?
3780
+ AND agent_id = ?
3781
+ AND project_name = ?
3782
+ AND COALESCE(memory_type, 'raw') = ?
3783
+ AND COALESCE(status, 'active') != 'deleted'`;
3784
+ if (input.excludeId) {
3785
+ sql += " AND id != ?";
3786
+ args.push(input.excludeId);
3787
+ }
3788
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3789
+ const result = await client.execute({ sql, args });
3790
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3791
+ }
3792
+ async function runPostWriteMemoryHygiene(memoryId) {
3793
+ try {
3794
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3795
+ const client = getClient2();
3796
+ const current = await client.execute({
3797
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3798
+ importance, timestamp
3799
+ FROM memories
3800
+ WHERE id = ?
3801
+ LIMIT 1`,
3802
+ args: [memoryId]
3803
+ });
3804
+ const row = current.rows[0];
3805
+ if (!row) return;
3806
+ const memoryType = String(row.memory_type ?? "raw");
3807
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3808
+ const agentId = String(row.agent_id);
3809
+ const projectName = String(row.project_name);
3810
+ if (contentHash) {
3811
+ await client.execute({
3812
+ sql: `UPDATE memories
3813
+ SET status = 'deleted',
3814
+ outcome = COALESCE(outcome, 'superseded')
3815
+ WHERE id != ?
3816
+ AND content_hash = ?
3817
+ AND agent_id = ?
3818
+ AND project_name = ?
3819
+ AND COALESCE(memory_type, 'raw') = ?
3820
+ AND COALESCE(status, 'active') = 'active'`,
3821
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3822
+ });
3823
+ }
3824
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3825
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3826
+ const old = await client.execute({
3827
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3828
+ args: [supersedesId]
3829
+ });
3830
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3831
+ const newImportance = Number(row.importance ?? 0);
3832
+ await client.batch([
3833
+ {
3834
+ sql: `UPDATE memories
3835
+ SET status = 'archived',
3836
+ outcome = COALESCE(outcome, 'superseded')
3837
+ WHERE id = ?`,
3838
+ args: [supersedesId]
3839
+ },
3840
+ {
3841
+ sql: `UPDATE memories
3842
+ SET importance = MAX(COALESCE(importance, 5), ?),
3843
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3844
+ WHERE id = ?`,
3845
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3846
+ }
3847
+ ], "write");
3848
+ }
3849
+ } catch (err) {
3850
+ process.stderr.write(
3851
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3852
+ `
3853
+ );
3854
+ }
3855
+ }
3856
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3857
+ if (memoryIds.length === 0) return;
3858
+ const run = () => {
3859
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3860
+ };
3861
+ if (typeof setImmediate === "function") setImmediate(run);
3862
+ else setTimeout(run, 0);
3863
+ }
3864
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3865
+ var init_memory_write_governor = __esm({
3866
+ "src/lib/memory-write-governor.ts"() {
3867
+ "use strict";
3868
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3869
+ "decision",
3870
+ "adr",
3871
+ "behavior",
3872
+ "procedure"
3873
+ ]);
3874
+ NOISE_DROP_PATTERNS = [
3875
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3876
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3877
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3878
+ /^\s*Intercom is a speedup, not delivery/im,
3879
+ /^\s*Context bar reads as USAGE not remaining/im
3880
+ ];
3881
+ SKIP_EMBED_PATTERNS = [
3882
+ /tmux capture-pane\b/i,
3883
+ /docker ps\b/i,
3884
+ /docker images\b/i,
3885
+ /git status\b/i,
3886
+ /grep .*node_modules/i,
3887
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3888
+ ];
3889
+ }
3890
+ });
3891
+
3694
3892
  // src/lib/shard-manager.ts
3695
3893
  var shard_manager_exports = {};
3696
3894
  __export(shard_manager_exports, {
@@ -3855,7 +4053,8 @@ async function ensureShardSchema(client) {
3855
4053
  }
3856
4054
  for (const idx of [
3857
4055
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3858
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
4056
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
4057
+ "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"
3859
4058
  ]) {
3860
4059
  try {
3861
4060
  await client.execute(idx);
@@ -4280,7 +4479,6 @@ __export(store_exports, {
4280
4479
  vectorToBlob: () => vectorToBlob,
4281
4480
  writeMemory: () => writeMemory
4282
4481
  });
4283
- import { createHash } from "crypto";
4284
4482
  function isBusyError2(err) {
4285
4483
  if (err instanceof Error) {
4286
4484
  const msg = err.message.toLowerCase();
@@ -4394,17 +4592,24 @@ async function writeMemory(record) {
4394
4592
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4395
4593
  );
4396
4594
  }
4397
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4398
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4595
+ const governed = governMemoryRecord(record);
4596
+ if (governed.shouldDrop) return;
4597
+ record = governed.record;
4598
+ const contentHash = governed.contentHash;
4599
+ const memoryType = record.memory_type ?? "raw";
4600
+ if (_pendingRecords.some(
4601
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4602
+ )) {
4399
4603
  return;
4400
4604
  }
4401
4605
  try {
4402
- const client = getClient();
4403
- const existing = await client.execute({
4404
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4405
- args: [contentHash, record.agent_id]
4606
+ const existing = await findScopedDuplicate({
4607
+ contentHash,
4608
+ agentId: record.agent_id,
4609
+ projectName: record.project_name,
4610
+ memoryType
4406
4611
  });
4407
- if (existing.rows.length > 0) return;
4612
+ if (existing) return;
4408
4613
  } catch {
4409
4614
  }
4410
4615
  const dbRow = {
@@ -4435,7 +4640,7 @@ async function writeMemory(record) {
4435
4640
  tier: record.tier ?? classifyTier(record),
4436
4641
  supersedes_id: record.supersedes_id ?? null,
4437
4642
  draft: record.draft ? 1 : 0,
4438
- memory_type: record.memory_type ?? "raw",
4643
+ memory_type: memoryType,
4439
4644
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4440
4645
  content_hash: contentHash,
4441
4646
  intent: record.intent ?? null,
@@ -4594,6 +4799,7 @@ async function flushBatch() {
4594
4799
  const globalClient = getClient();
4595
4800
  const globalStmts = batch.map(buildStmt);
4596
4801
  await globalClient.batch(globalStmts, "write");
4802
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4597
4803
  _pendingRecords.splice(0, batch.length);
4598
4804
  try {
4599
4805
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4852,6 +5058,7 @@ var init_store = __esm({
4852
5058
  init_keychain();
4853
5059
  init_config();
4854
5060
  init_state_bus();
5061
+ init_memory_write_governor();
4855
5062
  INIT_MAX_RETRIES = 3;
4856
5063
  INIT_RETRY_DELAY_MS = 1e3;
4857
5064
  _pendingRecords = [];
@@ -11377,7 +11584,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
11377
11584
  }
11378
11585
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
11379
11586
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
11380
- envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
11587
+ let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
11588
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
11589
+ ccModel += "[1m]";
11590
+ }
11591
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
11381
11592
  }
11382
11593
  }
11383
11594
  let spawnCommand;
@@ -2318,6 +2318,14 @@ async function ensureSchema() {
2318
2318
  );
2319
2319
  } catch {
2320
2320
  }
2321
+ try {
2322
+ await client.execute(
2323
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2324
+ ON memories(content_hash, agent_id, project_name, memory_type)
2325
+ WHERE content_hash IS NOT NULL`
2326
+ );
2327
+ } catch {
2328
+ }
2321
2329
  await client.executeMultiple(`
2322
2330
  CREATE TABLE IF NOT EXISTS entities (
2323
2331
  id TEXT PRIMARY KEY,
@@ -3063,6 +3071,196 @@ var init_state_bus = __esm({
3063
3071
  }
3064
3072
  });
3065
3073
 
3074
+ // src/lib/memory-write-governor.ts
3075
+ import { createHash } from "crypto";
3076
+ function normalizeMemoryText(text) {
3077
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3078
+ }
3079
+ function classifyMemoryType(input) {
3080
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3081
+ const tool = input.tool_name.toLowerCase();
3082
+ const text = input.raw_text.toLowerCase();
3083
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3084
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3085
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3086
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3087
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3088
+ if (tool === "store_memory" || tool === "manual") return "observation";
3089
+ return "raw";
3090
+ }
3091
+ function shouldDropMemory(text) {
3092
+ const normalized = normalizeMemoryText(text);
3093
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3094
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3095
+ return { drop: true, reason: "known_boilerplate_noise" };
3096
+ }
3097
+ return { drop: false };
3098
+ }
3099
+ function shouldSkipEmbedding(input) {
3100
+ const type = classifyMemoryType(input);
3101
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3102
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3103
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3104
+ return false;
3105
+ }
3106
+ function hashMemoryContent(text) {
3107
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3108
+ }
3109
+ function scopedDedupArgs(input) {
3110
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3111
+ }
3112
+ function governMemoryRecord(record) {
3113
+ const normalized = normalizeMemoryText(record.raw_text);
3114
+ const memoryType = classifyMemoryType({
3115
+ raw_text: normalized,
3116
+ agent_id: record.agent_id,
3117
+ project_name: record.project_name,
3118
+ tool_name: record.tool_name,
3119
+ memory_type: record.memory_type
3120
+ });
3121
+ const drop = shouldDropMemory(normalized);
3122
+ const skipEmbedding = shouldSkipEmbedding({
3123
+ raw_text: normalized,
3124
+ agent_id: record.agent_id,
3125
+ project_name: record.project_name,
3126
+ tool_name: record.tool_name,
3127
+ memory_type: memoryType
3128
+ });
3129
+ return {
3130
+ record: {
3131
+ ...record,
3132
+ raw_text: normalized,
3133
+ memory_type: memoryType,
3134
+ vector: skipEmbedding ? null : record.vector
3135
+ },
3136
+ contentHash: hashMemoryContent(normalized),
3137
+ shouldDrop: drop.drop,
3138
+ dropReason: drop.reason,
3139
+ skipEmbedding,
3140
+ hygiene: {
3141
+ dedup: true,
3142
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3143
+ }
3144
+ };
3145
+ }
3146
+ async function findScopedDuplicate(input) {
3147
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3148
+ const client = getClient2();
3149
+ const args = scopedDedupArgs(input);
3150
+ let sql = `SELECT id FROM memories
3151
+ WHERE content_hash = ?
3152
+ AND agent_id = ?
3153
+ AND project_name = ?
3154
+ AND COALESCE(memory_type, 'raw') = ?
3155
+ AND COALESCE(status, 'active') != 'deleted'`;
3156
+ if (input.excludeId) {
3157
+ sql += " AND id != ?";
3158
+ args.push(input.excludeId);
3159
+ }
3160
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3161
+ const result = await client.execute({ sql, args });
3162
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3163
+ }
3164
+ async function runPostWriteMemoryHygiene(memoryId) {
3165
+ try {
3166
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3167
+ const client = getClient2();
3168
+ const current = await client.execute({
3169
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3170
+ importance, timestamp
3171
+ FROM memories
3172
+ WHERE id = ?
3173
+ LIMIT 1`,
3174
+ args: [memoryId]
3175
+ });
3176
+ const row = current.rows[0];
3177
+ if (!row) return;
3178
+ const memoryType = String(row.memory_type ?? "raw");
3179
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3180
+ const agentId = String(row.agent_id);
3181
+ const projectName = String(row.project_name);
3182
+ if (contentHash) {
3183
+ await client.execute({
3184
+ sql: `UPDATE memories
3185
+ SET status = 'deleted',
3186
+ outcome = COALESCE(outcome, 'superseded')
3187
+ WHERE id != ?
3188
+ AND content_hash = ?
3189
+ AND agent_id = ?
3190
+ AND project_name = ?
3191
+ AND COALESCE(memory_type, 'raw') = ?
3192
+ AND COALESCE(status, 'active') = 'active'`,
3193
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3194
+ });
3195
+ }
3196
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3197
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3198
+ const old = await client.execute({
3199
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3200
+ args: [supersedesId]
3201
+ });
3202
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3203
+ const newImportance = Number(row.importance ?? 0);
3204
+ await client.batch([
3205
+ {
3206
+ sql: `UPDATE memories
3207
+ SET status = 'archived',
3208
+ outcome = COALESCE(outcome, 'superseded')
3209
+ WHERE id = ?`,
3210
+ args: [supersedesId]
3211
+ },
3212
+ {
3213
+ sql: `UPDATE memories
3214
+ SET importance = MAX(COALESCE(importance, 5), ?),
3215
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3216
+ WHERE id = ?`,
3217
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3218
+ }
3219
+ ], "write");
3220
+ }
3221
+ } catch (err) {
3222
+ process.stderr.write(
3223
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3224
+ `
3225
+ );
3226
+ }
3227
+ }
3228
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3229
+ if (memoryIds.length === 0) return;
3230
+ const run = () => {
3231
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3232
+ };
3233
+ if (typeof setImmediate === "function") setImmediate(run);
3234
+ else setTimeout(run, 0);
3235
+ }
3236
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3237
+ var init_memory_write_governor = __esm({
3238
+ "src/lib/memory-write-governor.ts"() {
3239
+ "use strict";
3240
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3241
+ "decision",
3242
+ "adr",
3243
+ "behavior",
3244
+ "procedure"
3245
+ ]);
3246
+ NOISE_DROP_PATTERNS = [
3247
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3248
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3249
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3250
+ /^\s*Intercom is a speedup, not delivery/im,
3251
+ /^\s*Context bar reads as USAGE not remaining/im
3252
+ ];
3253
+ SKIP_EMBED_PATTERNS = [
3254
+ /tmux capture-pane\b/i,
3255
+ /docker ps\b/i,
3256
+ /docker images\b/i,
3257
+ /git status\b/i,
3258
+ /grep .*node_modules/i,
3259
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3260
+ ];
3261
+ }
3262
+ });
3263
+
3066
3264
  // src/lib/shard-manager.ts
3067
3265
  var shard_manager_exports = {};
3068
3266
  __export(shard_manager_exports, {
@@ -3227,7 +3425,8 @@ async function ensureShardSchema(client) {
3227
3425
  }
3228
3426
  for (const idx of [
3229
3427
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3230
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3428
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3429
+ "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"
3231
3430
  ]) {
3232
3431
  try {
3233
3432
  await client.execute(idx);
@@ -3652,7 +3851,6 @@ __export(store_exports, {
3652
3851
  vectorToBlob: () => vectorToBlob,
3653
3852
  writeMemory: () => writeMemory
3654
3853
  });
3655
- import { createHash } from "crypto";
3656
3854
  function isBusyError2(err) {
3657
3855
  if (err instanceof Error) {
3658
3856
  const msg = err.message.toLowerCase();
@@ -3766,17 +3964,24 @@ async function writeMemory(record) {
3766
3964
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3767
3965
  );
3768
3966
  }
3769
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3770
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3967
+ const governed = governMemoryRecord(record);
3968
+ if (governed.shouldDrop) return;
3969
+ record = governed.record;
3970
+ const contentHash = governed.contentHash;
3971
+ const memoryType = record.memory_type ?? "raw";
3972
+ if (_pendingRecords.some(
3973
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3974
+ )) {
3771
3975
  return;
3772
3976
  }
3773
3977
  try {
3774
- const client = getClient();
3775
- const existing = await client.execute({
3776
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3777
- args: [contentHash, record.agent_id]
3978
+ const existing = await findScopedDuplicate({
3979
+ contentHash,
3980
+ agentId: record.agent_id,
3981
+ projectName: record.project_name,
3982
+ memoryType
3778
3983
  });
3779
- if (existing.rows.length > 0) return;
3984
+ if (existing) return;
3780
3985
  } catch {
3781
3986
  }
3782
3987
  const dbRow = {
@@ -3807,7 +4012,7 @@ async function writeMemory(record) {
3807
4012
  tier: record.tier ?? classifyTier(record),
3808
4013
  supersedes_id: record.supersedes_id ?? null,
3809
4014
  draft: record.draft ? 1 : 0,
3810
- memory_type: record.memory_type ?? "raw",
4015
+ memory_type: memoryType,
3811
4016
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3812
4017
  content_hash: contentHash,
3813
4018
  intent: record.intent ?? null,
@@ -3966,6 +4171,7 @@ async function flushBatch() {
3966
4171
  const globalClient = getClient();
3967
4172
  const globalStmts = batch.map(buildStmt);
3968
4173
  await globalClient.batch(globalStmts, "write");
4174
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3969
4175
  _pendingRecords.splice(0, batch.length);
3970
4176
  try {
3971
4177
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4224,6 +4430,7 @@ var init_store = __esm({
4224
4430
  init_keychain();
4225
4431
  init_config();
4226
4432
  init_state_bus();
4433
+ init_memory_write_governor();
4227
4434
  INIT_MAX_RETRIES = 3;
4228
4435
  INIT_RETRY_DELAY_MS = 1e3;
4229
4436
  _pendingRecords = [];