@askexenow/exe-os 0.9.34 → 0.9.36

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 (76) hide show
  1. package/dist/bin/backfill-conversations.js +212 -11
  2. package/dist/bin/backfill-responses.js +212 -11
  3. package/dist/bin/backfill-vectors.js +14 -3
  4. package/dist/bin/cleanup-stale-review-tasks.js +224 -12
  5. package/dist/bin/cli.js +265 -23
  6. package/dist/bin/exe-agent.js +1 -1
  7. package/dist/bin/exe-assign.js +217 -12
  8. package/dist/bin/exe-boot.js +25 -4
  9. package/dist/bin/exe-call.js +7 -5
  10. package/dist/bin/exe-dispatch.js +229 -13
  11. package/dist/bin/exe-doctor.js +14 -3
  12. package/dist/bin/exe-export-behaviors.js +301 -14
  13. package/dist/bin/exe-forget.js +245 -21
  14. package/dist/bin/exe-gateway.js +229 -13
  15. package/dist/bin/exe-heartbeat.js +224 -12
  16. package/dist/bin/exe-kill.js +224 -12
  17. package/dist/bin/exe-launch-agent.js +177 -9
  18. package/dist/bin/exe-link.js +8 -0
  19. package/dist/bin/exe-new-employee.js +26 -2
  20. package/dist/bin/exe-pending-messages.js +224 -12
  21. package/dist/bin/exe-pending-notifications.js +224 -12
  22. package/dist/bin/exe-pending-reviews.js +224 -12
  23. package/dist/bin/exe-rename.js +9 -1
  24. package/dist/bin/exe-review.js +224 -12
  25. package/dist/bin/exe-search.js +246 -21
  26. package/dist/bin/exe-session-cleanup.js +229 -13
  27. package/dist/bin/exe-start-codex.js +161 -5
  28. package/dist/bin/exe-start-opencode.js +172 -5
  29. package/dist/bin/exe-status.js +224 -12
  30. package/dist/bin/exe-team.js +224 -12
  31. package/dist/bin/git-sweep.js +229 -13
  32. package/dist/bin/graph-backfill.js +94 -3
  33. package/dist/bin/graph-export.js +224 -12
  34. package/dist/bin/install.js +25 -1
  35. package/dist/bin/intercom-check.js +229 -13
  36. package/dist/bin/scan-tasks.js +229 -13
  37. package/dist/bin/setup.js +15 -5
  38. package/dist/bin/shard-migrate.js +94 -3
  39. package/dist/gateway/index.js +229 -13
  40. package/dist/hooks/bug-report-worker.js +229 -13
  41. package/dist/hooks/codex-stop-task-finalizer.js +224 -12
  42. package/dist/hooks/commit-complete.js +229 -13
  43. package/dist/hooks/error-recall.js +246 -21
  44. package/dist/hooks/ingest.js +224 -12
  45. package/dist/hooks/instructions-loaded.js +224 -12
  46. package/dist/hooks/notification.js +224 -12
  47. package/dist/hooks/post-compact.js +224 -12
  48. package/dist/hooks/post-tool-combined.js +246 -21
  49. package/dist/hooks/pre-compact.js +229 -13
  50. package/dist/hooks/pre-tool-use.js +234 -18
  51. package/dist/hooks/prompt-submit.js +346 -23
  52. package/dist/hooks/session-end.js +229 -13
  53. package/dist/hooks/session-start.js +418 -42
  54. package/dist/hooks/stop.js +224 -12
  55. package/dist/hooks/subagent-stop.js +224 -12
  56. package/dist/hooks/summary-worker.js +15 -2
  57. package/dist/index.js +229 -13
  58. package/dist/lib/cloud-sync.js +8 -0
  59. package/dist/lib/consolidation.js +3 -1
  60. package/dist/lib/database.js +8 -0
  61. package/dist/lib/db.js +8 -0
  62. package/dist/lib/device-registry.js +8 -0
  63. package/dist/lib/employee-templates.js +7 -5
  64. package/dist/lib/exe-daemon.js +1776 -1433
  65. package/dist/lib/hybrid-search.js +246 -21
  66. package/dist/lib/schedules.js +14 -3
  67. package/dist/lib/store.js +217 -12
  68. package/dist/lib/tasks.js +5 -1
  69. package/dist/lib/tmux-routing.js +5 -1
  70. package/dist/mcp/server.js +331 -37
  71. package/dist/mcp/tools/create-task.js +5 -1
  72. package/dist/mcp/tools/update-task.js +5 -1
  73. package/dist/runtime/index.js +229 -13
  74. package/dist/tui/App.js +229 -13
  75. package/package.json +1 -1
  76. package/src/commands/exe/save.md +48 -0
@@ -2279,6 +2279,14 @@ async function ensureSchema() {
2279
2279
  );
2280
2280
  } catch {
2281
2281
  }
2282
+ try {
2283
+ await client.execute(
2284
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2285
+ ON memories(content_hash, agent_id, project_name, memory_type)
2286
+ WHERE content_hash IS NOT NULL`
2287
+ );
2288
+ } catch {
2289
+ }
2282
2290
  await client.executeMultiple(`
2283
2291
  CREATE TABLE IF NOT EXISTS entities (
2284
2292
  id TEXT PRIMARY KEY,
@@ -3024,6 +3032,197 @@ var init_state_bus = __esm({
3024
3032
  }
3025
3033
  });
3026
3034
 
3035
+ // src/lib/memory-write-governor.ts
3036
+ import { createHash } from "crypto";
3037
+ function normalizeMemoryText(text) {
3038
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3039
+ }
3040
+ function classifyMemoryType(input2) {
3041
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3042
+ const tool = input2.tool_name.toLowerCase();
3043
+ const text = input2.raw_text.toLowerCase();
3044
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3045
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3046
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3047
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3048
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3049
+ if (tool === "store_memory" || tool === "manual") return "observation";
3050
+ return "raw";
3051
+ }
3052
+ function shouldDropMemory(text) {
3053
+ const normalized = normalizeMemoryText(text);
3054
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3055
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3056
+ return { drop: true, reason: "known_boilerplate_noise" };
3057
+ }
3058
+ return { drop: false };
3059
+ }
3060
+ function shouldSkipEmbedding(input2) {
3061
+ const type = classifyMemoryType(input2);
3062
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3063
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3064
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3065
+ return false;
3066
+ }
3067
+ function hashMemoryContent(text) {
3068
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3069
+ }
3070
+ function scopedDedupArgs(input2) {
3071
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3072
+ }
3073
+ function governMemoryRecord(record) {
3074
+ const normalized = normalizeMemoryText(record.raw_text);
3075
+ const memoryType = classifyMemoryType({
3076
+ raw_text: normalized,
3077
+ agent_id: record.agent_id,
3078
+ project_name: record.project_name,
3079
+ tool_name: record.tool_name,
3080
+ memory_type: record.memory_type
3081
+ });
3082
+ const drop = shouldDropMemory(normalized);
3083
+ const skipEmbedding = shouldSkipEmbedding({
3084
+ raw_text: normalized,
3085
+ agent_id: record.agent_id,
3086
+ project_name: record.project_name,
3087
+ tool_name: record.tool_name,
3088
+ memory_type: memoryType
3089
+ });
3090
+ return {
3091
+ record: {
3092
+ ...record,
3093
+ raw_text: normalized,
3094
+ memory_type: memoryType,
3095
+ vector: skipEmbedding ? null : record.vector
3096
+ },
3097
+ contentHash: hashMemoryContent(normalized),
3098
+ shouldDrop: drop.drop,
3099
+ dropReason: drop.reason,
3100
+ skipEmbedding,
3101
+ hygiene: {
3102
+ dedup: true,
3103
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3104
+ }
3105
+ };
3106
+ }
3107
+ async function findScopedDuplicate(input2) {
3108
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3109
+ const client = getClient2();
3110
+ const args = scopedDedupArgs(input2);
3111
+ let sql = `SELECT id FROM memories
3112
+ WHERE content_hash = ?
3113
+ AND agent_id = ?
3114
+ AND project_name = ?
3115
+ AND COALESCE(memory_type, 'raw') = ?
3116
+ AND COALESCE(status, 'active') != 'deleted'`;
3117
+ if (input2.excludeId) {
3118
+ sql += " AND id != ?";
3119
+ args.push(input2.excludeId);
3120
+ }
3121
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3122
+ const result = await client.execute({ sql, args });
3123
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3124
+ }
3125
+ async function runPostWriteMemoryHygiene(memoryId) {
3126
+ try {
3127
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3128
+ const client = getClient2();
3129
+ const current = await client.execute({
3130
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3131
+ importance, timestamp
3132
+ FROM memories
3133
+ WHERE id = ?
3134
+ LIMIT 1`,
3135
+ args: [memoryId]
3136
+ });
3137
+ const row = current.rows[0];
3138
+ if (!row) return;
3139
+ const memoryType = String(row.memory_type ?? "raw");
3140
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3141
+ const agentId = String(row.agent_id);
3142
+ const projectName = String(row.project_name);
3143
+ if (contentHash) {
3144
+ await client.execute({
3145
+ sql: `UPDATE memories
3146
+ SET status = 'deleted',
3147
+ outcome = COALESCE(outcome, 'superseded')
3148
+ WHERE id != ?
3149
+ AND content_hash = ?
3150
+ AND agent_id = ?
3151
+ AND project_name = ?
3152
+ AND COALESCE(memory_type, 'raw') = ?
3153
+ AND COALESCE(status, 'active') = 'active'`,
3154
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3155
+ });
3156
+ }
3157
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3158
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3159
+ const old = await client.execute({
3160
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3161
+ args: [supersedesId]
3162
+ });
3163
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3164
+ const newImportance = Number(row.importance ?? 0);
3165
+ await client.batch([
3166
+ {
3167
+ sql: `UPDATE memories
3168
+ SET status = 'archived',
3169
+ outcome = COALESCE(outcome, 'superseded')
3170
+ WHERE id = ?`,
3171
+ args: [supersedesId]
3172
+ },
3173
+ {
3174
+ sql: `UPDATE memories
3175
+ SET importance = MAX(COALESCE(importance, 5), ?),
3176
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3177
+ WHERE id = ?`,
3178
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3179
+ }
3180
+ ], "write");
3181
+ }
3182
+ } catch (err) {
3183
+ process.stderr.write(
3184
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3185
+ `
3186
+ );
3187
+ }
3188
+ }
3189
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3190
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3191
+ if (memoryIds.length === 0) return;
3192
+ const run = () => {
3193
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3194
+ };
3195
+ if (typeof setImmediate === "function") setImmediate(run);
3196
+ else setTimeout(run, 0);
3197
+ }
3198
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3199
+ var init_memory_write_governor = __esm({
3200
+ "src/lib/memory-write-governor.ts"() {
3201
+ "use strict";
3202
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3203
+ "decision",
3204
+ "adr",
3205
+ "behavior",
3206
+ "procedure"
3207
+ ]);
3208
+ NOISE_DROP_PATTERNS = [
3209
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3210
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3211
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3212
+ /^\s*Intercom is a speedup, not delivery/im,
3213
+ /^\s*Context bar reads as USAGE not remaining/im
3214
+ ];
3215
+ SKIP_EMBED_PATTERNS = [
3216
+ /tmux capture-pane\b/i,
3217
+ /docker ps\b/i,
3218
+ /docker images\b/i,
3219
+ /git status\b/i,
3220
+ /grep .*node_modules/i,
3221
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3222
+ ];
3223
+ }
3224
+ });
3225
+
3027
3226
  // src/lib/shard-manager.ts
3028
3227
  var shard_manager_exports = {};
3029
3228
  __export(shard_manager_exports, {
@@ -3188,7 +3387,8 @@ async function ensureShardSchema(client) {
3188
3387
  }
3189
3388
  for (const idx of [
3190
3389
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3191
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3390
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3391
+ "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"
3192
3392
  ]) {
3193
3393
  try {
3194
3394
  await client.execute(idx);
@@ -3382,7 +3582,7 @@ var init_platform_procedures = __esm({
3382
3582
  title: "Chain of command \u2014 who talks to whom",
3383
3583
  domain: "workflow",
3384
3584
  priority: "p0",
3385
- content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3585
+ content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3386
3586
  },
3387
3587
  {
3388
3588
  title: "Single dispatch path \u2014 create_task only",
@@ -3613,7 +3813,6 @@ __export(store_exports, {
3613
3813
  vectorToBlob: () => vectorToBlob,
3614
3814
  writeMemory: () => writeMemory
3615
3815
  });
3616
- import { createHash } from "crypto";
3617
3816
  function isBusyError2(err) {
3618
3817
  if (err instanceof Error) {
3619
3818
  const msg = err.message.toLowerCase();
@@ -3727,17 +3926,24 @@ async function writeMemory(record) {
3727
3926
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3728
3927
  );
3729
3928
  }
3730
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3731
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3929
+ const governed = governMemoryRecord(record);
3930
+ if (governed.shouldDrop) return;
3931
+ record = governed.record;
3932
+ const contentHash = governed.contentHash;
3933
+ const memoryType = record.memory_type ?? "raw";
3934
+ if (_pendingRecords.some(
3935
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3936
+ )) {
3732
3937
  return;
3733
3938
  }
3734
3939
  try {
3735
- const client = getClient();
3736
- const existing = await client.execute({
3737
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3738
- args: [contentHash, record.agent_id]
3940
+ const existing = await findScopedDuplicate({
3941
+ contentHash,
3942
+ agentId: record.agent_id,
3943
+ projectName: record.project_name,
3944
+ memoryType
3739
3945
  });
3740
- if (existing.rows.length > 0) return;
3946
+ if (existing) return;
3741
3947
  } catch {
3742
3948
  }
3743
3949
  const dbRow = {
@@ -3768,7 +3974,7 @@ async function writeMemory(record) {
3768
3974
  tier: record.tier ?? classifyTier(record),
3769
3975
  supersedes_id: record.supersedes_id ?? null,
3770
3976
  draft: record.draft ? 1 : 0,
3771
- memory_type: record.memory_type ?? "raw",
3977
+ memory_type: memoryType,
3772
3978
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3773
3979
  content_hash: contentHash,
3774
3980
  intent: record.intent ?? null,
@@ -3927,6 +4133,7 @@ async function flushBatch() {
3927
4133
  const globalClient = getClient();
3928
4134
  const globalStmts = batch.map(buildStmt);
3929
4135
  await globalClient.batch(globalStmts, "write");
4136
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3930
4137
  _pendingRecords.splice(0, batch.length);
3931
4138
  try {
3932
4139
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4047,7 +4254,11 @@ async function searchMemories(queryVector, agentId, options) {
4047
4254
  sql += ` AND timestamp >= ?`;
4048
4255
  args.push(options.since);
4049
4256
  }
4050
- if (options?.memoryType) {
4257
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4258
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4259
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4260
+ args.push(...uniqueTypes);
4261
+ } else if (options?.memoryType) {
4051
4262
  sql += ` AND memory_type = ?`;
4052
4263
  args.push(options.memoryType);
4053
4264
  }
@@ -4185,6 +4396,7 @@ var init_store = __esm({
4185
4396
  init_keychain();
4186
4397
  init_config();
4187
4398
  init_state_bus();
4399
+ init_memory_write_governor();
4188
4400
  INIT_MAX_RETRIES = 3;
4189
4401
  INIT_RETRY_DELAY_MS = 1e3;
4190
4402
  _pendingRecords = [];
@@ -5448,6 +5660,17 @@ init_store();
5448
5660
  init_store();
5449
5661
  init_database();
5450
5662
  var RRF_K = 60;
5663
+ function appendMemoryTypeFilter(sql, args, column, options) {
5664
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
5665
+ const uniqueTypes = [...new Set(options.memoryTypes)];
5666
+ sql += ` AND ${column} IN (${uniqueTypes.map(() => "?").join(",")})`;
5667
+ args.push(...uniqueTypes);
5668
+ } else if (options?.memoryType) {
5669
+ sql += ` AND ${column} = ?`;
5670
+ args.push(options.memoryType);
5671
+ }
5672
+ return sql;
5673
+ }
5451
5674
  async function hybridSearch(queryText, agentId, options) {
5452
5675
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
5453
5676
  const config = await loadConfig2();
@@ -5676,6 +5899,7 @@ async function estimateCardinality(agentId, options) {
5676
5899
  sql += ` AND timestamp >= ?`;
5677
5900
  args.push(options.since);
5678
5901
  }
5902
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
5679
5903
  try {
5680
5904
  const result = await client.execute({ sql, args });
5681
5905
  return Number(result.rows[0]?.cnt) || 0;
@@ -5794,10 +6018,7 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
5794
6018
  sql += ` AND m.timestamp >= ?`;
5795
6019
  args.push(options.since);
5796
6020
  }
5797
- if (options?.memoryType) {
5798
- sql += ` AND m.memory_type = ?`;
5799
- args.push(options.memoryType);
5800
- }
6021
+ sql = appendMemoryTypeFilter(sql, args, "m.memory_type", options);
5801
6022
  sql += ` ORDER BY rank LIMIT ?`;
5802
6023
  args.push(limit);
5803
6024
  const result = await client.execute({ sql, args });
@@ -5854,9 +6075,16 @@ async function recentRecords(agentId, options, limit, textFilter) {
5854
6075
  AND timestamp >= ? AND timestamp <= ?
5855
6076
  AND COALESCE(status, 'active') = 'active'
5856
6077
  AND ${options?.includeRaw === false ? "COALESCE(memory_type, 'raw') != 'raw'" : "1 = 1"}
6078
+ ${options?.memoryTypes?.length ? `AND memory_type IN (${options.memoryTypes.map(() => "?").join(",")})` : options?.memoryType ? "AND memory_type = ?" : ""}
5857
6079
  AND COALESCE(confidence, 0.7) >= 0.3
5858
6080
  ORDER BY timestamp DESC LIMIT ?`,
5859
- args: [agentId, windowStart, killedAt, boundarySlots]
6081
+ args: [
6082
+ agentId,
6083
+ windowStart,
6084
+ killedAt,
6085
+ ...options?.memoryTypes?.length ? [...new Set(options.memoryTypes)] : options?.memoryType ? [options.memoryType] : [],
6086
+ boundarySlots
6087
+ ]
5860
6088
  });
5861
6089
  for (const row of boundaryResult.rows) {
5862
6090
  sessionBoundaryMemories.push(rowToMemoryRecord(row));
@@ -5902,10 +6130,7 @@ async function recentRecords(agentId, options, limit, textFilter) {
5902
6130
  sql += ` AND timestamp >= ?`;
5903
6131
  args.push(options.since);
5904
6132
  }
5905
- if (options?.memoryType) {
5906
- sql += ` AND memory_type = ?`;
5907
- args.push(options.memoryType);
5908
- }
6133
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
5909
6134
  if (textFilter) {
5910
6135
  sql += ` AND raw_text LIKE '%' || ? || '%'`;
5911
6136
  args.push(textFilter);
@@ -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,197 @@ 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 (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3367
+ if (memoryIds.length === 0) return;
3368
+ const run = () => {
3369
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3370
+ };
3371
+ if (typeof setImmediate === "function") setImmediate(run);
3372
+ else setTimeout(run, 0);
3373
+ }
3374
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3375
+ var init_memory_write_governor = __esm({
3376
+ "src/lib/memory-write-governor.ts"() {
3377
+ "use strict";
3378
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3379
+ "decision",
3380
+ "adr",
3381
+ "behavior",
3382
+ "procedure"
3383
+ ]);
3384
+ NOISE_DROP_PATTERNS = [
3385
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3386
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3387
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3388
+ /^\s*Intercom is a speedup, not delivery/im,
3389
+ /^\s*Context bar reads as USAGE not remaining/im
3390
+ ];
3391
+ SKIP_EMBED_PATTERNS = [
3392
+ /tmux capture-pane\b/i,
3393
+ /docker ps\b/i,
3394
+ /docker images\b/i,
3395
+ /git status\b/i,
3396
+ /grep .*node_modules/i,
3397
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3398
+ ];
3399
+ }
3400
+ });
3401
+
3203
3402
  // src/lib/shard-manager.ts
3204
3403
  var shard_manager_exports = {};
3205
3404
  __export(shard_manager_exports, {
@@ -3364,7 +3563,8 @@ async function ensureShardSchema(client) {
3364
3563
  }
3365
3564
  for (const idx of [
3366
3565
  "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"
3566
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3567
+ "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
3568
  ]) {
3369
3569
  try {
3370
3570
  await client.execute(idx);
@@ -3558,7 +3758,7 @@ var init_platform_procedures = __esm({
3558
3758
  title: "Chain of command \u2014 who talks to whom",
3559
3759
  domain: "workflow",
3560
3760
  priority: "p0",
3561
- content: "Founder -> COO -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the COO does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3761
+ content: "Founder -> coordinator (the executive agent, internally routed as 'COO') -> CTO/CMO. CTO -> engineers. CMO -> content production. Never skip levels: the coordinator does not bypass managers for specialist work. Specialists report to their manager. If you need cross-team info, use ask_team_memory \u2014 don't read other agents' task folders. Each level owns dispatch downward and review upward."
3562
3762
  },
3563
3763
  {
3564
3764
  title: "Single dispatch path \u2014 create_task only",
@@ -3789,7 +3989,6 @@ __export(store_exports, {
3789
3989
  vectorToBlob: () => vectorToBlob,
3790
3990
  writeMemory: () => writeMemory
3791
3991
  });
3792
- import { createHash } from "crypto";
3793
3992
  function isBusyError2(err) {
3794
3993
  if (err instanceof Error) {
3795
3994
  const msg = err.message.toLowerCase();
@@ -3903,17 +4102,24 @@ async function writeMemory(record) {
3903
4102
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3904
4103
  );
3905
4104
  }
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)) {
4105
+ const governed = governMemoryRecord(record);
4106
+ if (governed.shouldDrop) return;
4107
+ record = governed.record;
4108
+ const contentHash = governed.contentHash;
4109
+ const memoryType = record.memory_type ?? "raw";
4110
+ if (_pendingRecords.some(
4111
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4112
+ )) {
3908
4113
  return;
3909
4114
  }
3910
4115
  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]
4116
+ const existing = await findScopedDuplicate({
4117
+ contentHash,
4118
+ agentId: record.agent_id,
4119
+ projectName: record.project_name,
4120
+ memoryType
3915
4121
  });
3916
- if (existing.rows.length > 0) return;
4122
+ if (existing) return;
3917
4123
  } catch {
3918
4124
  }
3919
4125
  const dbRow = {
@@ -3944,7 +4150,7 @@ async function writeMemory(record) {
3944
4150
  tier: record.tier ?? classifyTier(record),
3945
4151
  supersedes_id: record.supersedes_id ?? null,
3946
4152
  draft: record.draft ? 1 : 0,
3947
- memory_type: record.memory_type ?? "raw",
4153
+ memory_type: memoryType,
3948
4154
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3949
4155
  content_hash: contentHash,
3950
4156
  intent: record.intent ?? null,
@@ -4103,6 +4309,7 @@ async function flushBatch() {
4103
4309
  const globalClient = getClient();
4104
4310
  const globalStmts = batch.map(buildStmt);
4105
4311
  await globalClient.batch(globalStmts, "write");
4312
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4106
4313
  _pendingRecords.splice(0, batch.length);
4107
4314
  try {
4108
4315
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4223,7 +4430,11 @@ async function searchMemories(queryVector, agentId, options) {
4223
4430
  sql += ` AND timestamp >= ?`;
4224
4431
  args.push(options.since);
4225
4432
  }
4226
- if (options?.memoryType) {
4433
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4434
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4435
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4436
+ args.push(...uniqueTypes);
4437
+ } else if (options?.memoryType) {
4227
4438
  sql += ` AND memory_type = ?`;
4228
4439
  args.push(options.memoryType);
4229
4440
  }
@@ -4361,6 +4572,7 @@ var init_store = __esm({
4361
4572
  init_keychain();
4362
4573
  init_config();
4363
4574
  init_state_bus();
4575
+ init_memory_write_governor();
4364
4576
  INIT_MAX_RETRIES = 3;
4365
4577
  INIT_RETRY_DELAY_MS = 1e3;
4366
4578
  _pendingRecords = [];