@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
@@ -2611,6 +2611,14 @@ async function ensureSchema() {
2611
2611
  );
2612
2612
  } catch {
2613
2613
  }
2614
+ try {
2615
+ await client.execute(
2616
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2617
+ ON memories(content_hash, agent_id, project_name, memory_type)
2618
+ WHERE content_hash IS NOT NULL`
2619
+ );
2620
+ } catch {
2621
+ }
2614
2622
  await client.executeMultiple(`
2615
2623
  CREATE TABLE IF NOT EXISTS entities (
2616
2624
  id TEXT PRIMARY KEY,
@@ -3481,6 +3489,197 @@ var init_state_bus = __esm({
3481
3489
  }
3482
3490
  });
3483
3491
 
3492
+ // src/lib/memory-write-governor.ts
3493
+ import { createHash } from "crypto";
3494
+ function normalizeMemoryText(text) {
3495
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3496
+ }
3497
+ function classifyMemoryType(input2) {
3498
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3499
+ const tool = input2.tool_name.toLowerCase();
3500
+ const text = input2.raw_text.toLowerCase();
3501
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3502
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3503
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3504
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3505
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3506
+ if (tool === "store_memory" || tool === "manual") return "observation";
3507
+ return "raw";
3508
+ }
3509
+ function shouldDropMemory(text) {
3510
+ const normalized = normalizeMemoryText(text);
3511
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3512
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3513
+ return { drop: true, reason: "known_boilerplate_noise" };
3514
+ }
3515
+ return { drop: false };
3516
+ }
3517
+ function shouldSkipEmbedding(input2) {
3518
+ const type = classifyMemoryType(input2);
3519
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3520
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3521
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3522
+ return false;
3523
+ }
3524
+ function hashMemoryContent(text) {
3525
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3526
+ }
3527
+ function scopedDedupArgs(input2) {
3528
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3529
+ }
3530
+ function governMemoryRecord(record) {
3531
+ const normalized = normalizeMemoryText(record.raw_text);
3532
+ const memoryType = classifyMemoryType({
3533
+ raw_text: normalized,
3534
+ agent_id: record.agent_id,
3535
+ project_name: record.project_name,
3536
+ tool_name: record.tool_name,
3537
+ memory_type: record.memory_type
3538
+ });
3539
+ const drop = shouldDropMemory(normalized);
3540
+ const skipEmbedding = shouldSkipEmbedding({
3541
+ raw_text: normalized,
3542
+ agent_id: record.agent_id,
3543
+ project_name: record.project_name,
3544
+ tool_name: record.tool_name,
3545
+ memory_type: memoryType
3546
+ });
3547
+ return {
3548
+ record: {
3549
+ ...record,
3550
+ raw_text: normalized,
3551
+ memory_type: memoryType,
3552
+ vector: skipEmbedding ? null : record.vector
3553
+ },
3554
+ contentHash: hashMemoryContent(normalized),
3555
+ shouldDrop: drop.drop,
3556
+ dropReason: drop.reason,
3557
+ skipEmbedding,
3558
+ hygiene: {
3559
+ dedup: true,
3560
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3561
+ }
3562
+ };
3563
+ }
3564
+ async function findScopedDuplicate(input2) {
3565
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3566
+ const client = getClient2();
3567
+ const args = scopedDedupArgs(input2);
3568
+ let sql = `SELECT id FROM memories
3569
+ WHERE content_hash = ?
3570
+ AND agent_id = ?
3571
+ AND project_name = ?
3572
+ AND COALESCE(memory_type, 'raw') = ?
3573
+ AND COALESCE(status, 'active') != 'deleted'`;
3574
+ if (input2.excludeId) {
3575
+ sql += " AND id != ?";
3576
+ args.push(input2.excludeId);
3577
+ }
3578
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3579
+ const result = await client.execute({ sql, args });
3580
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3581
+ }
3582
+ async function runPostWriteMemoryHygiene(memoryId) {
3583
+ try {
3584
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3585
+ const client = getClient2();
3586
+ const current = await client.execute({
3587
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3588
+ importance, timestamp
3589
+ FROM memories
3590
+ WHERE id = ?
3591
+ LIMIT 1`,
3592
+ args: [memoryId]
3593
+ });
3594
+ const row = current.rows[0];
3595
+ if (!row) return;
3596
+ const memoryType = String(row.memory_type ?? "raw");
3597
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3598
+ const agentId = String(row.agent_id);
3599
+ const projectName = String(row.project_name);
3600
+ if (contentHash) {
3601
+ await client.execute({
3602
+ sql: `UPDATE memories
3603
+ SET status = 'deleted',
3604
+ outcome = COALESCE(outcome, 'superseded')
3605
+ WHERE id != ?
3606
+ AND content_hash = ?
3607
+ AND agent_id = ?
3608
+ AND project_name = ?
3609
+ AND COALESCE(memory_type, 'raw') = ?
3610
+ AND COALESCE(status, 'active') = 'active'`,
3611
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3612
+ });
3613
+ }
3614
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3615
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3616
+ const old = await client.execute({
3617
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3618
+ args: [supersedesId]
3619
+ });
3620
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3621
+ const newImportance = Number(row.importance ?? 0);
3622
+ await client.batch([
3623
+ {
3624
+ sql: `UPDATE memories
3625
+ SET status = 'archived',
3626
+ outcome = COALESCE(outcome, 'superseded')
3627
+ WHERE id = ?`,
3628
+ args: [supersedesId]
3629
+ },
3630
+ {
3631
+ sql: `UPDATE memories
3632
+ SET importance = MAX(COALESCE(importance, 5), ?),
3633
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3634
+ WHERE id = ?`,
3635
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3636
+ }
3637
+ ], "write");
3638
+ }
3639
+ } catch (err) {
3640
+ process.stderr.write(
3641
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3642
+ `
3643
+ );
3644
+ }
3645
+ }
3646
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3647
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3648
+ if (memoryIds.length === 0) return;
3649
+ const run = () => {
3650
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3651
+ };
3652
+ if (typeof setImmediate === "function") setImmediate(run);
3653
+ else setTimeout(run, 0);
3654
+ }
3655
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3656
+ var init_memory_write_governor = __esm({
3657
+ "src/lib/memory-write-governor.ts"() {
3658
+ "use strict";
3659
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3660
+ "decision",
3661
+ "adr",
3662
+ "behavior",
3663
+ "procedure"
3664
+ ]);
3665
+ NOISE_DROP_PATTERNS = [
3666
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3667
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3668
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3669
+ /^\s*Intercom is a speedup, not delivery/im,
3670
+ /^\s*Context bar reads as USAGE not remaining/im
3671
+ ];
3672
+ SKIP_EMBED_PATTERNS = [
3673
+ /tmux capture-pane\b/i,
3674
+ /docker ps\b/i,
3675
+ /docker images\b/i,
3676
+ /git status\b/i,
3677
+ /grep .*node_modules/i,
3678
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3679
+ ];
3680
+ }
3681
+ });
3682
+
3484
3683
  // src/lib/shard-manager.ts
3485
3684
  var shard_manager_exports = {};
3486
3685
  __export(shard_manager_exports, {
@@ -3645,7 +3844,8 @@ async function ensureShardSchema(client) {
3645
3844
  }
3646
3845
  for (const idx of [
3647
3846
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3648
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3847
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3848
+ "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"
3649
3849
  ]) {
3650
3850
  try {
3651
3851
  await client.execute(idx);
@@ -3839,7 +4039,7 @@ var init_platform_procedures = __esm({
3839
4039
  title: "Chain of command \u2014 who talks to whom",
3840
4040
  domain: "workflow",
3841
4041
  priority: "p0",
3842
- 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."
4042
+ 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."
3843
4043
  },
3844
4044
  {
3845
4045
  title: "Single dispatch path \u2014 create_task only",
@@ -4070,7 +4270,6 @@ __export(store_exports, {
4070
4270
  vectorToBlob: () => vectorToBlob,
4071
4271
  writeMemory: () => writeMemory
4072
4272
  });
4073
- import { createHash } from "crypto";
4074
4273
  function isBusyError2(err) {
4075
4274
  if (err instanceof Error) {
4076
4275
  const msg = err.message.toLowerCase();
@@ -4184,17 +4383,24 @@ async function writeMemory(record) {
4184
4383
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4185
4384
  );
4186
4385
  }
4187
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4188
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4386
+ const governed = governMemoryRecord(record);
4387
+ if (governed.shouldDrop) return;
4388
+ record = governed.record;
4389
+ const contentHash = governed.contentHash;
4390
+ const memoryType = record.memory_type ?? "raw";
4391
+ if (_pendingRecords.some(
4392
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4393
+ )) {
4189
4394
  return;
4190
4395
  }
4191
4396
  try {
4192
- const client = getClient();
4193
- const existing = await client.execute({
4194
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4195
- args: [contentHash, record.agent_id]
4397
+ const existing = await findScopedDuplicate({
4398
+ contentHash,
4399
+ agentId: record.agent_id,
4400
+ projectName: record.project_name,
4401
+ memoryType
4196
4402
  });
4197
- if (existing.rows.length > 0) return;
4403
+ if (existing) return;
4198
4404
  } catch {
4199
4405
  }
4200
4406
  const dbRow = {
@@ -4225,7 +4431,7 @@ async function writeMemory(record) {
4225
4431
  tier: record.tier ?? classifyTier(record),
4226
4432
  supersedes_id: record.supersedes_id ?? null,
4227
4433
  draft: record.draft ? 1 : 0,
4228
- memory_type: record.memory_type ?? "raw",
4434
+ memory_type: memoryType,
4229
4435
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4230
4436
  content_hash: contentHash,
4231
4437
  intent: record.intent ?? null,
@@ -4384,6 +4590,7 @@ async function flushBatch() {
4384
4590
  const globalClient = getClient();
4385
4591
  const globalStmts = batch.map(buildStmt);
4386
4592
  await globalClient.batch(globalStmts, "write");
4593
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4387
4594
  _pendingRecords.splice(0, batch.length);
4388
4595
  try {
4389
4596
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4504,7 +4711,11 @@ async function searchMemories(queryVector, agentId, options) {
4504
4711
  sql += ` AND timestamp >= ?`;
4505
4712
  args.push(options.since);
4506
4713
  }
4507
- if (options?.memoryType) {
4714
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4715
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4716
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4717
+ args.push(...uniqueTypes);
4718
+ } else if (options?.memoryType) {
4508
4719
  sql += ` AND memory_type = ?`;
4509
4720
  args.push(options.memoryType);
4510
4721
  }
@@ -4642,6 +4853,7 @@ var init_store = __esm({
4642
4853
  init_keychain();
4643
4854
  init_config();
4644
4855
  init_state_bus();
4856
+ init_memory_write_governor();
4645
4857
  INIT_MAX_RETRIES = 3;
4646
4858
  INIT_RETRY_DELAY_MS = 1e3;
4647
4859
  _pendingRecords = [];
@@ -2592,6 +2592,14 @@ async function ensureSchema() {
2592
2592
  );
2593
2593
  } catch {
2594
2594
  }
2595
+ try {
2596
+ await client.execute(
2597
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2598
+ ON memories(content_hash, agent_id, project_name, memory_type)
2599
+ WHERE content_hash IS NOT NULL`
2600
+ );
2601
+ } catch {
2602
+ }
2595
2603
  await client.executeMultiple(`
2596
2604
  CREATE TABLE IF NOT EXISTS entities (
2597
2605
  id TEXT PRIMARY KEY,
@@ -3462,6 +3470,197 @@ var init_state_bus = __esm({
3462
3470
  }
3463
3471
  });
3464
3472
 
3473
+ // src/lib/memory-write-governor.ts
3474
+ import { createHash } from "crypto";
3475
+ function normalizeMemoryText(text) {
3476
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3477
+ }
3478
+ function classifyMemoryType(input2) {
3479
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3480
+ const tool = input2.tool_name.toLowerCase();
3481
+ const text = input2.raw_text.toLowerCase();
3482
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3483
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3484
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3485
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3486
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3487
+ if (tool === "store_memory" || tool === "manual") return "observation";
3488
+ return "raw";
3489
+ }
3490
+ function shouldDropMemory(text) {
3491
+ const normalized = normalizeMemoryText(text);
3492
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3493
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3494
+ return { drop: true, reason: "known_boilerplate_noise" };
3495
+ }
3496
+ return { drop: false };
3497
+ }
3498
+ function shouldSkipEmbedding(input2) {
3499
+ const type = classifyMemoryType(input2);
3500
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3501
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3502
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3503
+ return false;
3504
+ }
3505
+ function hashMemoryContent(text) {
3506
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3507
+ }
3508
+ function scopedDedupArgs(input2) {
3509
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3510
+ }
3511
+ function governMemoryRecord(record) {
3512
+ const normalized = normalizeMemoryText(record.raw_text);
3513
+ const memoryType = classifyMemoryType({
3514
+ raw_text: normalized,
3515
+ agent_id: record.agent_id,
3516
+ project_name: record.project_name,
3517
+ tool_name: record.tool_name,
3518
+ memory_type: record.memory_type
3519
+ });
3520
+ const drop = shouldDropMemory(normalized);
3521
+ const skipEmbedding = shouldSkipEmbedding({
3522
+ raw_text: normalized,
3523
+ agent_id: record.agent_id,
3524
+ project_name: record.project_name,
3525
+ tool_name: record.tool_name,
3526
+ memory_type: memoryType
3527
+ });
3528
+ return {
3529
+ record: {
3530
+ ...record,
3531
+ raw_text: normalized,
3532
+ memory_type: memoryType,
3533
+ vector: skipEmbedding ? null : record.vector
3534
+ },
3535
+ contentHash: hashMemoryContent(normalized),
3536
+ shouldDrop: drop.drop,
3537
+ dropReason: drop.reason,
3538
+ skipEmbedding,
3539
+ hygiene: {
3540
+ dedup: true,
3541
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3542
+ }
3543
+ };
3544
+ }
3545
+ async function findScopedDuplicate(input2) {
3546
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3547
+ const client = getClient2();
3548
+ const args = scopedDedupArgs(input2);
3549
+ let sql = `SELECT id FROM memories
3550
+ WHERE content_hash = ?
3551
+ AND agent_id = ?
3552
+ AND project_name = ?
3553
+ AND COALESCE(memory_type, 'raw') = ?
3554
+ AND COALESCE(status, 'active') != 'deleted'`;
3555
+ if (input2.excludeId) {
3556
+ sql += " AND id != ?";
3557
+ args.push(input2.excludeId);
3558
+ }
3559
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3560
+ const result = await client.execute({ sql, args });
3561
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3562
+ }
3563
+ async function runPostWriteMemoryHygiene(memoryId) {
3564
+ try {
3565
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3566
+ const client = getClient2();
3567
+ const current = await client.execute({
3568
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3569
+ importance, timestamp
3570
+ FROM memories
3571
+ WHERE id = ?
3572
+ LIMIT 1`,
3573
+ args: [memoryId]
3574
+ });
3575
+ const row = current.rows[0];
3576
+ if (!row) return;
3577
+ const memoryType = String(row.memory_type ?? "raw");
3578
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3579
+ const agentId = String(row.agent_id);
3580
+ const projectName = String(row.project_name);
3581
+ if (contentHash) {
3582
+ await client.execute({
3583
+ sql: `UPDATE memories
3584
+ SET status = 'deleted',
3585
+ outcome = COALESCE(outcome, 'superseded')
3586
+ WHERE id != ?
3587
+ AND content_hash = ?
3588
+ AND agent_id = ?
3589
+ AND project_name = ?
3590
+ AND COALESCE(memory_type, 'raw') = ?
3591
+ AND COALESCE(status, 'active') = 'active'`,
3592
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3593
+ });
3594
+ }
3595
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3596
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3597
+ const old = await client.execute({
3598
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3599
+ args: [supersedesId]
3600
+ });
3601
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3602
+ const newImportance = Number(row.importance ?? 0);
3603
+ await client.batch([
3604
+ {
3605
+ sql: `UPDATE memories
3606
+ SET status = 'archived',
3607
+ outcome = COALESCE(outcome, 'superseded')
3608
+ WHERE id = ?`,
3609
+ args: [supersedesId]
3610
+ },
3611
+ {
3612
+ sql: `UPDATE memories
3613
+ SET importance = MAX(COALESCE(importance, 5), ?),
3614
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3615
+ WHERE id = ?`,
3616
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3617
+ }
3618
+ ], "write");
3619
+ }
3620
+ } catch (err) {
3621
+ process.stderr.write(
3622
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3623
+ `
3624
+ );
3625
+ }
3626
+ }
3627
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3628
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3629
+ if (memoryIds.length === 0) return;
3630
+ const run = () => {
3631
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3632
+ };
3633
+ if (typeof setImmediate === "function") setImmediate(run);
3634
+ else setTimeout(run, 0);
3635
+ }
3636
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3637
+ var init_memory_write_governor = __esm({
3638
+ "src/lib/memory-write-governor.ts"() {
3639
+ "use strict";
3640
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3641
+ "decision",
3642
+ "adr",
3643
+ "behavior",
3644
+ "procedure"
3645
+ ]);
3646
+ NOISE_DROP_PATTERNS = [
3647
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3648
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3649
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3650
+ /^\s*Intercom is a speedup, not delivery/im,
3651
+ /^\s*Context bar reads as USAGE not remaining/im
3652
+ ];
3653
+ SKIP_EMBED_PATTERNS = [
3654
+ /tmux capture-pane\b/i,
3655
+ /docker ps\b/i,
3656
+ /docker images\b/i,
3657
+ /git status\b/i,
3658
+ /grep .*node_modules/i,
3659
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3660
+ ];
3661
+ }
3662
+ });
3663
+
3465
3664
  // src/lib/shard-manager.ts
3466
3665
  var shard_manager_exports = {};
3467
3666
  __export(shard_manager_exports, {
@@ -3626,7 +3825,8 @@ async function ensureShardSchema(client) {
3626
3825
  }
3627
3826
  for (const idx of [
3628
3827
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3629
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3828
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3829
+ "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"
3630
3830
  ]) {
3631
3831
  try {
3632
3832
  await client.execute(idx);
@@ -3820,7 +4020,7 @@ var init_platform_procedures = __esm({
3820
4020
  title: "Chain of command \u2014 who talks to whom",
3821
4021
  domain: "workflow",
3822
4022
  priority: "p0",
3823
- 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."
4023
+ 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."
3824
4024
  },
3825
4025
  {
3826
4026
  title: "Single dispatch path \u2014 create_task only",
@@ -4051,7 +4251,6 @@ __export(store_exports, {
4051
4251
  vectorToBlob: () => vectorToBlob,
4052
4252
  writeMemory: () => writeMemory
4053
4253
  });
4054
- import { createHash } from "crypto";
4055
4254
  function isBusyError2(err) {
4056
4255
  if (err instanceof Error) {
4057
4256
  const msg = err.message.toLowerCase();
@@ -4165,17 +4364,24 @@ async function writeMemory(record) {
4165
4364
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4166
4365
  );
4167
4366
  }
4168
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4169
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4367
+ const governed = governMemoryRecord(record);
4368
+ if (governed.shouldDrop) return;
4369
+ record = governed.record;
4370
+ const contentHash = governed.contentHash;
4371
+ const memoryType = record.memory_type ?? "raw";
4372
+ if (_pendingRecords.some(
4373
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4374
+ )) {
4170
4375
  return;
4171
4376
  }
4172
4377
  try {
4173
- const client = getClient();
4174
- const existing = await client.execute({
4175
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4176
- args: [contentHash, record.agent_id]
4378
+ const existing = await findScopedDuplicate({
4379
+ contentHash,
4380
+ agentId: record.agent_id,
4381
+ projectName: record.project_name,
4382
+ memoryType
4177
4383
  });
4178
- if (existing.rows.length > 0) return;
4384
+ if (existing) return;
4179
4385
  } catch {
4180
4386
  }
4181
4387
  const dbRow = {
@@ -4206,7 +4412,7 @@ async function writeMemory(record) {
4206
4412
  tier: record.tier ?? classifyTier(record),
4207
4413
  supersedes_id: record.supersedes_id ?? null,
4208
4414
  draft: record.draft ? 1 : 0,
4209
- memory_type: record.memory_type ?? "raw",
4415
+ memory_type: memoryType,
4210
4416
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4211
4417
  content_hash: contentHash,
4212
4418
  intent: record.intent ?? null,
@@ -4365,6 +4571,7 @@ async function flushBatch() {
4365
4571
  const globalClient = getClient();
4366
4572
  const globalStmts = batch.map(buildStmt);
4367
4573
  await globalClient.batch(globalStmts, "write");
4574
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4368
4575
  _pendingRecords.splice(0, batch.length);
4369
4576
  try {
4370
4577
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4485,7 +4692,11 @@ async function searchMemories(queryVector, agentId, options) {
4485
4692
  sql += ` AND timestamp >= ?`;
4486
4693
  args.push(options.since);
4487
4694
  }
4488
- if (options?.memoryType) {
4695
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4696
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4697
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4698
+ args.push(...uniqueTypes);
4699
+ } else if (options?.memoryType) {
4489
4700
  sql += ` AND memory_type = ?`;
4490
4701
  args.push(options.memoryType);
4491
4702
  }
@@ -4623,6 +4834,7 @@ var init_store = __esm({
4623
4834
  init_keychain();
4624
4835
  init_config();
4625
4836
  init_state_bus();
4837
+ init_memory_write_governor();
4626
4838
  INIT_MAX_RETRIES = 3;
4627
4839
  INIT_RETRY_DELAY_MS = 1e3;
4628
4840
  _pendingRecords = [];
@@ -2349,6 +2349,14 @@ async function ensureSchema() {
2349
2349
  );
2350
2350
  } catch {
2351
2351
  }
2352
+ try {
2353
+ await client.execute(
2354
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2355
+ ON memories(content_hash, agent_id, project_name, memory_type)
2356
+ WHERE content_hash IS NOT NULL`
2357
+ );
2358
+ } catch {
2359
+ }
2352
2360
  await client.executeMultiple(`
2353
2361
  CREATE TABLE IF NOT EXISTS entities (
2354
2362
  id TEXT PRIMARY KEY,
@@ -3373,7 +3381,8 @@ async function ensureShardSchema(client) {
3373
3381
  }
3374
3382
  for (const idx of [
3375
3383
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3376
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3384
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3385
+ "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"
3377
3386
  ]) {
3378
3387
  try {
3379
3388
  await client.execute(idx);
@@ -3567,7 +3576,7 @@ var init_platform_procedures = __esm({
3567
3576
  title: "Chain of command \u2014 who talks to whom",
3568
3577
  domain: "workflow",
3569
3578
  priority: "p0",
3570
- 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."
3579
+ 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."
3571
3580
  },
3572
3581
  {
3573
3582
  title: "Single dispatch path \u2014 create_task only",
@@ -6598,7 +6607,11 @@ init_database();
6598
6607
  init_keychain();
6599
6608
  init_config();
6600
6609
  init_state_bus();
6610
+
6611
+ // src/lib/memory-write-governor.ts
6601
6612
  import { createHash } from "crypto";
6613
+
6614
+ // src/lib/store.ts
6602
6615
  var INIT_MAX_RETRIES = 3;
6603
6616
  var INIT_RETRY_DELAY_MS = 1e3;
6604
6617
  function isBusyError2(err) {