@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
@@ -2290,6 +2290,14 @@ async function ensureSchema() {
2290
2290
  );
2291
2291
  } catch {
2292
2292
  }
2293
+ try {
2294
+ await client.execute(
2295
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2296
+ ON memories(content_hash, agent_id, project_name, memory_type)
2297
+ WHERE content_hash IS NOT NULL`
2298
+ );
2299
+ } catch {
2300
+ }
2293
2301
  await client.executeMultiple(`
2294
2302
  CREATE TABLE IF NOT EXISTS entities (
2295
2303
  id TEXT PRIMARY KEY,
@@ -3035,6 +3043,197 @@ var init_state_bus = __esm({
3035
3043
  }
3036
3044
  });
3037
3045
 
3046
+ // src/lib/memory-write-governor.ts
3047
+ import { createHash as createHash2 } from "crypto";
3048
+ function normalizeMemoryText(text) {
3049
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3050
+ }
3051
+ function classifyMemoryType(input2) {
3052
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3053
+ const tool = input2.tool_name.toLowerCase();
3054
+ const text = input2.raw_text.toLowerCase();
3055
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3056
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3057
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3058
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3059
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3060
+ if (tool === "store_memory" || tool === "manual") return "observation";
3061
+ return "raw";
3062
+ }
3063
+ function shouldDropMemory(text) {
3064
+ const normalized = normalizeMemoryText(text);
3065
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3066
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3067
+ return { drop: true, reason: "known_boilerplate_noise" };
3068
+ }
3069
+ return { drop: false };
3070
+ }
3071
+ function shouldSkipEmbedding(input2) {
3072
+ const type = classifyMemoryType(input2);
3073
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3074
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3075
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3076
+ return false;
3077
+ }
3078
+ function hashMemoryContent(text) {
3079
+ return createHash2("sha256").update(normalizeMemoryText(text)).digest("hex");
3080
+ }
3081
+ function scopedDedupArgs(input2) {
3082
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3083
+ }
3084
+ function governMemoryRecord(record) {
3085
+ const normalized = normalizeMemoryText(record.raw_text);
3086
+ const memoryType = classifyMemoryType({
3087
+ raw_text: normalized,
3088
+ agent_id: record.agent_id,
3089
+ project_name: record.project_name,
3090
+ tool_name: record.tool_name,
3091
+ memory_type: record.memory_type
3092
+ });
3093
+ const drop = shouldDropMemory(normalized);
3094
+ const skipEmbedding = shouldSkipEmbedding({
3095
+ raw_text: normalized,
3096
+ agent_id: record.agent_id,
3097
+ project_name: record.project_name,
3098
+ tool_name: record.tool_name,
3099
+ memory_type: memoryType
3100
+ });
3101
+ return {
3102
+ record: {
3103
+ ...record,
3104
+ raw_text: normalized,
3105
+ memory_type: memoryType,
3106
+ vector: skipEmbedding ? null : record.vector
3107
+ },
3108
+ contentHash: hashMemoryContent(normalized),
3109
+ shouldDrop: drop.drop,
3110
+ dropReason: drop.reason,
3111
+ skipEmbedding,
3112
+ hygiene: {
3113
+ dedup: true,
3114
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3115
+ }
3116
+ };
3117
+ }
3118
+ async function findScopedDuplicate(input2) {
3119
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3120
+ const client = getClient2();
3121
+ const args = scopedDedupArgs(input2);
3122
+ let sql = `SELECT id FROM memories
3123
+ WHERE content_hash = ?
3124
+ AND agent_id = ?
3125
+ AND project_name = ?
3126
+ AND COALESCE(memory_type, 'raw') = ?
3127
+ AND COALESCE(status, 'active') != 'deleted'`;
3128
+ if (input2.excludeId) {
3129
+ sql += " AND id != ?";
3130
+ args.push(input2.excludeId);
3131
+ }
3132
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3133
+ const result = await client.execute({ sql, args });
3134
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3135
+ }
3136
+ async function runPostWriteMemoryHygiene(memoryId) {
3137
+ try {
3138
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3139
+ const client = getClient2();
3140
+ const current = await client.execute({
3141
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3142
+ importance, timestamp
3143
+ FROM memories
3144
+ WHERE id = ?
3145
+ LIMIT 1`,
3146
+ args: [memoryId]
3147
+ });
3148
+ const row = current.rows[0];
3149
+ if (!row) return;
3150
+ const memoryType = String(row.memory_type ?? "raw");
3151
+ const contentHash2 = row.content_hash ? String(row.content_hash) : null;
3152
+ const agentId = String(row.agent_id);
3153
+ const projectName = String(row.project_name);
3154
+ if (contentHash2) {
3155
+ await client.execute({
3156
+ sql: `UPDATE memories
3157
+ SET status = 'deleted',
3158
+ outcome = COALESCE(outcome, 'superseded')
3159
+ WHERE id != ?
3160
+ AND content_hash = ?
3161
+ AND agent_id = ?
3162
+ AND project_name = ?
3163
+ AND COALESCE(memory_type, 'raw') = ?
3164
+ AND COALESCE(status, 'active') = 'active'`,
3165
+ args: [memoryId, contentHash2, agentId, projectName, memoryType]
3166
+ });
3167
+ }
3168
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3169
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3170
+ const old = await client.execute({
3171
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3172
+ args: [supersedesId]
3173
+ });
3174
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3175
+ const newImportance = Number(row.importance ?? 0);
3176
+ await client.batch([
3177
+ {
3178
+ sql: `UPDATE memories
3179
+ SET status = 'archived',
3180
+ outcome = COALESCE(outcome, 'superseded')
3181
+ WHERE id = ?`,
3182
+ args: [supersedesId]
3183
+ },
3184
+ {
3185
+ sql: `UPDATE memories
3186
+ SET importance = MAX(COALESCE(importance, 5), ?),
3187
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3188
+ WHERE id = ?`,
3189
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3190
+ }
3191
+ ], "write");
3192
+ }
3193
+ } catch (err) {
3194
+ process.stderr.write(
3195
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3196
+ `
3197
+ );
3198
+ }
3199
+ }
3200
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3201
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3202
+ if (memoryIds.length === 0) return;
3203
+ const run = () => {
3204
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3205
+ };
3206
+ if (typeof setImmediate === "function") setImmediate(run);
3207
+ else setTimeout(run, 0);
3208
+ }
3209
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3210
+ var init_memory_write_governor = __esm({
3211
+ "src/lib/memory-write-governor.ts"() {
3212
+ "use strict";
3213
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3214
+ "decision",
3215
+ "adr",
3216
+ "behavior",
3217
+ "procedure"
3218
+ ]);
3219
+ NOISE_DROP_PATTERNS = [
3220
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3221
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3222
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3223
+ /^\s*Intercom is a speedup, not delivery/im,
3224
+ /^\s*Context bar reads as USAGE not remaining/im
3225
+ ];
3226
+ SKIP_EMBED_PATTERNS = [
3227
+ /tmux capture-pane\b/i,
3228
+ /docker ps\b/i,
3229
+ /docker images\b/i,
3230
+ /git status\b/i,
3231
+ /grep .*node_modules/i,
3232
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3233
+ ];
3234
+ }
3235
+ });
3236
+
3038
3237
  // src/lib/shard-manager.ts
3039
3238
  var shard_manager_exports = {};
3040
3239
  __export(shard_manager_exports, {
@@ -3199,7 +3398,8 @@ async function ensureShardSchema(client) {
3199
3398
  }
3200
3399
  for (const idx of [
3201
3400
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3202
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3401
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3402
+ "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"
3203
3403
  ]) {
3204
3404
  try {
3205
3405
  await client.execute(idx);
@@ -3393,7 +3593,7 @@ var init_platform_procedures = __esm({
3393
3593
  title: "Chain of command \u2014 who talks to whom",
3394
3594
  domain: "workflow",
3395
3595
  priority: "p0",
3396
- 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."
3596
+ 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."
3397
3597
  },
3398
3598
  {
3399
3599
  title: "Single dispatch path \u2014 create_task only",
@@ -3624,7 +3824,6 @@ __export(store_exports, {
3624
3824
  vectorToBlob: () => vectorToBlob,
3625
3825
  writeMemory: () => writeMemory
3626
3826
  });
3627
- import { createHash as createHash2 } from "crypto";
3628
3827
  function isBusyError2(err) {
3629
3828
  if (err instanceof Error) {
3630
3829
  const msg = err.message.toLowerCase();
@@ -3738,17 +3937,24 @@ async function writeMemory(record) {
3738
3937
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3739
3938
  );
3740
3939
  }
3741
- const contentHash2 = createHash2("md5").update(record.raw_text).digest("hex");
3742
- if (_pendingRecords.some((r) => r.content_hash === contentHash2 && r.agent_id === record.agent_id)) {
3940
+ const governed = governMemoryRecord(record);
3941
+ if (governed.shouldDrop) return;
3942
+ record = governed.record;
3943
+ const contentHash2 = governed.contentHash;
3944
+ const memoryType = record.memory_type ?? "raw";
3945
+ if (_pendingRecords.some(
3946
+ (r) => r.content_hash === contentHash2 && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3947
+ )) {
3743
3948
  return;
3744
3949
  }
3745
3950
  try {
3746
- const client = getClient();
3747
- const existing = await client.execute({
3748
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3749
- args: [contentHash2, record.agent_id]
3951
+ const existing = await findScopedDuplicate({
3952
+ contentHash: contentHash2,
3953
+ agentId: record.agent_id,
3954
+ projectName: record.project_name,
3955
+ memoryType
3750
3956
  });
3751
- if (existing.rows.length > 0) return;
3957
+ if (existing) return;
3752
3958
  } catch {
3753
3959
  }
3754
3960
  const dbRow = {
@@ -3779,7 +3985,7 @@ async function writeMemory(record) {
3779
3985
  tier: record.tier ?? classifyTier(record),
3780
3986
  supersedes_id: record.supersedes_id ?? null,
3781
3987
  draft: record.draft ? 1 : 0,
3782
- memory_type: record.memory_type ?? "raw",
3988
+ memory_type: memoryType,
3783
3989
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3784
3990
  content_hash: contentHash2,
3785
3991
  intent: record.intent ?? null,
@@ -3938,6 +4144,7 @@ async function flushBatch() {
3938
4144
  const globalClient = getClient();
3939
4145
  const globalStmts = batch.map(buildStmt);
3940
4146
  await globalClient.batch(globalStmts, "write");
4147
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3941
4148
  _pendingRecords.splice(0, batch.length);
3942
4149
  try {
3943
4150
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4058,7 +4265,11 @@ async function searchMemories(queryVector, agentId, options) {
4058
4265
  sql += ` AND timestamp >= ?`;
4059
4266
  args.push(options.since);
4060
4267
  }
4061
- if (options?.memoryType) {
4268
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4269
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4270
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4271
+ args.push(...uniqueTypes);
4272
+ } else if (options?.memoryType) {
4062
4273
  sql += ` AND memory_type = ?`;
4063
4274
  args.push(options.memoryType);
4064
4275
  }
@@ -4196,6 +4407,7 @@ var init_store = __esm({
4196
4407
  init_keychain();
4197
4408
  init_config();
4198
4409
  init_state_bus();
4410
+ init_memory_write_governor();
4199
4411
  INIT_MAX_RETRIES = 3;
4200
4412
  INIT_RETRY_DELAY_MS = 1e3;
4201
4413
  _pendingRecords = [];
@@ -2290,6 +2290,14 @@ async function ensureSchema() {
2290
2290
  );
2291
2291
  } catch {
2292
2292
  }
2293
+ try {
2294
+ await client.execute(
2295
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2296
+ ON memories(content_hash, agent_id, project_name, memory_type)
2297
+ WHERE content_hash IS NOT NULL`
2298
+ );
2299
+ } catch {
2300
+ }
2293
2301
  await client.executeMultiple(`
2294
2302
  CREATE TABLE IF NOT EXISTS entities (
2295
2303
  id TEXT PRIMARY KEY,
@@ -3035,6 +3043,197 @@ var init_state_bus = __esm({
3035
3043
  }
3036
3044
  });
3037
3045
 
3046
+ // src/lib/memory-write-governor.ts
3047
+ import { createHash } from "crypto";
3048
+ function normalizeMemoryText(text) {
3049
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3050
+ }
3051
+ function classifyMemoryType(input2) {
3052
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3053
+ const tool = input2.tool_name.toLowerCase();
3054
+ const text = input2.raw_text.toLowerCase();
3055
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3056
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3057
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3058
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3059
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3060
+ if (tool === "store_memory" || tool === "manual") return "observation";
3061
+ return "raw";
3062
+ }
3063
+ function shouldDropMemory(text) {
3064
+ const normalized = normalizeMemoryText(text);
3065
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3066
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3067
+ return { drop: true, reason: "known_boilerplate_noise" };
3068
+ }
3069
+ return { drop: false };
3070
+ }
3071
+ function shouldSkipEmbedding(input2) {
3072
+ const type = classifyMemoryType(input2);
3073
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3074
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3075
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3076
+ return false;
3077
+ }
3078
+ function hashMemoryContent(text) {
3079
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3080
+ }
3081
+ function scopedDedupArgs(input2) {
3082
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3083
+ }
3084
+ function governMemoryRecord(record) {
3085
+ const normalized = normalizeMemoryText(record.raw_text);
3086
+ const memoryType = classifyMemoryType({
3087
+ raw_text: normalized,
3088
+ agent_id: record.agent_id,
3089
+ project_name: record.project_name,
3090
+ tool_name: record.tool_name,
3091
+ memory_type: record.memory_type
3092
+ });
3093
+ const drop = shouldDropMemory(normalized);
3094
+ const skipEmbedding = shouldSkipEmbedding({
3095
+ raw_text: normalized,
3096
+ agent_id: record.agent_id,
3097
+ project_name: record.project_name,
3098
+ tool_name: record.tool_name,
3099
+ memory_type: memoryType
3100
+ });
3101
+ return {
3102
+ record: {
3103
+ ...record,
3104
+ raw_text: normalized,
3105
+ memory_type: memoryType,
3106
+ vector: skipEmbedding ? null : record.vector
3107
+ },
3108
+ contentHash: hashMemoryContent(normalized),
3109
+ shouldDrop: drop.drop,
3110
+ dropReason: drop.reason,
3111
+ skipEmbedding,
3112
+ hygiene: {
3113
+ dedup: true,
3114
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3115
+ }
3116
+ };
3117
+ }
3118
+ async function findScopedDuplicate(input2) {
3119
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3120
+ const client = getClient2();
3121
+ const args = scopedDedupArgs(input2);
3122
+ let sql = `SELECT id FROM memories
3123
+ WHERE content_hash = ?
3124
+ AND agent_id = ?
3125
+ AND project_name = ?
3126
+ AND COALESCE(memory_type, 'raw') = ?
3127
+ AND COALESCE(status, 'active') != 'deleted'`;
3128
+ if (input2.excludeId) {
3129
+ sql += " AND id != ?";
3130
+ args.push(input2.excludeId);
3131
+ }
3132
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3133
+ const result = await client.execute({ sql, args });
3134
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3135
+ }
3136
+ async function runPostWriteMemoryHygiene(memoryId) {
3137
+ try {
3138
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3139
+ const client = getClient2();
3140
+ const current = await client.execute({
3141
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3142
+ importance, timestamp
3143
+ FROM memories
3144
+ WHERE id = ?
3145
+ LIMIT 1`,
3146
+ args: [memoryId]
3147
+ });
3148
+ const row = current.rows[0];
3149
+ if (!row) return;
3150
+ const memoryType = String(row.memory_type ?? "raw");
3151
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3152
+ const agentId = String(row.agent_id);
3153
+ const projectName = String(row.project_name);
3154
+ if (contentHash) {
3155
+ await client.execute({
3156
+ sql: `UPDATE memories
3157
+ SET status = 'deleted',
3158
+ outcome = COALESCE(outcome, 'superseded')
3159
+ WHERE id != ?
3160
+ AND content_hash = ?
3161
+ AND agent_id = ?
3162
+ AND project_name = ?
3163
+ AND COALESCE(memory_type, 'raw') = ?
3164
+ AND COALESCE(status, 'active') = 'active'`,
3165
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3166
+ });
3167
+ }
3168
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3169
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3170
+ const old = await client.execute({
3171
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3172
+ args: [supersedesId]
3173
+ });
3174
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3175
+ const newImportance = Number(row.importance ?? 0);
3176
+ await client.batch([
3177
+ {
3178
+ sql: `UPDATE memories
3179
+ SET status = 'archived',
3180
+ outcome = COALESCE(outcome, 'superseded')
3181
+ WHERE id = ?`,
3182
+ args: [supersedesId]
3183
+ },
3184
+ {
3185
+ sql: `UPDATE memories
3186
+ SET importance = MAX(COALESCE(importance, 5), ?),
3187
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3188
+ WHERE id = ?`,
3189
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3190
+ }
3191
+ ], "write");
3192
+ }
3193
+ } catch (err) {
3194
+ process.stderr.write(
3195
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3196
+ `
3197
+ );
3198
+ }
3199
+ }
3200
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3201
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3202
+ if (memoryIds.length === 0) return;
3203
+ const run = () => {
3204
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3205
+ };
3206
+ if (typeof setImmediate === "function") setImmediate(run);
3207
+ else setTimeout(run, 0);
3208
+ }
3209
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3210
+ var init_memory_write_governor = __esm({
3211
+ "src/lib/memory-write-governor.ts"() {
3212
+ "use strict";
3213
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3214
+ "decision",
3215
+ "adr",
3216
+ "behavior",
3217
+ "procedure"
3218
+ ]);
3219
+ NOISE_DROP_PATTERNS = [
3220
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3221
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3222
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3223
+ /^\s*Intercom is a speedup, not delivery/im,
3224
+ /^\s*Context bar reads as USAGE not remaining/im
3225
+ ];
3226
+ SKIP_EMBED_PATTERNS = [
3227
+ /tmux capture-pane\b/i,
3228
+ /docker ps\b/i,
3229
+ /docker images\b/i,
3230
+ /git status\b/i,
3231
+ /grep .*node_modules/i,
3232
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3233
+ ];
3234
+ }
3235
+ });
3236
+
3038
3237
  // src/lib/shard-manager.ts
3039
3238
  var shard_manager_exports = {};
3040
3239
  __export(shard_manager_exports, {
@@ -3199,7 +3398,8 @@ async function ensureShardSchema(client) {
3199
3398
  }
3200
3399
  for (const idx of [
3201
3400
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3202
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3401
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3402
+ "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"
3203
3403
  ]) {
3204
3404
  try {
3205
3405
  await client.execute(idx);
@@ -3393,7 +3593,7 @@ var init_platform_procedures = __esm({
3393
3593
  title: "Chain of command \u2014 who talks to whom",
3394
3594
  domain: "workflow",
3395
3595
  priority: "p0",
3396
- 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."
3596
+ 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."
3397
3597
  },
3398
3598
  {
3399
3599
  title: "Single dispatch path \u2014 create_task only",
@@ -3624,7 +3824,6 @@ __export(store_exports, {
3624
3824
  vectorToBlob: () => vectorToBlob,
3625
3825
  writeMemory: () => writeMemory
3626
3826
  });
3627
- import { createHash } from "crypto";
3628
3827
  function isBusyError2(err) {
3629
3828
  if (err instanceof Error) {
3630
3829
  const msg = err.message.toLowerCase();
@@ -3738,17 +3937,24 @@ async function writeMemory(record) {
3738
3937
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3739
3938
  );
3740
3939
  }
3741
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3742
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3940
+ const governed = governMemoryRecord(record);
3941
+ if (governed.shouldDrop) return;
3942
+ record = governed.record;
3943
+ const contentHash = governed.contentHash;
3944
+ const memoryType = record.memory_type ?? "raw";
3945
+ if (_pendingRecords.some(
3946
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3947
+ )) {
3743
3948
  return;
3744
3949
  }
3745
3950
  try {
3746
- const client = getClient();
3747
- const existing = await client.execute({
3748
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3749
- args: [contentHash, record.agent_id]
3951
+ const existing = await findScopedDuplicate({
3952
+ contentHash,
3953
+ agentId: record.agent_id,
3954
+ projectName: record.project_name,
3955
+ memoryType
3750
3956
  });
3751
- if (existing.rows.length > 0) return;
3957
+ if (existing) return;
3752
3958
  } catch {
3753
3959
  }
3754
3960
  const dbRow = {
@@ -3779,7 +3985,7 @@ async function writeMemory(record) {
3779
3985
  tier: record.tier ?? classifyTier(record),
3780
3986
  supersedes_id: record.supersedes_id ?? null,
3781
3987
  draft: record.draft ? 1 : 0,
3782
- memory_type: record.memory_type ?? "raw",
3988
+ memory_type: memoryType,
3783
3989
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3784
3990
  content_hash: contentHash,
3785
3991
  intent: record.intent ?? null,
@@ -3938,6 +4144,7 @@ async function flushBatch() {
3938
4144
  const globalClient = getClient();
3939
4145
  const globalStmts = batch.map(buildStmt);
3940
4146
  await globalClient.batch(globalStmts, "write");
4147
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3941
4148
  _pendingRecords.splice(0, batch.length);
3942
4149
  try {
3943
4150
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4058,7 +4265,11 @@ async function searchMemories(queryVector, agentId, options) {
4058
4265
  sql += ` AND timestamp >= ?`;
4059
4266
  args.push(options.since);
4060
4267
  }
4061
- if (options?.memoryType) {
4268
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4269
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4270
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4271
+ args.push(...uniqueTypes);
4272
+ } else if (options?.memoryType) {
4062
4273
  sql += ` AND memory_type = ?`;
4063
4274
  args.push(options.memoryType);
4064
4275
  }
@@ -4196,6 +4407,7 @@ var init_store = __esm({
4196
4407
  init_keychain();
4197
4408
  init_config();
4198
4409
  init_state_bus();
4410
+ init_memory_write_governor();
4199
4411
  INIT_MAX_RETRIES = 3;
4200
4412
  INIT_RETRY_DELAY_MS = 1e3;
4201
4413
  _pendingRecords = [];