@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
@@ -2213,6 +2213,14 @@ async function ensureSchema() {
2213
2213
  );
2214
2214
  } catch {
2215
2215
  }
2216
+ try {
2217
+ await client.execute(
2218
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2219
+ ON memories(content_hash, agent_id, project_name, memory_type)
2220
+ WHERE content_hash IS NOT NULL`
2221
+ );
2222
+ } catch {
2223
+ }
2216
2224
  await client.executeMultiple(`
2217
2225
  CREATE TABLE IF NOT EXISTS entities (
2218
2226
  id TEXT PRIMARY KEY,
@@ -2882,7 +2890,8 @@ async function ensureShardSchema(client) {
2882
2890
  }
2883
2891
  for (const idx of [
2884
2892
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2885
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
2893
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2894
+ "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"
2886
2895
  ]) {
2887
2896
  try {
2888
2897
  await client.execute(idx);
@@ -3076,7 +3085,7 @@ var init_platform_procedures = __esm({
3076
3085
  title: "Chain of command \u2014 who talks to whom",
3077
3086
  domain: "workflow",
3078
3087
  priority: "p0",
3079
- 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."
3088
+ 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."
3080
3089
  },
3081
3090
  {
3082
3091
  title: "Single dispatch path \u2014 create_task only",
@@ -3301,7 +3310,6 @@ import { parseArgs } from "util";
3301
3310
  // src/lib/store.ts
3302
3311
  init_memory();
3303
3312
  init_database();
3304
- import { createHash } from "crypto";
3305
3313
 
3306
3314
  // src/lib/keychain.ts
3307
3315
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -3534,6 +3542,191 @@ var StateBus = class {
3534
3542
  };
3535
3543
  var orgBus = new StateBus();
3536
3544
 
3545
+ // src/lib/memory-write-governor.ts
3546
+ import { createHash } from "crypto";
3547
+ var HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3548
+ "decision",
3549
+ "adr",
3550
+ "behavior",
3551
+ "procedure"
3552
+ ]);
3553
+ var NOISE_DROP_PATTERNS = [
3554
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3555
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3556
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3557
+ /^\s*Intercom is a speedup, not delivery/im,
3558
+ /^\s*Context bar reads as USAGE not remaining/im
3559
+ ];
3560
+ var SKIP_EMBED_PATTERNS = [
3561
+ /tmux capture-pane\b/i,
3562
+ /docker ps\b/i,
3563
+ /docker images\b/i,
3564
+ /git status\b/i,
3565
+ /grep .*node_modules/i,
3566
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3567
+ ];
3568
+ function normalizeMemoryText(text) {
3569
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3570
+ }
3571
+ function classifyMemoryType(input) {
3572
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3573
+ const tool = input.tool_name.toLowerCase();
3574
+ const text = input.raw_text.toLowerCase();
3575
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3576
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3577
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3578
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3579
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3580
+ if (tool === "store_memory" || tool === "manual") return "observation";
3581
+ return "raw";
3582
+ }
3583
+ function shouldDropMemory(text) {
3584
+ const normalized = normalizeMemoryText(text);
3585
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3586
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3587
+ return { drop: true, reason: "known_boilerplate_noise" };
3588
+ }
3589
+ return { drop: false };
3590
+ }
3591
+ function shouldSkipEmbedding(input) {
3592
+ const type = classifyMemoryType(input);
3593
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3594
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3595
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3596
+ return false;
3597
+ }
3598
+ function hashMemoryContent(text) {
3599
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3600
+ }
3601
+ function scopedDedupArgs(input) {
3602
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3603
+ }
3604
+ function governMemoryRecord(record) {
3605
+ const normalized = normalizeMemoryText(record.raw_text);
3606
+ const memoryType = classifyMemoryType({
3607
+ raw_text: normalized,
3608
+ agent_id: record.agent_id,
3609
+ project_name: record.project_name,
3610
+ tool_name: record.tool_name,
3611
+ memory_type: record.memory_type
3612
+ });
3613
+ const drop = shouldDropMemory(normalized);
3614
+ const skipEmbedding = shouldSkipEmbedding({
3615
+ raw_text: normalized,
3616
+ agent_id: record.agent_id,
3617
+ project_name: record.project_name,
3618
+ tool_name: record.tool_name,
3619
+ memory_type: memoryType
3620
+ });
3621
+ return {
3622
+ record: {
3623
+ ...record,
3624
+ raw_text: normalized,
3625
+ memory_type: memoryType,
3626
+ vector: skipEmbedding ? null : record.vector
3627
+ },
3628
+ contentHash: hashMemoryContent(normalized),
3629
+ shouldDrop: drop.drop,
3630
+ dropReason: drop.reason,
3631
+ skipEmbedding,
3632
+ hygiene: {
3633
+ dedup: true,
3634
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3635
+ }
3636
+ };
3637
+ }
3638
+ async function findScopedDuplicate(input) {
3639
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3640
+ const client = getClient2();
3641
+ const args = scopedDedupArgs(input);
3642
+ let sql = `SELECT id FROM memories
3643
+ WHERE content_hash = ?
3644
+ AND agent_id = ?
3645
+ AND project_name = ?
3646
+ AND COALESCE(memory_type, 'raw') = ?
3647
+ AND COALESCE(status, 'active') != 'deleted'`;
3648
+ if (input.excludeId) {
3649
+ sql += " AND id != ?";
3650
+ args.push(input.excludeId);
3651
+ }
3652
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3653
+ const result = await client.execute({ sql, args });
3654
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3655
+ }
3656
+ async function runPostWriteMemoryHygiene(memoryId) {
3657
+ try {
3658
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3659
+ const client = getClient2();
3660
+ const current = await client.execute({
3661
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3662
+ importance, timestamp
3663
+ FROM memories
3664
+ WHERE id = ?
3665
+ LIMIT 1`,
3666
+ args: [memoryId]
3667
+ });
3668
+ const row = current.rows[0];
3669
+ if (!row) return;
3670
+ const memoryType = String(row.memory_type ?? "raw");
3671
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3672
+ const agentId = String(row.agent_id);
3673
+ const projectName = String(row.project_name);
3674
+ if (contentHash) {
3675
+ await client.execute({
3676
+ sql: `UPDATE memories
3677
+ SET status = 'deleted',
3678
+ outcome = COALESCE(outcome, 'superseded')
3679
+ WHERE id != ?
3680
+ AND content_hash = ?
3681
+ AND agent_id = ?
3682
+ AND project_name = ?
3683
+ AND COALESCE(memory_type, 'raw') = ?
3684
+ AND COALESCE(status, 'active') = 'active'`,
3685
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3686
+ });
3687
+ }
3688
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3689
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3690
+ const old = await client.execute({
3691
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3692
+ args: [supersedesId]
3693
+ });
3694
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3695
+ const newImportance = Number(row.importance ?? 0);
3696
+ await client.batch([
3697
+ {
3698
+ sql: `UPDATE memories
3699
+ SET status = 'archived',
3700
+ outcome = COALESCE(outcome, 'superseded')
3701
+ WHERE id = ?`,
3702
+ args: [supersedesId]
3703
+ },
3704
+ {
3705
+ sql: `UPDATE memories
3706
+ SET importance = MAX(COALESCE(importance, 5), ?),
3707
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3708
+ WHERE id = ?`,
3709
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3710
+ }
3711
+ ], "write");
3712
+ }
3713
+ } catch (err) {
3714
+ process.stderr.write(
3715
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3716
+ `
3717
+ );
3718
+ }
3719
+ }
3720
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3721
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3722
+ if (memoryIds.length === 0) return;
3723
+ const run = () => {
3724
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3725
+ };
3726
+ if (typeof setImmediate === "function") setImmediate(run);
3727
+ else setTimeout(run, 0);
3728
+ }
3729
+
3537
3730
  // src/lib/store.ts
3538
3731
  var INIT_MAX_RETRIES = 3;
3539
3732
  var INIT_RETRY_DELAY_MS = 1e3;
@@ -3656,17 +3849,24 @@ async function writeMemory(record) {
3656
3849
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3657
3850
  );
3658
3851
  }
3659
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3660
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3852
+ const governed = governMemoryRecord(record);
3853
+ if (governed.shouldDrop) return;
3854
+ record = governed.record;
3855
+ const contentHash = governed.contentHash;
3856
+ const memoryType = record.memory_type ?? "raw";
3857
+ if (_pendingRecords.some(
3858
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3859
+ )) {
3661
3860
  return;
3662
3861
  }
3663
3862
  try {
3664
- const client = getClient();
3665
- const existing = await client.execute({
3666
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3667
- args: [contentHash, record.agent_id]
3863
+ const existing = await findScopedDuplicate({
3864
+ contentHash,
3865
+ agentId: record.agent_id,
3866
+ projectName: record.project_name,
3867
+ memoryType
3668
3868
  });
3669
- if (existing.rows.length > 0) return;
3869
+ if (existing) return;
3670
3870
  } catch {
3671
3871
  }
3672
3872
  const dbRow = {
@@ -3697,7 +3897,7 @@ async function writeMemory(record) {
3697
3897
  tier: record.tier ?? classifyTier(record),
3698
3898
  supersedes_id: record.supersedes_id ?? null,
3699
3899
  draft: record.draft ? 1 : 0,
3700
- memory_type: record.memory_type ?? "raw",
3900
+ memory_type: memoryType,
3701
3901
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3702
3902
  content_hash: contentHash,
3703
3903
  intent: record.intent ?? null,
@@ -3856,6 +4056,7 @@ async function flushBatch() {
3856
4056
  const globalClient = getClient();
3857
4057
  const globalStmts = batch.map(buildStmt);
3858
4058
  await globalClient.batch(globalStmts, "write");
4059
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3859
4060
  _pendingRecords.splice(0, batch.length);
3860
4061
  try {
3861
4062
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -2213,6 +2213,14 @@ async function ensureSchema() {
2213
2213
  );
2214
2214
  } catch {
2215
2215
  }
2216
+ try {
2217
+ await client.execute(
2218
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2219
+ ON memories(content_hash, agent_id, project_name, memory_type)
2220
+ WHERE content_hash IS NOT NULL`
2221
+ );
2222
+ } catch {
2223
+ }
2216
2224
  await client.executeMultiple(`
2217
2225
  CREATE TABLE IF NOT EXISTS entities (
2218
2226
  id TEXT PRIMARY KEY,
@@ -2882,7 +2890,8 @@ async function ensureShardSchema(client) {
2882
2890
  }
2883
2891
  for (const idx of [
2884
2892
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2885
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
2893
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2894
+ "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"
2886
2895
  ]) {
2887
2896
  try {
2888
2897
  await client.execute(idx);
@@ -3076,7 +3085,7 @@ var init_platform_procedures = __esm({
3076
3085
  title: "Chain of command \u2014 who talks to whom",
3077
3086
  domain: "workflow",
3078
3087
  priority: "p0",
3079
- 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."
3088
+ 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."
3080
3089
  },
3081
3090
  {
3082
3091
  title: "Single dispatch path \u2014 create_task only",
@@ -3300,7 +3309,6 @@ import { homedir } from "os";
3300
3309
  // src/lib/store.ts
3301
3310
  init_memory();
3302
3311
  init_database();
3303
- import { createHash } from "crypto";
3304
3312
 
3305
3313
  // src/lib/keychain.ts
3306
3314
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -3533,6 +3541,191 @@ var StateBus = class {
3533
3541
  };
3534
3542
  var orgBus = new StateBus();
3535
3543
 
3544
+ // src/lib/memory-write-governor.ts
3545
+ import { createHash } from "crypto";
3546
+ var HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3547
+ "decision",
3548
+ "adr",
3549
+ "behavior",
3550
+ "procedure"
3551
+ ]);
3552
+ var NOISE_DROP_PATTERNS = [
3553
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3554
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3555
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3556
+ /^\s*Intercom is a speedup, not delivery/im,
3557
+ /^\s*Context bar reads as USAGE not remaining/im
3558
+ ];
3559
+ var SKIP_EMBED_PATTERNS = [
3560
+ /tmux capture-pane\b/i,
3561
+ /docker ps\b/i,
3562
+ /docker images\b/i,
3563
+ /git status\b/i,
3564
+ /grep .*node_modules/i,
3565
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3566
+ ];
3567
+ function normalizeMemoryText(text) {
3568
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3569
+ }
3570
+ function classifyMemoryType(input) {
3571
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
3572
+ const tool = input.tool_name.toLowerCase();
3573
+ const text = input.raw_text.toLowerCase();
3574
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3575
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3576
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3577
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3578
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3579
+ if (tool === "store_memory" || tool === "manual") return "observation";
3580
+ return "raw";
3581
+ }
3582
+ function shouldDropMemory(text) {
3583
+ const normalized = normalizeMemoryText(text);
3584
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3585
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3586
+ return { drop: true, reason: "known_boilerplate_noise" };
3587
+ }
3588
+ return { drop: false };
3589
+ }
3590
+ function shouldSkipEmbedding(input) {
3591
+ const type = classifyMemoryType(input);
3592
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3593
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
3594
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
3595
+ return false;
3596
+ }
3597
+ function hashMemoryContent(text) {
3598
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3599
+ }
3600
+ function scopedDedupArgs(input) {
3601
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
3602
+ }
3603
+ function governMemoryRecord(record) {
3604
+ const normalized = normalizeMemoryText(record.raw_text);
3605
+ const memoryType = classifyMemoryType({
3606
+ raw_text: normalized,
3607
+ agent_id: record.agent_id,
3608
+ project_name: record.project_name,
3609
+ tool_name: record.tool_name,
3610
+ memory_type: record.memory_type
3611
+ });
3612
+ const drop = shouldDropMemory(normalized);
3613
+ const skipEmbedding = shouldSkipEmbedding({
3614
+ raw_text: normalized,
3615
+ agent_id: record.agent_id,
3616
+ project_name: record.project_name,
3617
+ tool_name: record.tool_name,
3618
+ memory_type: memoryType
3619
+ });
3620
+ return {
3621
+ record: {
3622
+ ...record,
3623
+ raw_text: normalized,
3624
+ memory_type: memoryType,
3625
+ vector: skipEmbedding ? null : record.vector
3626
+ },
3627
+ contentHash: hashMemoryContent(normalized),
3628
+ shouldDrop: drop.drop,
3629
+ dropReason: drop.reason,
3630
+ skipEmbedding,
3631
+ hygiene: {
3632
+ dedup: true,
3633
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3634
+ }
3635
+ };
3636
+ }
3637
+ async function findScopedDuplicate(input) {
3638
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3639
+ const client = getClient2();
3640
+ const args = scopedDedupArgs(input);
3641
+ let sql = `SELECT id FROM memories
3642
+ WHERE content_hash = ?
3643
+ AND agent_id = ?
3644
+ AND project_name = ?
3645
+ AND COALESCE(memory_type, 'raw') = ?
3646
+ AND COALESCE(status, 'active') != 'deleted'`;
3647
+ if (input.excludeId) {
3648
+ sql += " AND id != ?";
3649
+ args.push(input.excludeId);
3650
+ }
3651
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3652
+ const result = await client.execute({ sql, args });
3653
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3654
+ }
3655
+ async function runPostWriteMemoryHygiene(memoryId) {
3656
+ try {
3657
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3658
+ const client = getClient2();
3659
+ const current = await client.execute({
3660
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3661
+ importance, timestamp
3662
+ FROM memories
3663
+ WHERE id = ?
3664
+ LIMIT 1`,
3665
+ args: [memoryId]
3666
+ });
3667
+ const row = current.rows[0];
3668
+ if (!row) return;
3669
+ const memoryType = String(row.memory_type ?? "raw");
3670
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3671
+ const agentId = String(row.agent_id);
3672
+ const projectName = String(row.project_name);
3673
+ if (contentHash) {
3674
+ await client.execute({
3675
+ sql: `UPDATE memories
3676
+ SET status = 'deleted',
3677
+ outcome = COALESCE(outcome, 'superseded')
3678
+ WHERE id != ?
3679
+ AND content_hash = ?
3680
+ AND agent_id = ?
3681
+ AND project_name = ?
3682
+ AND COALESCE(memory_type, 'raw') = ?
3683
+ AND COALESCE(status, 'active') = 'active'`,
3684
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3685
+ });
3686
+ }
3687
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3688
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3689
+ const old = await client.execute({
3690
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3691
+ args: [supersedesId]
3692
+ });
3693
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3694
+ const newImportance = Number(row.importance ?? 0);
3695
+ await client.batch([
3696
+ {
3697
+ sql: `UPDATE memories
3698
+ SET status = 'archived',
3699
+ outcome = COALESCE(outcome, 'superseded')
3700
+ WHERE id = ?`,
3701
+ args: [supersedesId]
3702
+ },
3703
+ {
3704
+ sql: `UPDATE memories
3705
+ SET importance = MAX(COALESCE(importance, 5), ?),
3706
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3707
+ WHERE id = ?`,
3708
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3709
+ }
3710
+ ], "write");
3711
+ }
3712
+ } catch (err) {
3713
+ process.stderr.write(
3714
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3715
+ `
3716
+ );
3717
+ }
3718
+ }
3719
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3720
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3721
+ if (memoryIds.length === 0) return;
3722
+ const run = () => {
3723
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3724
+ };
3725
+ if (typeof setImmediate === "function") setImmediate(run);
3726
+ else setTimeout(run, 0);
3727
+ }
3728
+
3536
3729
  // src/lib/store.ts
3537
3730
  var INIT_MAX_RETRIES = 3;
3538
3731
  var INIT_RETRY_DELAY_MS = 1e3;
@@ -3655,17 +3848,24 @@ async function writeMemory(record) {
3655
3848
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3656
3849
  );
3657
3850
  }
3658
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3659
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
3851
+ const governed = governMemoryRecord(record);
3852
+ if (governed.shouldDrop) return;
3853
+ record = governed.record;
3854
+ const contentHash = governed.contentHash;
3855
+ const memoryType = record.memory_type ?? "raw";
3856
+ if (_pendingRecords.some(
3857
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
3858
+ )) {
3660
3859
  return;
3661
3860
  }
3662
3861
  try {
3663
- const client = getClient();
3664
- const existing = await client.execute({
3665
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3666
- args: [contentHash, record.agent_id]
3862
+ const existing = await findScopedDuplicate({
3863
+ contentHash,
3864
+ agentId: record.agent_id,
3865
+ projectName: record.project_name,
3866
+ memoryType
3667
3867
  });
3668
- if (existing.rows.length > 0) return;
3868
+ if (existing) return;
3669
3869
  } catch {
3670
3870
  }
3671
3871
  const dbRow = {
@@ -3696,7 +3896,7 @@ async function writeMemory(record) {
3696
3896
  tier: record.tier ?? classifyTier(record),
3697
3897
  supersedes_id: record.supersedes_id ?? null,
3698
3898
  draft: record.draft ? 1 : 0,
3699
- memory_type: record.memory_type ?? "raw",
3899
+ memory_type: memoryType,
3700
3900
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3701
3901
  content_hash: contentHash,
3702
3902
  intent: record.intent ?? null,
@@ -3855,6 +4055,7 @@ async function flushBatch() {
3855
4055
  const globalClient = getClient();
3856
4056
  const globalStmts = batch.map(buildStmt);
3857
4057
  await globalClient.batch(globalStmts, "write");
4058
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
3858
4059
  _pendingRecords.splice(0, batch.length);
3859
4060
  try {
3860
4061
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -2209,6 +2209,14 @@ async function ensureSchema() {
2209
2209
  );
2210
2210
  } catch {
2211
2211
  }
2212
+ try {
2213
+ await client.execute(
2214
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2215
+ ON memories(content_hash, agent_id, project_name, memory_type)
2216
+ WHERE content_hash IS NOT NULL`
2217
+ );
2218
+ } catch {
2219
+ }
2212
2220
  await client.executeMultiple(`
2213
2221
  CREATE TABLE IF NOT EXISTS entities (
2214
2222
  id TEXT PRIMARY KEY,
@@ -2878,7 +2886,8 @@ async function ensureShardSchema(client) {
2878
2886
  }
2879
2887
  for (const idx of [
2880
2888
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
2881
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
2889
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
2890
+ "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"
2882
2891
  ]) {
2883
2892
  try {
2884
2893
  await client.execute(idx);
@@ -3072,7 +3081,7 @@ var init_platform_procedures = __esm({
3072
3081
  title: "Chain of command \u2014 who talks to whom",
3073
3082
  domain: "workflow",
3074
3083
  priority: "p0",
3075
- 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."
3084
+ 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."
3076
3085
  },
3077
3086
  {
3078
3087
  title: "Single dispatch path \u2014 create_task only",
@@ -3288,7 +3297,6 @@ ${p.content}`).join("\n\n");
3288
3297
  // src/lib/store.ts
3289
3298
  init_memory();
3290
3299
  init_database();
3291
- import { createHash } from "crypto";
3292
3300
 
3293
3301
  // src/lib/keychain.ts
3294
3302
  import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
@@ -3521,6 +3529,9 @@ var StateBus = class {
3521
3529
  };
3522
3530
  var orgBus = new StateBus();
3523
3531
 
3532
+ // src/lib/memory-write-governor.ts
3533
+ import { createHash } from "crypto";
3534
+
3524
3535
  // src/lib/store.ts
3525
3536
  var INIT_MAX_RETRIES = 3;
3526
3537
  var INIT_RETRY_DELAY_MS = 1e3;