@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
@@ -2302,6 +2302,14 @@ async function ensureSchema() {
2302
2302
  );
2303
2303
  } catch {
2304
2304
  }
2305
+ try {
2306
+ await client.execute(
2307
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2308
+ ON memories(content_hash, agent_id, project_name, memory_type)
2309
+ WHERE content_hash IS NOT NULL`
2310
+ );
2311
+ } catch {
2312
+ }
2305
2313
  await client.executeMultiple(`
2306
2314
  CREATE TABLE IF NOT EXISTS entities (
2307
2315
  id TEXT PRIMARY KEY,
@@ -3047,6 +3055,197 @@ var init_state_bus = __esm({
3047
3055
  }
3048
3056
  });
3049
3057
 
3058
+ // src/lib/memory-write-governor.ts
3059
+ import { createHash } from "crypto";
3060
+ function normalizeMemoryText(text) {
3061
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3062
+ }
3063
+ function classifyMemoryType(input) {
3064
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3065
+ const tool = input.tool_name.toLowerCase();
3066
+ const text = input.raw_text.toLowerCase();
3067
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3068
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3069
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3070
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3071
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3072
+ if (tool === "store_memory" || tool === "manual") return "observation";
3073
+ return "raw";
3074
+ }
3075
+ function shouldDropMemory(text) {
3076
+ const normalized = normalizeMemoryText(text);
3077
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3078
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3079
+ return { drop: true, reason: "known_boilerplate_noise" };
3080
+ }
3081
+ return { drop: false };
3082
+ }
3083
+ function shouldSkipEmbedding(input) {
3084
+ const type = classifyMemoryType(input);
3085
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3086
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3087
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3088
+ return false;
3089
+ }
3090
+ function hashMemoryContent(text) {
3091
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3092
+ }
3093
+ function scopedDedupArgs(input) {
3094
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3095
+ }
3096
+ function governMemoryRecord(record) {
3097
+ const normalized = normalizeMemoryText(record.raw_text);
3098
+ const memoryType = classifyMemoryType({
3099
+ raw_text: normalized,
3100
+ agent_id: record.agent_id,
3101
+ project_name: record.project_name,
3102
+ tool_name: record.tool_name,
3103
+ memory_type: record.memory_type
3104
+ });
3105
+ const drop = shouldDropMemory(normalized);
3106
+ const skipEmbedding = shouldSkipEmbedding({
3107
+ raw_text: normalized,
3108
+ agent_id: record.agent_id,
3109
+ project_name: record.project_name,
3110
+ tool_name: record.tool_name,
3111
+ memory_type: memoryType
3112
+ });
3113
+ return {
3114
+ record: {
3115
+ ...record,
3116
+ raw_text: normalized,
3117
+ memory_type: memoryType,
3118
+ vector: skipEmbedding ? null : record.vector
3119
+ },
3120
+ contentHash: hashMemoryContent(normalized),
3121
+ shouldDrop: drop.drop,
3122
+ dropReason: drop.reason,
3123
+ skipEmbedding,
3124
+ hygiene: {
3125
+ dedup: true,
3126
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3127
+ }
3128
+ };
3129
+ }
3130
+ async function findScopedDuplicate(input) {
3131
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3132
+ const client = getClient2();
3133
+ const args = scopedDedupArgs(input);
3134
+ let sql = `SELECT id FROM memories
3135
+ WHERE content_hash = ?
3136
+ AND agent_id = ?
3137
+ AND project_name = ?
3138
+ AND COALESCE(memory_type, 'raw') = ?
3139
+ AND COALESCE(status, 'active') != 'deleted'`;
3140
+ if (input.excludeId) {
3141
+ sql += " AND id != ?";
3142
+ args.push(input.excludeId);
3143
+ }
3144
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3145
+ const result = await client.execute({ sql, args });
3146
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3147
+ }
3148
+ async function runPostWriteMemoryHygiene(memoryId) {
3149
+ try {
3150
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3151
+ const client = getClient2();
3152
+ const current = await client.execute({
3153
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3154
+ importance, timestamp
3155
+ FROM memories
3156
+ WHERE id = ?
3157
+ LIMIT 1`,
3158
+ args: [memoryId]
3159
+ });
3160
+ const row = current.rows[0];
3161
+ if (!row) return;
3162
+ const memoryType = String(row.memory_type ?? "raw");
3163
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3164
+ const agentId = String(row.agent_id);
3165
+ const projectName = String(row.project_name);
3166
+ if (contentHash) {
3167
+ await client.execute({
3168
+ sql: `UPDATE memories
3169
+ SET status = 'deleted',
3170
+ outcome = COALESCE(outcome, 'superseded')
3171
+ WHERE id != ?
3172
+ AND content_hash = ?
3173
+ AND agent_id = ?
3174
+ AND project_name = ?
3175
+ AND COALESCE(memory_type, 'raw') = ?
3176
+ AND COALESCE(status, 'active') = 'active'`,
3177
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3178
+ });
3179
+ }
3180
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3181
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3182
+ const old = await client.execute({
3183
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3184
+ args: [supersedesId]
3185
+ });
3186
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3187
+ const newImportance = Number(row.importance ?? 0);
3188
+ await client.batch([
3189
+ {
3190
+ sql: `UPDATE memories
3191
+ SET status = 'archived',
3192
+ outcome = COALESCE(outcome, 'superseded')
3193
+ WHERE id = ?`,
3194
+ args: [supersedesId]
3195
+ },
3196
+ {
3197
+ sql: `UPDATE memories
3198
+ SET importance = MAX(COALESCE(importance, 5), ?),
3199
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3200
+ WHERE id = ?`,
3201
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3202
+ }
3203
+ ], "write");
3204
+ }
3205
+ } catch (err) {
3206
+ process.stderr.write(
3207
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3208
+ `
3209
+ );
3210
+ }
3211
+ }
3212
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3213
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3214
+ if (memoryIds.length === 0) return;
3215
+ const run = () => {
3216
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3217
+ };
3218
+ if (typeof setImmediate === "function") setImmediate(run);
3219
+ else setTimeout(run, 0);
3220
+ }
3221
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3222
+ var init_memory_write_governor = __esm({
3223
+ "src/lib/memory-write-governor.ts"() {
3224
+ "use strict";
3225
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3226
+ "decision",
3227
+ "adr",
3228
+ "behavior",
3229
+ "procedure"
3230
+ ]);
3231
+ NOISE_DROP_PATTERNS = [
3232
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3233
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3234
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3235
+ /^\s*Intercom is a speedup, not delivery/im,
3236
+ /^\s*Context bar reads as USAGE not remaining/im
3237
+ ];
3238
+ SKIP_EMBED_PATTERNS = [
3239
+ /tmux capture-pane\b/i,
3240
+ /docker ps\b/i,
3241
+ /docker images\b/i,
3242
+ /git status\b/i,
3243
+ /grep .*node_modules/i,
3244
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3245
+ ];
3246
+ }
3247
+ });
3248
+
3050
3249
  // src/lib/shard-manager.ts
3051
3250
  var shard_manager_exports = {};
3052
3251
  __export(shard_manager_exports, {
@@ -3211,7 +3410,8 @@ async function ensureShardSchema(client) {
3211
3410
  }
3212
3411
  for (const idx of [
3213
3412
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3214
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3413
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3414
+ "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"
3215
3415
  ]) {
3216
3416
  try {
3217
3417
  await client.execute(idx);
@@ -3405,7 +3605,7 @@ var init_platform_procedures = __esm({
3405
3605
  title: "Chain of command \u2014 who talks to whom",
3406
3606
  domain: "workflow",
3407
3607
  priority: "p0",
3408
- 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."
3608
+ 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."
3409
3609
  },
3410
3610
  {
3411
3611
  title: "Single dispatch path \u2014 create_task only",
@@ -3636,7 +3836,6 @@ __export(store_exports, {
3636
3836
  vectorToBlob: () => vectorToBlob,
3637
3837
  writeMemory: () => writeMemory
3638
3838
  });
3639
- import { createHash } from "crypto";
3640
3839
  function isBusyError2(err) {
3641
3840
  if (err instanceof Error) {
3642
3841
  const msg = err.message.toLowerCase();
@@ -3750,17 +3949,24 @@ async function writeMemory(record) {
3750
3949
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3751
3950
  );
3752
3951
  }
3753
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3754
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3952
+ const governed = governMemoryRecord(record);
3953
+ if (governed.shouldDrop) return;
3954
+ record = governed.record;
3955
+ const contentHash = governed.contentHash;
3956
+ const memoryType = record.memory_type ?? "raw";
3957
+ if (_pendingRecords.some(
3958
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3959
+ )) {
3755
3960
  return;
3756
3961
  }
3757
3962
  try {
3758
- const client = getClient();
3759
- const existing = await client.execute({
3760
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3761
- args: [contentHash, record.agent_id]
3963
+ const existing = await findScopedDuplicate({
3964
+ contentHash,
3965
+ agentId: record.agent_id,
3966
+ projectName: record.project_name,
3967
+ memoryType
3762
3968
  });
3763
- if (existing.rows.length > 0) return;
3969
+ if (existing) return;
3764
3970
  } catch {
3765
3971
  }
3766
3972
  const dbRow = {
@@ -3791,7 +3997,7 @@ async function writeMemory(record) {
3791
3997
  tier: record.tier ?? classifyTier(record),
3792
3998
  supersedes_id: record.supersedes_id ?? null,
3793
3999
  draft: record.draft ? 1 : 0,
3794
- memory_type: record.memory_type ?? "raw",
4000
+ memory_type: memoryType,
3795
4001
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3796
4002
  content_hash: contentHash,
3797
4003
  intent: record.intent ?? null,
@@ -3950,6 +4156,7 @@ async function flushBatch() {
3950
4156
  const globalClient = getClient();
3951
4157
  const globalStmts = batch.map(buildStmt);
3952
4158
  await globalClient.batch(globalStmts, "write");
4159
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3953
4160
  _pendingRecords.splice(0, batch.length);
3954
4161
  try {
3955
4162
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4070,7 +4277,11 @@ async function searchMemories(queryVector, agentId, options) {
4070
4277
  sql += ` AND timestamp >= ?`;
4071
4278
  args.push(options.since);
4072
4279
  }
4073
- if (options?.memoryType) {
4280
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4281
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4282
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4283
+ args.push(...uniqueTypes);
4284
+ } else if (options?.memoryType) {
4074
4285
  sql += ` AND memory_type = ?`;
4075
4286
  args.push(options.memoryType);
4076
4287
  }
@@ -4208,6 +4419,7 @@ var init_store = __esm({
4208
4419
  init_keychain();
4209
4420
  init_config();
4210
4421
  init_state_bus();
4422
+ init_memory_write_governor();
4211
4423
  INIT_MAX_RETRIES = 3;
4212
4424
  INIT_RETRY_DELAY_MS = 1e3;
4213
4425
  _pendingRecords = [];
@@ -2277,6 +2277,14 @@ async function ensureSchema() {
2277
2277
  );
2278
2278
  } catch {
2279
2279
  }
2280
+ try {
2281
+ await client.execute(
2282
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2283
+ ON memories(content_hash, agent_id, project_name, memory_type)
2284
+ WHERE content_hash IS NOT NULL`
2285
+ );
2286
+ } catch {
2287
+ }
2280
2288
  await client.executeMultiple(`
2281
2289
  CREATE TABLE IF NOT EXISTS entities (
2282
2290
  id TEXT PRIMARY KEY,
@@ -3022,6 +3030,197 @@ var init_state_bus = __esm({
3022
3030
  }
3023
3031
  });
3024
3032
 
3033
+ // src/lib/memory-write-governor.ts
3034
+ import { createHash } from "crypto";
3035
+ function normalizeMemoryText(text) {
3036
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3037
+ }
3038
+ function classifyMemoryType(input) {
3039
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3040
+ const tool = input.tool_name.toLowerCase();
3041
+ const text = input.raw_text.toLowerCase();
3042
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3043
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3044
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3045
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3046
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3047
+ if (tool === "store_memory" || tool === "manual") return "observation";
3048
+ return "raw";
3049
+ }
3050
+ function shouldDropMemory(text) {
3051
+ const normalized = normalizeMemoryText(text);
3052
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3053
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3054
+ return { drop: true, reason: "known_boilerplate_noise" };
3055
+ }
3056
+ return { drop: false };
3057
+ }
3058
+ function shouldSkipEmbedding(input) {
3059
+ const type = classifyMemoryType(input);
3060
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3061
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3062
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3063
+ return false;
3064
+ }
3065
+ function hashMemoryContent(text) {
3066
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3067
+ }
3068
+ function scopedDedupArgs(input) {
3069
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3070
+ }
3071
+ function governMemoryRecord(record) {
3072
+ const normalized = normalizeMemoryText(record.raw_text);
3073
+ const memoryType = classifyMemoryType({
3074
+ raw_text: normalized,
3075
+ agent_id: record.agent_id,
3076
+ project_name: record.project_name,
3077
+ tool_name: record.tool_name,
3078
+ memory_type: record.memory_type
3079
+ });
3080
+ const drop = shouldDropMemory(normalized);
3081
+ const skipEmbedding = shouldSkipEmbedding({
3082
+ raw_text: normalized,
3083
+ agent_id: record.agent_id,
3084
+ project_name: record.project_name,
3085
+ tool_name: record.tool_name,
3086
+ memory_type: memoryType
3087
+ });
3088
+ return {
3089
+ record: {
3090
+ ...record,
3091
+ raw_text: normalized,
3092
+ memory_type: memoryType,
3093
+ vector: skipEmbedding ? null : record.vector
3094
+ },
3095
+ contentHash: hashMemoryContent(normalized),
3096
+ shouldDrop: drop.drop,
3097
+ dropReason: drop.reason,
3098
+ skipEmbedding,
3099
+ hygiene: {
3100
+ dedup: true,
3101
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3102
+ }
3103
+ };
3104
+ }
3105
+ async function findScopedDuplicate(input) {
3106
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3107
+ const client = getClient2();
3108
+ const args = scopedDedupArgs(input);
3109
+ let sql = `SELECT id FROM memories
3110
+ WHERE content_hash = ?
3111
+ AND agent_id = ?
3112
+ AND project_name = ?
3113
+ AND COALESCE(memory_type, 'raw') = ?
3114
+ AND COALESCE(status, 'active') != 'deleted'`;
3115
+ if (input.excludeId) {
3116
+ sql += " AND id != ?";
3117
+ args.push(input.excludeId);
3118
+ }
3119
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3120
+ const result = await client.execute({ sql, args });
3121
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3122
+ }
3123
+ async function runPostWriteMemoryHygiene(memoryId) {
3124
+ try {
3125
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3126
+ const client = getClient2();
3127
+ const current = await client.execute({
3128
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3129
+ importance, timestamp
3130
+ FROM memories
3131
+ WHERE id = ?
3132
+ LIMIT 1`,
3133
+ args: [memoryId]
3134
+ });
3135
+ const row = current.rows[0];
3136
+ if (!row) return;
3137
+ const memoryType = String(row.memory_type ?? "raw");
3138
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3139
+ const agentId = String(row.agent_id);
3140
+ const projectName = String(row.project_name);
3141
+ if (contentHash) {
3142
+ await client.execute({
3143
+ sql: `UPDATE memories
3144
+ SET status = 'deleted',
3145
+ outcome = COALESCE(outcome, 'superseded')
3146
+ WHERE id != ?
3147
+ AND content_hash = ?
3148
+ AND agent_id = ?
3149
+ AND project_name = ?
3150
+ AND COALESCE(memory_type, 'raw') = ?
3151
+ AND COALESCE(status, 'active') = 'active'`,
3152
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3153
+ });
3154
+ }
3155
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3156
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3157
+ const old = await client.execute({
3158
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3159
+ args: [supersedesId]
3160
+ });
3161
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3162
+ const newImportance = Number(row.importance ?? 0);
3163
+ await client.batch([
3164
+ {
3165
+ sql: `UPDATE memories
3166
+ SET status = 'archived',
3167
+ outcome = COALESCE(outcome, 'superseded')
3168
+ WHERE id = ?`,
3169
+ args: [supersedesId]
3170
+ },
3171
+ {
3172
+ sql: `UPDATE memories
3173
+ SET importance = MAX(COALESCE(importance, 5), ?),
3174
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3175
+ WHERE id = ?`,
3176
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3177
+ }
3178
+ ], "write");
3179
+ }
3180
+ } catch (err) {
3181
+ process.stderr.write(
3182
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3183
+ `
3184
+ );
3185
+ }
3186
+ }
3187
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3188
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3189
+ if (memoryIds.length === 0) return;
3190
+ const run = () => {
3191
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3192
+ };
3193
+ if (typeof setImmediate === "function") setImmediate(run);
3194
+ else setTimeout(run, 0);
3195
+ }
3196
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3197
+ var init_memory_write_governor = __esm({
3198
+ "src/lib/memory-write-governor.ts"() {
3199
+ "use strict";
3200
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3201
+ "decision",
3202
+ "adr",
3203
+ "behavior",
3204
+ "procedure"
3205
+ ]);
3206
+ NOISE_DROP_PATTERNS = [
3207
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3208
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3209
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3210
+ /^\s*Intercom is a speedup, not delivery/im,
3211
+ /^\s*Context bar reads as USAGE not remaining/im
3212
+ ];
3213
+ SKIP_EMBED_PATTERNS = [
3214
+ /tmux capture-pane\b/i,
3215
+ /docker ps\b/i,
3216
+ /docker images\b/i,
3217
+ /git status\b/i,
3218
+ /grep .*node_modules/i,
3219
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3220
+ ];
3221
+ }
3222
+ });
3223
+
3025
3224
  // src/lib/shard-manager.ts
3026
3225
  var shard_manager_exports = {};
3027
3226
  __export(shard_manager_exports, {
@@ -3186,7 +3385,8 @@ async function ensureShardSchema(client) {
3186
3385
  }
3187
3386
  for (const idx of [
3188
3387
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3189
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3388
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3389
+ "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"
3190
3390
  ]) {
3191
3391
  try {
3192
3392
  await client.execute(idx);
@@ -3380,7 +3580,7 @@ var init_platform_procedures = __esm({
3380
3580
  title: "Chain of command \u2014 who talks to whom",
3381
3581
  domain: "workflow",
3382
3582
  priority: "p0",
3383
- 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."
3583
+ 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."
3384
3584
  },
3385
3585
  {
3386
3586
  title: "Single dispatch path \u2014 create_task only",
@@ -3611,7 +3811,6 @@ __export(store_exports, {
3611
3811
  vectorToBlob: () => vectorToBlob,
3612
3812
  writeMemory: () => writeMemory
3613
3813
  });
3614
- import { createHash } from "crypto";
3615
3814
  function isBusyError2(err) {
3616
3815
  if (err instanceof Error) {
3617
3816
  const msg = err.message.toLowerCase();
@@ -3725,17 +3924,24 @@ async function writeMemory(record) {
3725
3924
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3726
3925
  );
3727
3926
  }
3728
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3729
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3927
+ const governed = governMemoryRecord(record);
3928
+ if (governed.shouldDrop) return;
3929
+ record = governed.record;
3930
+ const contentHash = governed.contentHash;
3931
+ const memoryType = record.memory_type ?? "raw";
3932
+ if (_pendingRecords.some(
3933
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3934
+ )) {
3730
3935
  return;
3731
3936
  }
3732
3937
  try {
3733
- const client = getClient();
3734
- const existing = await client.execute({
3735
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3736
- args: [contentHash, record.agent_id]
3938
+ const existing = await findScopedDuplicate({
3939
+ contentHash,
3940
+ agentId: record.agent_id,
3941
+ projectName: record.project_name,
3942
+ memoryType
3737
3943
  });
3738
- if (existing.rows.length > 0) return;
3944
+ if (existing) return;
3739
3945
  } catch {
3740
3946
  }
3741
3947
  const dbRow = {
@@ -3766,7 +3972,7 @@ async function writeMemory(record) {
3766
3972
  tier: record.tier ?? classifyTier(record),
3767
3973
  supersedes_id: record.supersedes_id ?? null,
3768
3974
  draft: record.draft ? 1 : 0,
3769
- memory_type: record.memory_type ?? "raw",
3975
+ memory_type: memoryType,
3770
3976
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3771
3977
  content_hash: contentHash,
3772
3978
  intent: record.intent ?? null,
@@ -3925,6 +4131,7 @@ async function flushBatch() {
3925
4131
  const globalClient = getClient();
3926
4132
  const globalStmts = batch.map(buildStmt);
3927
4133
  await globalClient.batch(globalStmts, "write");
4134
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3928
4135
  _pendingRecords.splice(0, batch.length);
3929
4136
  try {
3930
4137
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4045,7 +4252,11 @@ async function searchMemories(queryVector, agentId, options) {
4045
4252
  sql += ` AND timestamp >= ?`;
4046
4253
  args.push(options.since);
4047
4254
  }
4048
- if (options?.memoryType) {
4255
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4256
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4257
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4258
+ args.push(...uniqueTypes);
4259
+ } else if (options?.memoryType) {
4049
4260
  sql += ` AND memory_type = ?`;
4050
4261
  args.push(options.memoryType);
4051
4262
  }
@@ -4183,6 +4394,7 @@ var init_store = __esm({
4183
4394
  init_keychain();
4184
4395
  init_config();
4185
4396
  init_state_bus();
4397
+ init_memory_write_governor();
4186
4398
  INIT_MAX_RETRIES = 3;
4187
4399
  INIT_RETRY_DELAY_MS = 1e3;
4188
4400
  _pendingRecords = [];
@@ -5339,6 +5551,17 @@ init_store();
5339
5551
  init_store();
5340
5552
  init_database();
5341
5553
  var RRF_K = 60;
5554
+ function appendMemoryTypeFilter(sql, args, column, options) {
5555
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
5556
+ const uniqueTypes = [...new Set(options.memoryTypes)];
5557
+ sql += ` AND ${column} IN (${uniqueTypes.map(() => "?").join(",")})`;
5558
+ args.push(...uniqueTypes);
5559
+ } else if (options?.memoryType) {
5560
+ sql += ` AND ${column} = ?`;
5561
+ args.push(options.memoryType);
5562
+ }
5563
+ return sql;
5564
+ }
5342
5565
  async function hybridSearch(queryText, agentId, options) {
5343
5566
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
5344
5567
  const config = await loadConfig2();
@@ -5567,6 +5790,7 @@ async function estimateCardinality(agentId, options) {
5567
5790
  sql += ` AND timestamp >= ?`;
5568
5791
  args.push(options.since);
5569
5792
  }
5793
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
5570
5794
  try {
5571
5795
  const result = await client.execute({ sql, args });
5572
5796
  return Number(result.rows[0]?.cnt) || 0;
@@ -5685,10 +5909,7 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
5685
5909
  sql += ` AND m.timestamp >= ?`;
5686
5910
  args.push(options.since);
5687
5911
  }
5688
- if (options?.memoryType) {
5689
- sql += ` AND m.memory_type = ?`;
5690
- args.push(options.memoryType);
5691
- }
5912
+ sql = appendMemoryTypeFilter(sql, args, "m.memory_type", options);
5692
5913
  sql += ` ORDER BY rank LIMIT ?`;
5693
5914
  args.push(limit);
5694
5915
  const result = await client.execute({ sql, args });
@@ -5745,9 +5966,16 @@ async function recentRecords(agentId, options, limit, textFilter) {
5745
5966
  AND timestamp >= ? AND timestamp <= ?
5746
5967
  AND COALESCE(status, 'active') = 'active'
5747
5968
  AND ${options?.includeRaw === false ? "COALESCE(memory_type, 'raw') != 'raw'" : "1 = 1"}
5969
+ ${options?.memoryTypes?.length ? `AND memory_type IN (${options.memoryTypes.map(() => "?").join(",")})` : options?.memoryType ? "AND memory_type = ?" : ""}
5748
5970
  AND COALESCE(confidence, 0.7) >= 0.3
5749
5971
  ORDER BY timestamp DESC LIMIT ?`,
5750
- args: [agentId, windowStart, killedAt, boundarySlots]
5972
+ args: [
5973
+ agentId,
5974
+ windowStart,
5975
+ killedAt,
5976
+ ...options?.memoryTypes?.length ? [...new Set(options.memoryTypes)] : options?.memoryType ? [options.memoryType] : [],
5977
+ boundarySlots
5978
+ ]
5751
5979
  });
5752
5980
  for (const row of boundaryResult.rows) {
5753
5981
  sessionBoundaryMemories.push(rowToMemoryRecord(row));
@@ -5793,10 +6021,7 @@ async function recentRecords(agentId, options, limit, textFilter) {
5793
6021
  sql += ` AND timestamp >= ?`;
5794
6022
  args.push(options.since);
5795
6023
  }
5796
- if (options?.memoryType) {
5797
- sql += ` AND memory_type = ?`;
5798
- args.push(options.memoryType);
5799
- }
6024
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
5800
6025
  if (textFilter) {
5801
6026
  sql += ` AND raw_text LIKE '%' || ? || '%'`;
5802
6027
  args.push(textFilter);