@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
@@ -2299,6 +2299,14 @@ async function ensureSchema() {
2299
2299
  );
2300
2300
  } catch {
2301
2301
  }
2302
+ try {
2303
+ await client.execute(
2304
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2305
+ ON memories(content_hash, agent_id, project_name, memory_type)
2306
+ WHERE content_hash IS NOT NULL`
2307
+ );
2308
+ } catch {
2309
+ }
2302
2310
  await client.executeMultiple(`
2303
2311
  CREATE TABLE IF NOT EXISTS entities (
2304
2312
  id TEXT PRIMARY KEY,
@@ -3518,6 +3526,197 @@ var init_state_bus = __esm({
3518
3526
  }
3519
3527
  });
3520
3528
 
3529
+ // src/lib/memory-write-governor.ts
3530
+ import { createHash } from "crypto";
3531
+ function normalizeMemoryText(text) {
3532
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3533
+ }
3534
+ function classifyMemoryType(input) {
3535
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3536
+ const tool = input.tool_name.toLowerCase();
3537
+ const text = input.raw_text.toLowerCase();
3538
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3539
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3540
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3541
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3542
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3543
+ if (tool === "store_memory" || tool === "manual") return "observation";
3544
+ return "raw";
3545
+ }
3546
+ function shouldDropMemory(text) {
3547
+ const normalized = normalizeMemoryText(text);
3548
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3549
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3550
+ return { drop: true, reason: "known_boilerplate_noise" };
3551
+ }
3552
+ return { drop: false };
3553
+ }
3554
+ function shouldSkipEmbedding(input) {
3555
+ const type = classifyMemoryType(input);
3556
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3557
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3558
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3559
+ return false;
3560
+ }
3561
+ function hashMemoryContent(text) {
3562
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3563
+ }
3564
+ function scopedDedupArgs(input) {
3565
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3566
+ }
3567
+ function governMemoryRecord(record) {
3568
+ const normalized = normalizeMemoryText(record.raw_text);
3569
+ const memoryType = classifyMemoryType({
3570
+ raw_text: normalized,
3571
+ agent_id: record.agent_id,
3572
+ project_name: record.project_name,
3573
+ tool_name: record.tool_name,
3574
+ memory_type: record.memory_type
3575
+ });
3576
+ const drop = shouldDropMemory(normalized);
3577
+ const skipEmbedding = shouldSkipEmbedding({
3578
+ raw_text: normalized,
3579
+ agent_id: record.agent_id,
3580
+ project_name: record.project_name,
3581
+ tool_name: record.tool_name,
3582
+ memory_type: memoryType
3583
+ });
3584
+ return {
3585
+ record: {
3586
+ ...record,
3587
+ raw_text: normalized,
3588
+ memory_type: memoryType,
3589
+ vector: skipEmbedding ? null : record.vector
3590
+ },
3591
+ contentHash: hashMemoryContent(normalized),
3592
+ shouldDrop: drop.drop,
3593
+ dropReason: drop.reason,
3594
+ skipEmbedding,
3595
+ hygiene: {
3596
+ dedup: true,
3597
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3598
+ }
3599
+ };
3600
+ }
3601
+ async function findScopedDuplicate(input) {
3602
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3603
+ const client = getClient2();
3604
+ const args = scopedDedupArgs(input);
3605
+ let sql = `SELECT id FROM memories
3606
+ WHERE content_hash = ?
3607
+ AND agent_id = ?
3608
+ AND project_name = ?
3609
+ AND COALESCE(memory_type, 'raw') = ?
3610
+ AND COALESCE(status, 'active') != 'deleted'`;
3611
+ if (input.excludeId) {
3612
+ sql += " AND id != ?";
3613
+ args.push(input.excludeId);
3614
+ }
3615
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3616
+ const result = await client.execute({ sql, args });
3617
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3618
+ }
3619
+ async function runPostWriteMemoryHygiene(memoryId) {
3620
+ try {
3621
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3622
+ const client = getClient2();
3623
+ const current = await client.execute({
3624
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3625
+ importance, timestamp
3626
+ FROM memories
3627
+ WHERE id = ?
3628
+ LIMIT 1`,
3629
+ args: [memoryId]
3630
+ });
3631
+ const row = current.rows[0];
3632
+ if (!row) return;
3633
+ const memoryType = String(row.memory_type ?? "raw");
3634
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3635
+ const agentId = String(row.agent_id);
3636
+ const projectName = String(row.project_name);
3637
+ if (contentHash) {
3638
+ await client.execute({
3639
+ sql: `UPDATE memories
3640
+ SET status = 'deleted',
3641
+ outcome = COALESCE(outcome, 'superseded')
3642
+ WHERE id != ?
3643
+ AND content_hash = ?
3644
+ AND agent_id = ?
3645
+ AND project_name = ?
3646
+ AND COALESCE(memory_type, 'raw') = ?
3647
+ AND COALESCE(status, 'active') = 'active'`,
3648
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3649
+ });
3650
+ }
3651
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3652
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3653
+ const old = await client.execute({
3654
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3655
+ args: [supersedesId]
3656
+ });
3657
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3658
+ const newImportance = Number(row.importance ?? 0);
3659
+ await client.batch([
3660
+ {
3661
+ sql: `UPDATE memories
3662
+ SET status = 'archived',
3663
+ outcome = COALESCE(outcome, 'superseded')
3664
+ WHERE id = ?`,
3665
+ args: [supersedesId]
3666
+ },
3667
+ {
3668
+ sql: `UPDATE memories
3669
+ SET importance = MAX(COALESCE(importance, 5), ?),
3670
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3671
+ WHERE id = ?`,
3672
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3673
+ }
3674
+ ], "write");
3675
+ }
3676
+ } catch (err) {
3677
+ process.stderr.write(
3678
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3679
+ `
3680
+ );
3681
+ }
3682
+ }
3683
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3684
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3685
+ if (memoryIds.length === 0) return;
3686
+ const run = () => {
3687
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3688
+ };
3689
+ if (typeof setImmediate === "function") setImmediate(run);
3690
+ else setTimeout(run, 0);
3691
+ }
3692
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3693
+ var init_memory_write_governor = __esm({
3694
+ "src/lib/memory-write-governor.ts"() {
3695
+ "use strict";
3696
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3697
+ "decision",
3698
+ "adr",
3699
+ "behavior",
3700
+ "procedure"
3701
+ ]);
3702
+ NOISE_DROP_PATTERNS = [
3703
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3704
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3705
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3706
+ /^\s*Intercom is a speedup, not delivery/im,
3707
+ /^\s*Context bar reads as USAGE not remaining/im
3708
+ ];
3709
+ SKIP_EMBED_PATTERNS = [
3710
+ /tmux capture-pane\b/i,
3711
+ /docker ps\b/i,
3712
+ /docker images\b/i,
3713
+ /git status\b/i,
3714
+ /grep .*node_modules/i,
3715
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3716
+ ];
3717
+ }
3718
+ });
3719
+
3521
3720
  // src/lib/shard-manager.ts
3522
3721
  var shard_manager_exports = {};
3523
3722
  __export(shard_manager_exports, {
@@ -3682,7 +3881,8 @@ async function ensureShardSchema(client) {
3682
3881
  }
3683
3882
  for (const idx of [
3684
3883
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3685
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3884
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3885
+ "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"
3686
3886
  ]) {
3687
3887
  try {
3688
3888
  await client.execute(idx);
@@ -3876,7 +4076,7 @@ var init_platform_procedures = __esm({
3876
4076
  title: "Chain of command \u2014 who talks to whom",
3877
4077
  domain: "workflow",
3878
4078
  priority: "p0",
3879
- 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."
4079
+ 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."
3880
4080
  },
3881
4081
  {
3882
4082
  title: "Single dispatch path \u2014 create_task only",
@@ -4107,7 +4307,6 @@ __export(store_exports, {
4107
4307
  vectorToBlob: () => vectorToBlob,
4108
4308
  writeMemory: () => writeMemory
4109
4309
  });
4110
- import { createHash } from "crypto";
4111
4310
  function isBusyError2(err) {
4112
4311
  if (err instanceof Error) {
4113
4312
  const msg = err.message.toLowerCase();
@@ -4221,17 +4420,24 @@ async function writeMemory(record) {
4221
4420
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4222
4421
  );
4223
4422
  }
4224
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4225
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4423
+ const governed = governMemoryRecord(record);
4424
+ if (governed.shouldDrop) return;
4425
+ record = governed.record;
4426
+ const contentHash = governed.contentHash;
4427
+ const memoryType = record.memory_type ?? "raw";
4428
+ if (_pendingRecords.some(
4429
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4430
+ )) {
4226
4431
  return;
4227
4432
  }
4228
4433
  try {
4229
- const client = getClient();
4230
- const existing = await client.execute({
4231
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4232
- args: [contentHash, record.agent_id]
4434
+ const existing = await findScopedDuplicate({
4435
+ contentHash,
4436
+ agentId: record.agent_id,
4437
+ projectName: record.project_name,
4438
+ memoryType
4233
4439
  });
4234
- if (existing.rows.length > 0) return;
4440
+ if (existing) return;
4235
4441
  } catch {
4236
4442
  }
4237
4443
  const dbRow = {
@@ -4262,7 +4468,7 @@ async function writeMemory(record) {
4262
4468
  tier: record.tier ?? classifyTier(record),
4263
4469
  supersedes_id: record.supersedes_id ?? null,
4264
4470
  draft: record.draft ? 1 : 0,
4265
- memory_type: record.memory_type ?? "raw",
4471
+ memory_type: memoryType,
4266
4472
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4267
4473
  content_hash: contentHash,
4268
4474
  intent: record.intent ?? null,
@@ -4421,6 +4627,7 @@ async function flushBatch() {
4421
4627
  const globalClient = getClient();
4422
4628
  const globalStmts = batch.map(buildStmt);
4423
4629
  await globalClient.batch(globalStmts, "write");
4630
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4424
4631
  _pendingRecords.splice(0, batch.length);
4425
4632
  try {
4426
4633
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4541,7 +4748,11 @@ async function searchMemories(queryVector, agentId, options) {
4541
4748
  sql += ` AND timestamp >= ?`;
4542
4749
  args.push(options.since);
4543
4750
  }
4544
- if (options?.memoryType) {
4751
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4752
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4753
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4754
+ args.push(...uniqueTypes);
4755
+ } else if (options?.memoryType) {
4545
4756
  sql += ` AND memory_type = ?`;
4546
4757
  args.push(options.memoryType);
4547
4758
  }
@@ -4679,6 +4890,7 @@ var init_store = __esm({
4679
4890
  init_keychain();
4680
4891
  init_config();
4681
4892
  init_state_bus();
4893
+ init_memory_write_governor();
4682
4894
  INIT_MAX_RETRIES = 3;
4683
4895
  INIT_RETRY_DELAY_MS = 1e3;
4684
4896
  _pendingRecords = [];
@@ -2299,6 +2299,14 @@ async function ensureSchema() {
2299
2299
  );
2300
2300
  } catch {
2301
2301
  }
2302
+ try {
2303
+ await client.execute(
2304
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2305
+ ON memories(content_hash, agent_id, project_name, memory_type)
2306
+ WHERE content_hash IS NOT NULL`
2307
+ );
2308
+ } catch {
2309
+ }
2302
2310
  await client.executeMultiple(`
2303
2311
  CREATE TABLE IF NOT EXISTS entities (
2304
2312
  id TEXT PRIMARY KEY,
@@ -3557,6 +3565,197 @@ var init_keychain = __esm({
3557
3565
  }
3558
3566
  });
3559
3567
 
3568
+ // src/lib/memory-write-governor.ts
3569
+ import { createHash } from "crypto";
3570
+ function normalizeMemoryText(text) {
3571
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3572
+ }
3573
+ function classifyMemoryType(input) {
3574
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3575
+ const tool = input.tool_name.toLowerCase();
3576
+ const text = input.raw_text.toLowerCase();
3577
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3578
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3579
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3580
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3581
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3582
+ if (tool === "store_memory" || tool === "manual") return "observation";
3583
+ return "raw";
3584
+ }
3585
+ function shouldDropMemory(text) {
3586
+ const normalized = normalizeMemoryText(text);
3587
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3588
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3589
+ return { drop: true, reason: "known_boilerplate_noise" };
3590
+ }
3591
+ return { drop: false };
3592
+ }
3593
+ function shouldSkipEmbedding(input) {
3594
+ const type = classifyMemoryType(input);
3595
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3596
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3597
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3598
+ return false;
3599
+ }
3600
+ function hashMemoryContent(text) {
3601
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3602
+ }
3603
+ function scopedDedupArgs(input) {
3604
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3605
+ }
3606
+ function governMemoryRecord(record) {
3607
+ const normalized = normalizeMemoryText(record.raw_text);
3608
+ const memoryType = classifyMemoryType({
3609
+ raw_text: normalized,
3610
+ agent_id: record.agent_id,
3611
+ project_name: record.project_name,
3612
+ tool_name: record.tool_name,
3613
+ memory_type: record.memory_type
3614
+ });
3615
+ const drop = shouldDropMemory(normalized);
3616
+ const skipEmbedding = shouldSkipEmbedding({
3617
+ raw_text: normalized,
3618
+ agent_id: record.agent_id,
3619
+ project_name: record.project_name,
3620
+ tool_name: record.tool_name,
3621
+ memory_type: memoryType
3622
+ });
3623
+ return {
3624
+ record: {
3625
+ ...record,
3626
+ raw_text: normalized,
3627
+ memory_type: memoryType,
3628
+ vector: skipEmbedding ? null : record.vector
3629
+ },
3630
+ contentHash: hashMemoryContent(normalized),
3631
+ shouldDrop: drop.drop,
3632
+ dropReason: drop.reason,
3633
+ skipEmbedding,
3634
+ hygiene: {
3635
+ dedup: true,
3636
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3637
+ }
3638
+ };
3639
+ }
3640
+ async function findScopedDuplicate(input) {
3641
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3642
+ const client = getClient2();
3643
+ const args = scopedDedupArgs(input);
3644
+ let sql = `SELECT id FROM memories
3645
+ WHERE content_hash = ?
3646
+ AND agent_id = ?
3647
+ AND project_name = ?
3648
+ AND COALESCE(memory_type, 'raw') = ?
3649
+ AND COALESCE(status, 'active') != 'deleted'`;
3650
+ if (input.excludeId) {
3651
+ sql += " AND id != ?";
3652
+ args.push(input.excludeId);
3653
+ }
3654
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3655
+ const result = await client.execute({ sql, args });
3656
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3657
+ }
3658
+ async function runPostWriteMemoryHygiene(memoryId) {
3659
+ try {
3660
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3661
+ const client = getClient2();
3662
+ const current = await client.execute({
3663
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3664
+ importance, timestamp
3665
+ FROM memories
3666
+ WHERE id = ?
3667
+ LIMIT 1`,
3668
+ args: [memoryId]
3669
+ });
3670
+ const row = current.rows[0];
3671
+ if (!row) return;
3672
+ const memoryType = String(row.memory_type ?? "raw");
3673
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3674
+ const agentId = String(row.agent_id);
3675
+ const projectName = String(row.project_name);
3676
+ if (contentHash) {
3677
+ await client.execute({
3678
+ sql: `UPDATE memories
3679
+ SET status = 'deleted',
3680
+ outcome = COALESCE(outcome, 'superseded')
3681
+ WHERE id != ?
3682
+ AND content_hash = ?
3683
+ AND agent_id = ?
3684
+ AND project_name = ?
3685
+ AND COALESCE(memory_type, 'raw') = ?
3686
+ AND COALESCE(status, 'active') = 'active'`,
3687
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3688
+ });
3689
+ }
3690
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3691
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3692
+ const old = await client.execute({
3693
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3694
+ args: [supersedesId]
3695
+ });
3696
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3697
+ const newImportance = Number(row.importance ?? 0);
3698
+ await client.batch([
3699
+ {
3700
+ sql: `UPDATE memories
3701
+ SET status = 'archived',
3702
+ outcome = COALESCE(outcome, 'superseded')
3703
+ WHERE id = ?`,
3704
+ args: [supersedesId]
3705
+ },
3706
+ {
3707
+ sql: `UPDATE memories
3708
+ SET importance = MAX(COALESCE(importance, 5), ?),
3709
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3710
+ WHERE id = ?`,
3711
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3712
+ }
3713
+ ], "write");
3714
+ }
3715
+ } catch (err) {
3716
+ process.stderr.write(
3717
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3718
+ `
3719
+ );
3720
+ }
3721
+ }
3722
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3723
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3724
+ if (memoryIds.length === 0) return;
3725
+ const run = () => {
3726
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3727
+ };
3728
+ if (typeof setImmediate === "function") setImmediate(run);
3729
+ else setTimeout(run, 0);
3730
+ }
3731
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3732
+ var init_memory_write_governor = __esm({
3733
+ "src/lib/memory-write-governor.ts"() {
3734
+ "use strict";
3735
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3736
+ "decision",
3737
+ "adr",
3738
+ "behavior",
3739
+ "procedure"
3740
+ ]);
3741
+ NOISE_DROP_PATTERNS = [
3742
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3743
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3744
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3745
+ /^\s*Intercom is a speedup, not delivery/im,
3746
+ /^\s*Context bar reads as USAGE not remaining/im
3747
+ ];
3748
+ SKIP_EMBED_PATTERNS = [
3749
+ /tmux capture-pane\b/i,
3750
+ /docker ps\b/i,
3751
+ /docker images\b/i,
3752
+ /git status\b/i,
3753
+ /grep .*node_modules/i,
3754
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3755
+ ];
3756
+ }
3757
+ });
3758
+
3560
3759
  // src/lib/shard-manager.ts
3561
3760
  var shard_manager_exports = {};
3562
3761
  __export(shard_manager_exports, {
@@ -3721,7 +3920,8 @@ async function ensureShardSchema(client) {
3721
3920
  }
3722
3921
  for (const idx of [
3723
3922
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3724
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3923
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3924
+ "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"
3725
3925
  ]) {
3726
3926
  try {
3727
3927
  await client.execute(idx);
@@ -3915,7 +4115,7 @@ var init_platform_procedures = __esm({
3915
4115
  title: "Chain of command \u2014 who talks to whom",
3916
4116
  domain: "workflow",
3917
4117
  priority: "p0",
3918
- 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."
4118
+ 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."
3919
4119
  },
3920
4120
  {
3921
4121
  title: "Single dispatch path \u2014 create_task only",
@@ -4146,7 +4346,6 @@ __export(store_exports, {
4146
4346
  vectorToBlob: () => vectorToBlob,
4147
4347
  writeMemory: () => writeMemory
4148
4348
  });
4149
- import { createHash } from "crypto";
4150
4349
  function isBusyError2(err) {
4151
4350
  if (err instanceof Error) {
4152
4351
  const msg = err.message.toLowerCase();
@@ -4260,17 +4459,24 @@ async function writeMemory(record) {
4260
4459
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4261
4460
  );
4262
4461
  }
4263
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4264
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4462
+ const governed = governMemoryRecord(record);
4463
+ if (governed.shouldDrop) return;
4464
+ record = governed.record;
4465
+ const contentHash = governed.contentHash;
4466
+ const memoryType = record.memory_type ?? "raw";
4467
+ if (_pendingRecords.some(
4468
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4469
+ )) {
4265
4470
  return;
4266
4471
  }
4267
4472
  try {
4268
- const client = getClient();
4269
- const existing = await client.execute({
4270
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4271
- args: [contentHash, record.agent_id]
4473
+ const existing = await findScopedDuplicate({
4474
+ contentHash,
4475
+ agentId: record.agent_id,
4476
+ projectName: record.project_name,
4477
+ memoryType
4272
4478
  });
4273
- if (existing.rows.length > 0) return;
4479
+ if (existing) return;
4274
4480
  } catch {
4275
4481
  }
4276
4482
  const dbRow = {
@@ -4301,7 +4507,7 @@ async function writeMemory(record) {
4301
4507
  tier: record.tier ?? classifyTier(record),
4302
4508
  supersedes_id: record.supersedes_id ?? null,
4303
4509
  draft: record.draft ? 1 : 0,
4304
- memory_type: record.memory_type ?? "raw",
4510
+ memory_type: memoryType,
4305
4511
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4306
4512
  content_hash: contentHash,
4307
4513
  intent: record.intent ?? null,
@@ -4460,6 +4666,7 @@ async function flushBatch() {
4460
4666
  const globalClient = getClient();
4461
4667
  const globalStmts = batch.map(buildStmt);
4462
4668
  await globalClient.batch(globalStmts, "write");
4669
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4463
4670
  _pendingRecords.splice(0, batch.length);
4464
4671
  try {
4465
4672
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4580,7 +4787,11 @@ async function searchMemories(queryVector, agentId, options) {
4580
4787
  sql += ` AND timestamp >= ?`;
4581
4788
  args.push(options.since);
4582
4789
  }
4583
- if (options?.memoryType) {
4790
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4791
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4792
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4793
+ args.push(...uniqueTypes);
4794
+ } else if (options?.memoryType) {
4584
4795
  sql += ` AND memory_type = ?`;
4585
4796
  args.push(options.memoryType);
4586
4797
  }
@@ -4718,6 +4929,7 @@ var init_store = __esm({
4718
4929
  init_keychain();
4719
4930
  init_config();
4720
4931
  init_state_bus();
4932
+ init_memory_write_governor();
4721
4933
  INIT_MAX_RETRIES = 3;
4722
4934
  INIT_RETRY_DELAY_MS = 1e3;
4723
4935
  _pendingRecords = [];
@@ -2037,6 +2037,14 @@ async function ensureSchema() {
2037
2037
  );
2038
2038
  } catch {
2039
2039
  }
2040
+ try {
2041
+ await client.execute(
2042
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2043
+ ON memories(content_hash, agent_id, project_name, memory_type)
2044
+ WHERE content_hash IS NOT NULL`
2045
+ );
2046
+ } catch {
2047
+ }
2040
2048
  await client.executeMultiple(`
2041
2049
  CREATE TABLE IF NOT EXISTS entities (
2042
2050
  id TEXT PRIMARY KEY,
@@ -2585,7 +2593,7 @@ var PLATFORM_PROCEDURES = [
2585
2593
  title: "Chain of command \u2014 who talks to whom",
2586
2594
  domain: "workflow",
2587
2595
  priority: "p0",
2588
- 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."
2596
+ 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."
2589
2597
  },
2590
2598
  {
2591
2599
  title: "Single dispatch path \u2014 create_task only",