@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
@@ -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 = [];
@@ -4277,6 +4489,17 @@ init_database();
4277
4489
  // src/lib/hybrid-search.ts
4278
4490
  init_store();
4279
4491
  init_database();
4492
+ function appendMemoryTypeFilter(sql, args, column, options) {
4493
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4494
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4495
+ sql += ` AND ${column} IN (${uniqueTypes.map(() => "?").join(",")})`;
4496
+ args.push(...uniqueTypes);
4497
+ } else if (options?.memoryType) {
4498
+ sql += ` AND ${column} = ?`;
4499
+ args.push(options.memoryType);
4500
+ }
4501
+ return sql;
4502
+ }
4280
4503
  async function lightweightSearch(queryText, agentId, options) {
4281
4504
  const client = getClient();
4282
4505
  const limit = options?.limit ?? 5;
@@ -4344,10 +4567,7 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
4344
4567
  sql += ` AND m.timestamp >= ?`;
4345
4568
  args.push(options.since);
4346
4569
  }
4347
- if (options?.memoryType) {
4348
- sql += ` AND m.memory_type = ?`;
4349
- args.push(options.memoryType);
4350
- }
4570
+ sql = appendMemoryTypeFilter(sql, args, "m.memory_type", options);
4351
4571
  sql += ` ORDER BY rank LIMIT ?`;
4352
4572
  args.push(limit);
4353
4573
  const result = await client.execute({ sql, args });
@@ -4404,9 +4624,16 @@ async function recentRecords(agentId, options, limit, textFilter) {
4404
4624
  AND timestamp >= ? AND timestamp <= ?
4405
4625
  AND COALESCE(status, 'active') = 'active'
4406
4626
  AND ${options?.includeRaw === false ? "COALESCE(memory_type, 'raw') != 'raw'" : "1 = 1"}
4627
+ ${options?.memoryTypes?.length ? `AND memory_type IN (${options.memoryTypes.map(() => "?").join(",")})` : options?.memoryType ? "AND memory_type = ?" : ""}
4407
4628
  AND COALESCE(confidence, 0.7) >= 0.3
4408
4629
  ORDER BY timestamp DESC LIMIT ?`,
4409
- args: [agentId, windowStart, killedAt, boundarySlots]
4630
+ args: [
4631
+ agentId,
4632
+ windowStart,
4633
+ killedAt,
4634
+ ...options?.memoryTypes?.length ? [...new Set(options.memoryTypes)] : options?.memoryType ? [options.memoryType] : [],
4635
+ boundarySlots
4636
+ ]
4410
4637
  });
4411
4638
  for (const row of boundaryResult.rows) {
4412
4639
  sessionBoundaryMemories.push(rowToMemoryRecord(row));
@@ -4452,10 +4679,7 @@ async function recentRecords(agentId, options, limit, textFilter) {
4452
4679
  sql += ` AND timestamp >= ?`;
4453
4680
  args.push(options.since);
4454
4681
  }
4455
- if (options?.memoryType) {
4456
- sql += ` AND memory_type = ?`;
4457
- args.push(options.memoryType);
4458
- }
4682
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
4459
4683
  if (textFilter) {
4460
4684
  sql += ` AND raw_text LIKE '%' || ? || '%'`;
4461
4685
  args.push(textFilter);
@@ -2933,6 +2933,14 @@ async function ensureSchema() {
2933
2933
  );
2934
2934
  } catch {
2935
2935
  }
2936
+ try {
2937
+ await client.execute(
2938
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2939
+ ON memories(content_hash, agent_id, project_name, memory_type)
2940
+ WHERE content_hash IS NOT NULL`
2941
+ );
2942
+ } catch {
2943
+ }
2936
2944
  await client.executeMultiple(`
2937
2945
  CREATE TABLE IF NOT EXISTS entities (
2938
2946
  id TEXT PRIMARY KEY,
@@ -3691,6 +3699,197 @@ var init_keychain = __esm({
3691
3699
  }
3692
3700
  });
3693
3701
 
3702
+ // src/lib/memory-write-governor.ts
3703
+ import { createHash } from "crypto";
3704
+ function normalizeMemoryText(text) {
3705
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3706
+ }
3707
+ function classifyMemoryType(input) {
3708
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3709
+ const tool = input.tool_name.toLowerCase();
3710
+ const text = input.raw_text.toLowerCase();
3711
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3712
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3713
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3714
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3715
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3716
+ if (tool === "store_memory" || tool === "manual") return "observation";
3717
+ return "raw";
3718
+ }
3719
+ function shouldDropMemory(text) {
3720
+ const normalized = normalizeMemoryText(text);
3721
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3722
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3723
+ return { drop: true, reason: "known_boilerplate_noise" };
3724
+ }
3725
+ return { drop: false };
3726
+ }
3727
+ function shouldSkipEmbedding(input) {
3728
+ const type = classifyMemoryType(input);
3729
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3730
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3731
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3732
+ return false;
3733
+ }
3734
+ function hashMemoryContent(text) {
3735
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3736
+ }
3737
+ function scopedDedupArgs(input) {
3738
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3739
+ }
3740
+ function governMemoryRecord(record) {
3741
+ const normalized = normalizeMemoryText(record.raw_text);
3742
+ const memoryType = classifyMemoryType({
3743
+ raw_text: normalized,
3744
+ agent_id: record.agent_id,
3745
+ project_name: record.project_name,
3746
+ tool_name: record.tool_name,
3747
+ memory_type: record.memory_type
3748
+ });
3749
+ const drop = shouldDropMemory(normalized);
3750
+ const skipEmbedding = shouldSkipEmbedding({
3751
+ raw_text: normalized,
3752
+ agent_id: record.agent_id,
3753
+ project_name: record.project_name,
3754
+ tool_name: record.tool_name,
3755
+ memory_type: memoryType
3756
+ });
3757
+ return {
3758
+ record: {
3759
+ ...record,
3760
+ raw_text: normalized,
3761
+ memory_type: memoryType,
3762
+ vector: skipEmbedding ? null : record.vector
3763
+ },
3764
+ contentHash: hashMemoryContent(normalized),
3765
+ shouldDrop: drop.drop,
3766
+ dropReason: drop.reason,
3767
+ skipEmbedding,
3768
+ hygiene: {
3769
+ dedup: true,
3770
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3771
+ }
3772
+ };
3773
+ }
3774
+ async function findScopedDuplicate(input) {
3775
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3776
+ const client = getClient2();
3777
+ const args = scopedDedupArgs(input);
3778
+ let sql = `SELECT id FROM memories
3779
+ WHERE content_hash = ?
3780
+ AND agent_id = ?
3781
+ AND project_name = ?
3782
+ AND COALESCE(memory_type, 'raw') = ?
3783
+ AND COALESCE(status, 'active') != 'deleted'`;
3784
+ if (input.excludeId) {
3785
+ sql += " AND id != ?";
3786
+ args.push(input.excludeId);
3787
+ }
3788
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3789
+ const result = await client.execute({ sql, args });
3790
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3791
+ }
3792
+ async function runPostWriteMemoryHygiene(memoryId) {
3793
+ try {
3794
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3795
+ const client = getClient2();
3796
+ const current = await client.execute({
3797
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3798
+ importance, timestamp
3799
+ FROM memories
3800
+ WHERE id = ?
3801
+ LIMIT 1`,
3802
+ args: [memoryId]
3803
+ });
3804
+ const row = current.rows[0];
3805
+ if (!row) return;
3806
+ const memoryType = String(row.memory_type ?? "raw");
3807
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3808
+ const agentId = String(row.agent_id);
3809
+ const projectName = String(row.project_name);
3810
+ if (contentHash) {
3811
+ await client.execute({
3812
+ sql: `UPDATE memories
3813
+ SET status = 'deleted',
3814
+ outcome = COALESCE(outcome, 'superseded')
3815
+ WHERE id != ?
3816
+ AND content_hash = ?
3817
+ AND agent_id = ?
3818
+ AND project_name = ?
3819
+ AND COALESCE(memory_type, 'raw') = ?
3820
+ AND COALESCE(status, 'active') = 'active'`,
3821
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3822
+ });
3823
+ }
3824
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3825
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3826
+ const old = await client.execute({
3827
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3828
+ args: [supersedesId]
3829
+ });
3830
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3831
+ const newImportance = Number(row.importance ?? 0);
3832
+ await client.batch([
3833
+ {
3834
+ sql: `UPDATE memories
3835
+ SET status = 'archived',
3836
+ outcome = COALESCE(outcome, 'superseded')
3837
+ WHERE id = ?`,
3838
+ args: [supersedesId]
3839
+ },
3840
+ {
3841
+ sql: `UPDATE memories
3842
+ SET importance = MAX(COALESCE(importance, 5), ?),
3843
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3844
+ WHERE id = ?`,
3845
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3846
+ }
3847
+ ], "write");
3848
+ }
3849
+ } catch (err) {
3850
+ process.stderr.write(
3851
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3852
+ `
3853
+ );
3854
+ }
3855
+ }
3856
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3857
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3858
+ if (memoryIds.length === 0) return;
3859
+ const run = () => {
3860
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3861
+ };
3862
+ if (typeof setImmediate === "function") setImmediate(run);
3863
+ else setTimeout(run, 0);
3864
+ }
3865
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3866
+ var init_memory_write_governor = __esm({
3867
+ "src/lib/memory-write-governor.ts"() {
3868
+ "use strict";
3869
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3870
+ "decision",
3871
+ "adr",
3872
+ "behavior",
3873
+ "procedure"
3874
+ ]);
3875
+ NOISE_DROP_PATTERNS = [
3876
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3877
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3878
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3879
+ /^\s*Intercom is a speedup, not delivery/im,
3880
+ /^\s*Context bar reads as USAGE not remaining/im
3881
+ ];
3882
+ SKIP_EMBED_PATTERNS = [
3883
+ /tmux capture-pane\b/i,
3884
+ /docker ps\b/i,
3885
+ /docker images\b/i,
3886
+ /git status\b/i,
3887
+ /grep .*node_modules/i,
3888
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3889
+ ];
3890
+ }
3891
+ });
3892
+
3694
3893
  // src/lib/shard-manager.ts
3695
3894
  var shard_manager_exports = {};
3696
3895
  __export(shard_manager_exports, {
@@ -3855,7 +4054,8 @@ async function ensureShardSchema(client) {
3855
4054
  }
3856
4055
  for (const idx of [
3857
4056
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3858
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
4057
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
4058
+ "CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash ON memories(content_hash, agent_id, project_name, memory_type) WHERE content_hash IS NOT NULL"
3859
4059
  ]) {
3860
4060
  try {
3861
4061
  await client.execute(idx);
@@ -4049,7 +4249,7 @@ var init_platform_procedures = __esm({
4049
4249
  title: "Chain of command \u2014 who talks to whom",
4050
4250
  domain: "workflow",
4051
4251
  priority: "p0",
4052
- 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."
4252
+ 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."
4053
4253
  },
4054
4254
  {
4055
4255
  title: "Single dispatch path \u2014 create_task only",
@@ -4280,7 +4480,6 @@ __export(store_exports, {
4280
4480
  vectorToBlob: () => vectorToBlob,
4281
4481
  writeMemory: () => writeMemory
4282
4482
  });
4283
- import { createHash } from "crypto";
4284
4483
  function isBusyError2(err) {
4285
4484
  if (err instanceof Error) {
4286
4485
  const msg = err.message.toLowerCase();
@@ -4394,17 +4593,24 @@ async function writeMemory(record) {
4394
4593
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4395
4594
  );
4396
4595
  }
4397
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4398
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4596
+ const governed = governMemoryRecord(record);
4597
+ if (governed.shouldDrop) return;
4598
+ record = governed.record;
4599
+ const contentHash = governed.contentHash;
4600
+ const memoryType = record.memory_type ?? "raw";
4601
+ if (_pendingRecords.some(
4602
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4603
+ )) {
4399
4604
  return;
4400
4605
  }
4401
4606
  try {
4402
- const client = getClient();
4403
- const existing = await client.execute({
4404
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4405
- args: [contentHash, record.agent_id]
4607
+ const existing = await findScopedDuplicate({
4608
+ contentHash,
4609
+ agentId: record.agent_id,
4610
+ projectName: record.project_name,
4611
+ memoryType
4406
4612
  });
4407
- if (existing.rows.length > 0) return;
4613
+ if (existing) return;
4408
4614
  } catch {
4409
4615
  }
4410
4616
  const dbRow = {
@@ -4435,7 +4641,7 @@ async function writeMemory(record) {
4435
4641
  tier: record.tier ?? classifyTier(record),
4436
4642
  supersedes_id: record.supersedes_id ?? null,
4437
4643
  draft: record.draft ? 1 : 0,
4438
- memory_type: record.memory_type ?? "raw",
4644
+ memory_type: memoryType,
4439
4645
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4440
4646
  content_hash: contentHash,
4441
4647
  intent: record.intent ?? null,
@@ -4594,6 +4800,7 @@ async function flushBatch() {
4594
4800
  const globalClient = getClient();
4595
4801
  const globalStmts = batch.map(buildStmt);
4596
4802
  await globalClient.batch(globalStmts, "write");
4803
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4597
4804
  _pendingRecords.splice(0, batch.length);
4598
4805
  try {
4599
4806
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4714,7 +4921,11 @@ async function searchMemories(queryVector, agentId, options) {
4714
4921
  sql += ` AND timestamp >= ?`;
4715
4922
  args.push(options.since);
4716
4923
  }
4717
- if (options?.memoryType) {
4924
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4925
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4926
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4927
+ args.push(...uniqueTypes);
4928
+ } else if (options?.memoryType) {
4718
4929
  sql += ` AND memory_type = ?`;
4719
4930
  args.push(options.memoryType);
4720
4931
  }
@@ -4852,6 +5063,7 @@ var init_store = __esm({
4852
5063
  init_keychain();
4853
5064
  init_config();
4854
5065
  init_state_bus();
5066
+ init_memory_write_governor();
4855
5067
  INIT_MAX_RETRIES = 3;
4856
5068
  INIT_RETRY_DELAY_MS = 1e3;
4857
5069
  _pendingRecords = [];
@@ -11377,7 +11589,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
11377
11589
  }
11378
11590
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
11379
11591
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
11380
- envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
11592
+ let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
11593
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
11594
+ ccModel += "[1m]";
11595
+ }
11596
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
11381
11597
  }
11382
11598
  }
11383
11599
  let spawnCommand;