@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
@@ -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,197 @@ 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 (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3230
+ if (memoryIds.length === 0) return;
3231
+ const run = () => {
3232
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3233
+ };
3234
+ if (typeof setImmediate === "function") setImmediate(run);
3235
+ else setTimeout(run, 0);
3236
+ }
3237
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3238
+ var init_memory_write_governor = __esm({
3239
+ "src/lib/memory-write-governor.ts"() {
3240
+ "use strict";
3241
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3242
+ "decision",
3243
+ "adr",
3244
+ "behavior",
3245
+ "procedure"
3246
+ ]);
3247
+ NOISE_DROP_PATTERNS = [
3248
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3249
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3250
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3251
+ /^\s*Intercom is a speedup, not delivery/im,
3252
+ /^\s*Context bar reads as USAGE not remaining/im
3253
+ ];
3254
+ SKIP_EMBED_PATTERNS = [
3255
+ /tmux capture-pane\b/i,
3256
+ /docker ps\b/i,
3257
+ /docker images\b/i,
3258
+ /git status\b/i,
3259
+ /grep .*node_modules/i,
3260
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3261
+ ];
3262
+ }
3263
+ });
3264
+
3066
3265
  // src/lib/shard-manager.ts
3067
3266
  var shard_manager_exports = {};
3068
3267
  __export(shard_manager_exports, {
@@ -3227,7 +3426,8 @@ async function ensureShardSchema(client) {
3227
3426
  }
3228
3427
  for (const idx of [
3229
3428
  "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"
3429
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3430
+ "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
3431
  ]) {
3232
3432
  try {
3233
3433
  await client.execute(idx);
@@ -3421,7 +3621,7 @@ var init_platform_procedures = __esm({
3421
3621
  title: "Chain of command \u2014 who talks to whom",
3422
3622
  domain: "workflow",
3423
3623
  priority: "p0",
3424
- 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."
3624
+ 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."
3425
3625
  },
3426
3626
  {
3427
3627
  title: "Single dispatch path \u2014 create_task only",
@@ -3652,7 +3852,6 @@ __export(store_exports, {
3652
3852
  vectorToBlob: () => vectorToBlob,
3653
3853
  writeMemory: () => writeMemory
3654
3854
  });
3655
- import { createHash } from "crypto";
3656
3855
  function isBusyError2(err) {
3657
3856
  if (err instanceof Error) {
3658
3857
  const msg = err.message.toLowerCase();
@@ -3766,17 +3965,24 @@ async function writeMemory(record) {
3766
3965
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3767
3966
  );
3768
3967
  }
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)) {
3968
+ const governed = governMemoryRecord(record);
3969
+ if (governed.shouldDrop) return;
3970
+ record = governed.record;
3971
+ const contentHash = governed.contentHash;
3972
+ const memoryType = record.memory_type ?? "raw";
3973
+ if (_pendingRecords.some(
3974
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3975
+ )) {
3771
3976
  return;
3772
3977
  }
3773
3978
  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]
3979
+ const existing = await findScopedDuplicate({
3980
+ contentHash,
3981
+ agentId: record.agent_id,
3982
+ projectName: record.project_name,
3983
+ memoryType
3778
3984
  });
3779
- if (existing.rows.length > 0) return;
3985
+ if (existing) return;
3780
3986
  } catch {
3781
3987
  }
3782
3988
  const dbRow = {
@@ -3807,7 +4013,7 @@ async function writeMemory(record) {
3807
4013
  tier: record.tier ?? classifyTier(record),
3808
4014
  supersedes_id: record.supersedes_id ?? null,
3809
4015
  draft: record.draft ? 1 : 0,
3810
- memory_type: record.memory_type ?? "raw",
4016
+ memory_type: memoryType,
3811
4017
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3812
4018
  content_hash: contentHash,
3813
4019
  intent: record.intent ?? null,
@@ -3966,6 +4172,7 @@ async function flushBatch() {
3966
4172
  const globalClient = getClient();
3967
4173
  const globalStmts = batch.map(buildStmt);
3968
4174
  await globalClient.batch(globalStmts, "write");
4175
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3969
4176
  _pendingRecords.splice(0, batch.length);
3970
4177
  try {
3971
4178
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4086,7 +4293,11 @@ async function searchMemories(queryVector, agentId, options) {
4086
4293
  sql += ` AND timestamp >= ?`;
4087
4294
  args.push(options.since);
4088
4295
  }
4089
- if (options?.memoryType) {
4296
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4297
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4298
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4299
+ args.push(...uniqueTypes);
4300
+ } else if (options?.memoryType) {
4090
4301
  sql += ` AND memory_type = ?`;
4091
4302
  args.push(options.memoryType);
4092
4303
  }
@@ -4224,6 +4435,7 @@ var init_store = __esm({
4224
4435
  init_keychain();
4225
4436
  init_config();
4226
4437
  init_state_bus();
4438
+ init_memory_write_governor();
4227
4439
  INIT_MAX_RETRIES = 3;
4228
4440
  INIT_RETRY_DELAY_MS = 1e3;
4229
4441
  _pendingRecords = [];
@@ -2288,6 +2288,14 @@ async function ensureSchema() {
2288
2288
  );
2289
2289
  } catch {
2290
2290
  }
2291
+ try {
2292
+ await client.execute(
2293
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2294
+ ON memories(content_hash, agent_id, project_name, memory_type)
2295
+ WHERE content_hash IS NOT NULL`
2296
+ );
2297
+ } catch {
2298
+ }
2291
2299
  await client.executeMultiple(`
2292
2300
  CREATE TABLE IF NOT EXISTS entities (
2293
2301
  id TEXT PRIMARY KEY,
@@ -3033,6 +3041,197 @@ var init_state_bus = __esm({
3033
3041
  }
3034
3042
  });
3035
3043
 
3044
+ // src/lib/memory-write-governor.ts
3045
+ import { createHash } from "crypto";
3046
+ function normalizeMemoryText(text) {
3047
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3048
+ }
3049
+ function classifyMemoryType(input) {
3050
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3051
+ const tool = input.tool_name.toLowerCase();
3052
+ const text = input.raw_text.toLowerCase();
3053
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3054
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3055
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3056
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3057
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3058
+ if (tool === "store_memory" || tool === "manual") return "observation";
3059
+ return "raw";
3060
+ }
3061
+ function shouldDropMemory(text) {
3062
+ const normalized = normalizeMemoryText(text);
3063
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3064
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3065
+ return { drop: true, reason: "known_boilerplate_noise" };
3066
+ }
3067
+ return { drop: false };
3068
+ }
3069
+ function shouldSkipEmbedding(input) {
3070
+ const type = classifyMemoryType(input);
3071
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3072
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3073
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3074
+ return false;
3075
+ }
3076
+ function hashMemoryContent(text) {
3077
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3078
+ }
3079
+ function scopedDedupArgs(input) {
3080
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3081
+ }
3082
+ function governMemoryRecord(record) {
3083
+ const normalized = normalizeMemoryText(record.raw_text);
3084
+ const memoryType = classifyMemoryType({
3085
+ raw_text: normalized,
3086
+ agent_id: record.agent_id,
3087
+ project_name: record.project_name,
3088
+ tool_name: record.tool_name,
3089
+ memory_type: record.memory_type
3090
+ });
3091
+ const drop = shouldDropMemory(normalized);
3092
+ const skipEmbedding = shouldSkipEmbedding({
3093
+ raw_text: normalized,
3094
+ agent_id: record.agent_id,
3095
+ project_name: record.project_name,
3096
+ tool_name: record.tool_name,
3097
+ memory_type: memoryType
3098
+ });
3099
+ return {
3100
+ record: {
3101
+ ...record,
3102
+ raw_text: normalized,
3103
+ memory_type: memoryType,
3104
+ vector: skipEmbedding ? null : record.vector
3105
+ },
3106
+ contentHash: hashMemoryContent(normalized),
3107
+ shouldDrop: drop.drop,
3108
+ dropReason: drop.reason,
3109
+ skipEmbedding,
3110
+ hygiene: {
3111
+ dedup: true,
3112
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3113
+ }
3114
+ };
3115
+ }
3116
+ async function findScopedDuplicate(input) {
3117
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3118
+ const client = getClient2();
3119
+ const args = scopedDedupArgs(input);
3120
+ let sql = `SELECT id FROM memories
3121
+ WHERE content_hash = ?
3122
+ AND agent_id = ?
3123
+ AND project_name = ?
3124
+ AND COALESCE(memory_type, 'raw') = ?
3125
+ AND COALESCE(status, 'active') != 'deleted'`;
3126
+ if (input.excludeId) {
3127
+ sql += " AND id != ?";
3128
+ args.push(input.excludeId);
3129
+ }
3130
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3131
+ const result = await client.execute({ sql, args });
3132
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3133
+ }
3134
+ async function runPostWriteMemoryHygiene(memoryId) {
3135
+ try {
3136
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3137
+ const client = getClient2();
3138
+ const current = await client.execute({
3139
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3140
+ importance, timestamp
3141
+ FROM memories
3142
+ WHERE id = ?
3143
+ LIMIT 1`,
3144
+ args: [memoryId]
3145
+ });
3146
+ const row = current.rows[0];
3147
+ if (!row) return;
3148
+ const memoryType = String(row.memory_type ?? "raw");
3149
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3150
+ const agentId = String(row.agent_id);
3151
+ const projectName = String(row.project_name);
3152
+ if (contentHash) {
3153
+ await client.execute({
3154
+ sql: `UPDATE memories
3155
+ SET status = 'deleted',
3156
+ outcome = COALESCE(outcome, 'superseded')
3157
+ WHERE id != ?
3158
+ AND content_hash = ?
3159
+ AND agent_id = ?
3160
+ AND project_name = ?
3161
+ AND COALESCE(memory_type, 'raw') = ?
3162
+ AND COALESCE(status, 'active') = 'active'`,
3163
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3164
+ });
3165
+ }
3166
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3167
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3168
+ const old = await client.execute({
3169
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3170
+ args: [supersedesId]
3171
+ });
3172
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3173
+ const newImportance = Number(row.importance ?? 0);
3174
+ await client.batch([
3175
+ {
3176
+ sql: `UPDATE memories
3177
+ SET status = 'archived',
3178
+ outcome = COALESCE(outcome, 'superseded')
3179
+ WHERE id = ?`,
3180
+ args: [supersedesId]
3181
+ },
3182
+ {
3183
+ sql: `UPDATE memories
3184
+ SET importance = MAX(COALESCE(importance, 5), ?),
3185
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3186
+ WHERE id = ?`,
3187
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3188
+ }
3189
+ ], "write");
3190
+ }
3191
+ } catch (err) {
3192
+ process.stderr.write(
3193
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3194
+ `
3195
+ );
3196
+ }
3197
+ }
3198
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3199
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3200
+ if (memoryIds.length === 0) return;
3201
+ const run = () => {
3202
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3203
+ };
3204
+ if (typeof setImmediate === "function") setImmediate(run);
3205
+ else setTimeout(run, 0);
3206
+ }
3207
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3208
+ var init_memory_write_governor = __esm({
3209
+ "src/lib/memory-write-governor.ts"() {
3210
+ "use strict";
3211
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3212
+ "decision",
3213
+ "adr",
3214
+ "behavior",
3215
+ "procedure"
3216
+ ]);
3217
+ NOISE_DROP_PATTERNS = [
3218
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3219
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3220
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3221
+ /^\s*Intercom is a speedup, not delivery/im,
3222
+ /^\s*Context bar reads as USAGE not remaining/im
3223
+ ];
3224
+ SKIP_EMBED_PATTERNS = [
3225
+ /tmux capture-pane\b/i,
3226
+ /docker ps\b/i,
3227
+ /docker images\b/i,
3228
+ /git status\b/i,
3229
+ /grep .*node_modules/i,
3230
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3231
+ ];
3232
+ }
3233
+ });
3234
+
3036
3235
  // src/lib/shard-manager.ts
3037
3236
  var shard_manager_exports = {};
3038
3237
  __export(shard_manager_exports, {
@@ -3197,7 +3396,8 @@ async function ensureShardSchema(client) {
3197
3396
  }
3198
3397
  for (const idx of [
3199
3398
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3200
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3399
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3400
+ "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
3201
3401
  ]) {
3202
3402
  try {
3203
3403
  await client.execute(idx);
@@ -3391,7 +3591,7 @@ var init_platform_procedures = __esm({
3391
3591
  title: "Chain of command \u2014 who talks to whom",
3392
3592
  domain: "workflow",
3393
3593
  priority: "p0",
3394
- 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."
3594
+ 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."
3395
3595
  },
3396
3596
  {
3397
3597
  title: "Single dispatch path \u2014 create_task only",
@@ -3622,7 +3822,6 @@ __export(store_exports, {
3622
3822
  vectorToBlob: () => vectorToBlob,
3623
3823
  writeMemory: () => writeMemory
3624
3824
  });
3625
- import { createHash } from "crypto";
3626
3825
  function isBusyError2(err) {
3627
3826
  if (err instanceof Error) {
3628
3827
  const msg = err.message.toLowerCase();
@@ -3736,17 +3935,24 @@ async function writeMemory(record) {
3736
3935
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3737
3936
  );
3738
3937
  }
3739
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3740
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3938
+ const governed = governMemoryRecord(record);
3939
+ if (governed.shouldDrop) return;
3940
+ record = governed.record;
3941
+ const contentHash = governed.contentHash;
3942
+ const memoryType = record.memory_type ?? "raw";
3943
+ if (_pendingRecords.some(
3944
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3945
+ )) {
3741
3946
  return;
3742
3947
  }
3743
3948
  try {
3744
- const client = getClient();
3745
- const existing = await client.execute({
3746
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3747
- args: [contentHash, record.agent_id]
3949
+ const existing = await findScopedDuplicate({
3950
+ contentHash,
3951
+ agentId: record.agent_id,
3952
+ projectName: record.project_name,
3953
+ memoryType
3748
3954
  });
3749
- if (existing.rows.length > 0) return;
3955
+ if (existing) return;
3750
3956
  } catch {
3751
3957
  }
3752
3958
  const dbRow = {
@@ -3777,7 +3983,7 @@ async function writeMemory(record) {
3777
3983
  tier: record.tier ?? classifyTier(record),
3778
3984
  supersedes_id: record.supersedes_id ?? null,
3779
3985
  draft: record.draft ? 1 : 0,
3780
- memory_type: record.memory_type ?? "raw",
3986
+ memory_type: memoryType,
3781
3987
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3782
3988
  content_hash: contentHash,
3783
3989
  intent: record.intent ?? null,
@@ -3936,6 +4142,7 @@ async function flushBatch() {
3936
4142
  const globalClient = getClient();
3937
4143
  const globalStmts = batch.map(buildStmt);
3938
4144
  await globalClient.batch(globalStmts, "write");
4145
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3939
4146
  _pendingRecords.splice(0, batch.length);
3940
4147
  try {
3941
4148
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4056,7 +4263,11 @@ async function searchMemories(queryVector, agentId, options) {
4056
4263
  sql += ` AND timestamp >= ?`;
4057
4264
  args.push(options.since);
4058
4265
  }
4059
- if (options?.memoryType) {
4266
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4267
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4268
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4269
+ args.push(...uniqueTypes);
4270
+ } else if (options?.memoryType) {
4060
4271
  sql += ` AND memory_type = ?`;
4061
4272
  args.push(options.memoryType);
4062
4273
  }
@@ -4194,6 +4405,7 @@ var init_store = __esm({
4194
4405
  init_keychain();
4195
4406
  init_config();
4196
4407
  init_state_bus();
4408
+ init_memory_write_governor();
4197
4409
  INIT_MAX_RETRIES = 3;
4198
4410
  INIT_RETRY_DELAY_MS = 1e3;
4199
4411
  _pendingRecords = [];