@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
@@ -2847,6 +2847,14 @@ async function ensureSchema() {
2847
2847
  );
2848
2848
  } catch {
2849
2849
  }
2850
+ try {
2851
+ await client.execute(
2852
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2853
+ ON memories(content_hash, agent_id, project_name, memory_type)
2854
+ WHERE content_hash IS NOT NULL`
2855
+ );
2856
+ } catch {
2857
+ }
2850
2858
  await client.executeMultiple(`
2851
2859
  CREATE TABLE IF NOT EXISTS entities (
2852
2860
  id TEXT PRIMARY KEY,
@@ -6449,7 +6457,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6449
6457
  }
6450
6458
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
6451
6459
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
6452
- envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
6460
+ let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
6461
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
6462
+ ccModel += "[1m]";
6463
+ }
6464
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
6453
6465
  }
6454
6466
  }
6455
6467
  let spawnCommand;
@@ -6763,6 +6775,197 @@ var init_keychain = __esm({
6763
6775
  }
6764
6776
  });
6765
6777
 
6778
+ // src/lib/memory-write-governor.ts
6779
+ import { createHash } from "crypto";
6780
+ function normalizeMemoryText(text) {
6781
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
6782
+ }
6783
+ function classifyMemoryType(input) {
6784
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
6785
+ const tool = input.tool_name.toLowerCase();
6786
+ const text = input.raw_text.toLowerCase();
6787
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
6788
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
6789
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
6790
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
6791
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
6792
+ if (tool === "store_memory" || tool === "manual") return "observation";
6793
+ return "raw";
6794
+ }
6795
+ function shouldDropMemory(text) {
6796
+ const normalized = normalizeMemoryText(text);
6797
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
6798
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
6799
+ return { drop: true, reason: "known_boilerplate_noise" };
6800
+ }
6801
+ return { drop: false };
6802
+ }
6803
+ function shouldSkipEmbedding(input) {
6804
+ const type = classifyMemoryType(input);
6805
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
6806
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
6807
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
6808
+ return false;
6809
+ }
6810
+ function hashMemoryContent(text) {
6811
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
6812
+ }
6813
+ function scopedDedupArgs(input) {
6814
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
6815
+ }
6816
+ function governMemoryRecord(record) {
6817
+ const normalized = normalizeMemoryText(record.raw_text);
6818
+ const memoryType = classifyMemoryType({
6819
+ raw_text: normalized,
6820
+ agent_id: record.agent_id,
6821
+ project_name: record.project_name,
6822
+ tool_name: record.tool_name,
6823
+ memory_type: record.memory_type
6824
+ });
6825
+ const drop = shouldDropMemory(normalized);
6826
+ const skipEmbedding = shouldSkipEmbedding({
6827
+ raw_text: normalized,
6828
+ agent_id: record.agent_id,
6829
+ project_name: record.project_name,
6830
+ tool_name: record.tool_name,
6831
+ memory_type: memoryType
6832
+ });
6833
+ return {
6834
+ record: {
6835
+ ...record,
6836
+ raw_text: normalized,
6837
+ memory_type: memoryType,
6838
+ vector: skipEmbedding ? null : record.vector
6839
+ },
6840
+ contentHash: hashMemoryContent(normalized),
6841
+ shouldDrop: drop.drop,
6842
+ dropReason: drop.reason,
6843
+ skipEmbedding,
6844
+ hygiene: {
6845
+ dedup: true,
6846
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
6847
+ }
6848
+ };
6849
+ }
6850
+ async function findScopedDuplicate(input) {
6851
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
6852
+ const client = getClient2();
6853
+ const args = scopedDedupArgs(input);
6854
+ let sql = `SELECT id FROM memories
6855
+ WHERE content_hash = ?
6856
+ AND agent_id = ?
6857
+ AND project_name = ?
6858
+ AND COALESCE(memory_type, 'raw') = ?
6859
+ AND COALESCE(status, 'active') != 'deleted'`;
6860
+ if (input.excludeId) {
6861
+ sql += " AND id != ?";
6862
+ args.push(input.excludeId);
6863
+ }
6864
+ sql += " ORDER BY timestamp DESC LIMIT 1";
6865
+ const result = await client.execute({ sql, args });
6866
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
6867
+ }
6868
+ async function runPostWriteMemoryHygiene(memoryId) {
6869
+ try {
6870
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
6871
+ const client = getClient2();
6872
+ const current = await client.execute({
6873
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
6874
+ importance, timestamp
6875
+ FROM memories
6876
+ WHERE id = ?
6877
+ LIMIT 1`,
6878
+ args: [memoryId]
6879
+ });
6880
+ const row = current.rows[0];
6881
+ if (!row) return;
6882
+ const memoryType = String(row.memory_type ?? "raw");
6883
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
6884
+ const agentId = String(row.agent_id);
6885
+ const projectName = String(row.project_name);
6886
+ if (contentHash) {
6887
+ await client.execute({
6888
+ sql: `UPDATE memories
6889
+ SET status = 'deleted',
6890
+ outcome = COALESCE(outcome, 'superseded')
6891
+ WHERE id != ?
6892
+ AND content_hash = ?
6893
+ AND agent_id = ?
6894
+ AND project_name = ?
6895
+ AND COALESCE(memory_type, 'raw') = ?
6896
+ AND COALESCE(status, 'active') = 'active'`,
6897
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
6898
+ });
6899
+ }
6900
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
6901
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
6902
+ const old = await client.execute({
6903
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
6904
+ args: [supersedesId]
6905
+ });
6906
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
6907
+ const newImportance = Number(row.importance ?? 0);
6908
+ await client.batch([
6909
+ {
6910
+ sql: `UPDATE memories
6911
+ SET status = 'archived',
6912
+ outcome = COALESCE(outcome, 'superseded')
6913
+ WHERE id = ?`,
6914
+ args: [supersedesId]
6915
+ },
6916
+ {
6917
+ sql: `UPDATE memories
6918
+ SET importance = MAX(COALESCE(importance, 5), ?),
6919
+ parent_memory_id = COALESCE(parent_memory_id, ?)
6920
+ WHERE id = ?`,
6921
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
6922
+ }
6923
+ ], "write");
6924
+ }
6925
+ } catch (err) {
6926
+ process.stderr.write(
6927
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
6928
+ `
6929
+ );
6930
+ }
6931
+ }
6932
+ function schedulePostWriteMemoryHygiene(memoryIds) {
6933
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
6934
+ if (memoryIds.length === 0) return;
6935
+ const run = () => {
6936
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
6937
+ };
6938
+ if (typeof setImmediate === "function") setImmediate(run);
6939
+ else setTimeout(run, 0);
6940
+ }
6941
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
6942
+ var init_memory_write_governor = __esm({
6943
+ "src/lib/memory-write-governor.ts"() {
6944
+ "use strict";
6945
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
6946
+ "decision",
6947
+ "adr",
6948
+ "behavior",
6949
+ "procedure"
6950
+ ]);
6951
+ NOISE_DROP_PATTERNS = [
6952
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
6953
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
6954
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
6955
+ /^\s*Intercom is a speedup, not delivery/im,
6956
+ /^\s*Context bar reads as USAGE not remaining/im
6957
+ ];
6958
+ SKIP_EMBED_PATTERNS = [
6959
+ /tmux capture-pane\b/i,
6960
+ /docker ps\b/i,
6961
+ /docker images\b/i,
6962
+ /git status\b/i,
6963
+ /grep .*node_modules/i,
6964
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
6965
+ ];
6966
+ }
6967
+ });
6968
+
6766
6969
  // src/lib/shard-manager.ts
6767
6970
  var shard_manager_exports = {};
6768
6971
  __export(shard_manager_exports, {
@@ -6927,7 +7130,8 @@ async function ensureShardSchema(client) {
6927
7130
  }
6928
7131
  for (const idx of [
6929
7132
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
6930
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
7133
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
7134
+ "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"
6931
7135
  ]) {
6932
7136
  try {
6933
7137
  await client.execute(idx);
@@ -7121,7 +7325,7 @@ var init_platform_procedures = __esm({
7121
7325
  title: "Chain of command \u2014 who talks to whom",
7122
7326
  domain: "workflow",
7123
7327
  priority: "p0",
7124
- 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."
7328
+ 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."
7125
7329
  },
7126
7330
  {
7127
7331
  title: "Single dispatch path \u2014 create_task only",
@@ -7352,7 +7556,6 @@ __export(store_exports, {
7352
7556
  vectorToBlob: () => vectorToBlob,
7353
7557
  writeMemory: () => writeMemory
7354
7558
  });
7355
- import { createHash } from "crypto";
7356
7559
  function isBusyError2(err) {
7357
7560
  if (err instanceof Error) {
7358
7561
  const msg = err.message.toLowerCase();
@@ -7466,17 +7669,24 @@ async function writeMemory(record) {
7466
7669
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
7467
7670
  );
7468
7671
  }
7469
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
7470
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
7672
+ const governed = governMemoryRecord(record);
7673
+ if (governed.shouldDrop) return;
7674
+ record = governed.record;
7675
+ const contentHash = governed.contentHash;
7676
+ const memoryType = record.memory_type ?? "raw";
7677
+ if (_pendingRecords.some(
7678
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
7679
+ )) {
7471
7680
  return;
7472
7681
  }
7473
7682
  try {
7474
- const client = getClient();
7475
- const existing = await client.execute({
7476
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
7477
- args: [contentHash, record.agent_id]
7683
+ const existing = await findScopedDuplicate({
7684
+ contentHash,
7685
+ agentId: record.agent_id,
7686
+ projectName: record.project_name,
7687
+ memoryType
7478
7688
  });
7479
- if (existing.rows.length > 0) return;
7689
+ if (existing) return;
7480
7690
  } catch {
7481
7691
  }
7482
7692
  const dbRow = {
@@ -7507,7 +7717,7 @@ async function writeMemory(record) {
7507
7717
  tier: record.tier ?? classifyTier(record),
7508
7718
  supersedes_id: record.supersedes_id ?? null,
7509
7719
  draft: record.draft ? 1 : 0,
7510
- memory_type: record.memory_type ?? "raw",
7720
+ memory_type: memoryType,
7511
7721
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
7512
7722
  content_hash: contentHash,
7513
7723
  intent: record.intent ?? null,
@@ -7666,6 +7876,7 @@ async function flushBatch() {
7666
7876
  const globalClient = getClient();
7667
7877
  const globalStmts = batch.map(buildStmt);
7668
7878
  await globalClient.batch(globalStmts, "write");
7879
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
7669
7880
  _pendingRecords.splice(0, batch.length);
7670
7881
  try {
7671
7882
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -7786,7 +7997,11 @@ async function searchMemories(queryVector, agentId, options) {
7786
7997
  sql += ` AND timestamp >= ?`;
7787
7998
  args.push(options.since);
7788
7999
  }
7789
- if (options?.memoryType) {
8000
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
8001
+ const uniqueTypes = [...new Set(options.memoryTypes)];
8002
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
8003
+ args.push(...uniqueTypes);
8004
+ } else if (options?.memoryType) {
7790
8005
  sql += ` AND memory_type = ?`;
7791
8006
  args.push(options.memoryType);
7792
8007
  }
@@ -7924,6 +8139,7 @@ var init_store = __esm({
7924
8139
  init_keychain();
7925
8140
  init_config();
7926
8141
  init_state_bus();
8142
+ init_memory_write_governor();
7927
8143
  INIT_MAX_RETRIES = 3;
7928
8144
  INIT_RETRY_DELAY_MS = 1e3;
7929
8145
  _pendingRecords = [];
package/dist/tui/App.js CHANGED
@@ -3098,6 +3098,14 @@ async function ensureSchema() {
3098
3098
  );
3099
3099
  } catch {
3100
3100
  }
3101
+ try {
3102
+ await client.execute(
3103
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
3104
+ ON memories(content_hash, agent_id, project_name, memory_type)
3105
+ WHERE content_hash IS NOT NULL`
3106
+ );
3107
+ } catch {
3108
+ }
3101
3109
  await client.executeMultiple(`
3102
3110
  CREATE TABLE IF NOT EXISTS entities (
3103
3111
  id TEXT PRIMARY KEY,
@@ -7053,7 +7061,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7053
7061
  }
7054
7062
  if (!useExeAgent && !useCodex && !useOpencode && !useBinSymlink) {
7055
7063
  if (agentRtConfig.runtime === "claude" && agentRtConfig.model) {
7056
- envPrefix = `${envPrefix} ANTHROPIC_MODEL=${agentRtConfig.model}`;
7064
+ let ccModel = agentRtConfig.model.replace(/(\d+)\.(\d+)/g, "$1-$2");
7065
+ if (/claude-(opus|sonnet)-4-[6-9]/.test(ccModel) && !ccModel.includes("[1m]")) {
7066
+ ccModel += "[1m]";
7067
+ }
7068
+ envPrefix = `${envPrefix} ANTHROPIC_MODEL=${ccModel}`;
7057
7069
  }
7058
7070
  }
7059
7071
  let spawnCommand;
@@ -8508,7 +8520,7 @@ var init_platform_procedures = __esm({
8508
8520
  title: "Chain of command \u2014 who talks to whom",
8509
8521
  domain: "workflow",
8510
8522
  priority: "p0",
8511
- 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."
8523
+ 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."
8512
8524
  },
8513
8525
  {
8514
8526
  title: "Single dispatch path \u2014 create_task only",
@@ -11072,6 +11084,197 @@ var init_wiki_client = __esm({
11072
11084
  }
11073
11085
  });
11074
11086
 
11087
+ // src/lib/memory-write-governor.ts
11088
+ import { createHash } from "crypto";
11089
+ function normalizeMemoryText(text) {
11090
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
11091
+ }
11092
+ function classifyMemoryType(input) {
11093
+ if (input.memory_type && input.memory_type.trim()) return input.memory_type.trim();
11094
+ const tool = input.tool_name.toLowerCase();
11095
+ const text = input.raw_text.toLowerCase();
11096
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
11097
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
11098
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
11099
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
11100
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
11101
+ if (tool === "store_memory" || tool === "manual") return "observation";
11102
+ return "raw";
11103
+ }
11104
+ function shouldDropMemory(text) {
11105
+ const normalized = normalizeMemoryText(text);
11106
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
11107
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
11108
+ return { drop: true, reason: "known_boilerplate_noise" };
11109
+ }
11110
+ return { drop: false };
11111
+ }
11112
+ function shouldSkipEmbedding(input) {
11113
+ const type = classifyMemoryType(input);
11114
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
11115
+ if (type === "raw" && input.raw_text.length > 2e4) return true;
11116
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input.raw_text))) return true;
11117
+ return false;
11118
+ }
11119
+ function hashMemoryContent(text) {
11120
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
11121
+ }
11122
+ function scopedDedupArgs(input) {
11123
+ return [input.contentHash, input.agentId, input.projectName, input.memoryType];
11124
+ }
11125
+ function governMemoryRecord(record) {
11126
+ const normalized = normalizeMemoryText(record.raw_text);
11127
+ const memoryType = classifyMemoryType({
11128
+ raw_text: normalized,
11129
+ agent_id: record.agent_id,
11130
+ project_name: record.project_name,
11131
+ tool_name: record.tool_name,
11132
+ memory_type: record.memory_type
11133
+ });
11134
+ const drop = shouldDropMemory(normalized);
11135
+ const skipEmbedding = shouldSkipEmbedding({
11136
+ raw_text: normalized,
11137
+ agent_id: record.agent_id,
11138
+ project_name: record.project_name,
11139
+ tool_name: record.tool_name,
11140
+ memory_type: memoryType
11141
+ });
11142
+ return {
11143
+ record: {
11144
+ ...record,
11145
+ raw_text: normalized,
11146
+ memory_type: memoryType,
11147
+ vector: skipEmbedding ? null : record.vector
11148
+ },
11149
+ contentHash: hashMemoryContent(normalized),
11150
+ shouldDrop: drop.drop,
11151
+ dropReason: drop.reason,
11152
+ skipEmbedding,
11153
+ hygiene: {
11154
+ dedup: true,
11155
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
11156
+ }
11157
+ };
11158
+ }
11159
+ async function findScopedDuplicate(input) {
11160
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
11161
+ const client = getClient2();
11162
+ const args = scopedDedupArgs(input);
11163
+ let sql = `SELECT id FROM memories
11164
+ WHERE content_hash = ?
11165
+ AND agent_id = ?
11166
+ AND project_name = ?
11167
+ AND COALESCE(memory_type, 'raw') = ?
11168
+ AND COALESCE(status, 'active') != 'deleted'`;
11169
+ if (input.excludeId) {
11170
+ sql += " AND id != ?";
11171
+ args.push(input.excludeId);
11172
+ }
11173
+ sql += " ORDER BY timestamp DESC LIMIT 1";
11174
+ const result = await client.execute({ sql, args });
11175
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
11176
+ }
11177
+ async function runPostWriteMemoryHygiene(memoryId) {
11178
+ try {
11179
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
11180
+ const client = getClient2();
11181
+ const current = await client.execute({
11182
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
11183
+ importance, timestamp
11184
+ FROM memories
11185
+ WHERE id = ?
11186
+ LIMIT 1`,
11187
+ args: [memoryId]
11188
+ });
11189
+ const row = current.rows[0];
11190
+ if (!row) return;
11191
+ const memoryType = String(row.memory_type ?? "raw");
11192
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
11193
+ const agentId = String(row.agent_id);
11194
+ const projectName = String(row.project_name);
11195
+ if (contentHash) {
11196
+ await client.execute({
11197
+ sql: `UPDATE memories
11198
+ SET status = 'deleted',
11199
+ outcome = COALESCE(outcome, 'superseded')
11200
+ WHERE id != ?
11201
+ AND content_hash = ?
11202
+ AND agent_id = ?
11203
+ AND project_name = ?
11204
+ AND COALESCE(memory_type, 'raw') = ?
11205
+ AND COALESCE(status, 'active') = 'active'`,
11206
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
11207
+ });
11208
+ }
11209
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
11210
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
11211
+ const old = await client.execute({
11212
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
11213
+ args: [supersedesId]
11214
+ });
11215
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
11216
+ const newImportance = Number(row.importance ?? 0);
11217
+ await client.batch([
11218
+ {
11219
+ sql: `UPDATE memories
11220
+ SET status = 'archived',
11221
+ outcome = COALESCE(outcome, 'superseded')
11222
+ WHERE id = ?`,
11223
+ args: [supersedesId]
11224
+ },
11225
+ {
11226
+ sql: `UPDATE memories
11227
+ SET importance = MAX(COALESCE(importance, 5), ?),
11228
+ parent_memory_id = COALESCE(parent_memory_id, ?)
11229
+ WHERE id = ?`,
11230
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
11231
+ }
11232
+ ], "write");
11233
+ }
11234
+ } catch (err) {
11235
+ process.stderr.write(
11236
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
11237
+ `
11238
+ );
11239
+ }
11240
+ }
11241
+ function schedulePostWriteMemoryHygiene(memoryIds) {
11242
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
11243
+ if (memoryIds.length === 0) return;
11244
+ const run = () => {
11245
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
11246
+ };
11247
+ if (typeof setImmediate === "function") setImmediate(run);
11248
+ else setTimeout(run, 0);
11249
+ }
11250
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
11251
+ var init_memory_write_governor = __esm({
11252
+ "src/lib/memory-write-governor.ts"() {
11253
+ "use strict";
11254
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
11255
+ "decision",
11256
+ "adr",
11257
+ "behavior",
11258
+ "procedure"
11259
+ ]);
11260
+ NOISE_DROP_PATTERNS = [
11261
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
11262
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
11263
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
11264
+ /^\s*Intercom is a speedup, not delivery/im,
11265
+ /^\s*Context bar reads as USAGE not remaining/im
11266
+ ];
11267
+ SKIP_EMBED_PATTERNS = [
11268
+ /tmux capture-pane\b/i,
11269
+ /docker ps\b/i,
11270
+ /docker images\b/i,
11271
+ /git status\b/i,
11272
+ /grep .*node_modules/i,
11273
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
11274
+ ];
11275
+ }
11276
+ });
11277
+
11075
11278
  // src/lib/shard-manager.ts
11076
11279
  var shard_manager_exports = {};
11077
11280
  __export(shard_manager_exports, {
@@ -11236,7 +11439,8 @@ async function ensureShardSchema(client) {
11236
11439
  }
11237
11440
  for (const idx of [
11238
11441
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
11239
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
11442
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
11443
+ "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"
11240
11444
  ]) {
11241
11445
  try {
11242
11446
  await client.execute(idx);
@@ -11412,7 +11616,6 @@ __export(store_exports, {
11412
11616
  vectorToBlob: () => vectorToBlob,
11413
11617
  writeMemory: () => writeMemory
11414
11618
  });
11415
- import { createHash } from "crypto";
11416
11619
  function isBusyError2(err) {
11417
11620
  if (err instanceof Error) {
11418
11621
  const msg = err.message.toLowerCase();
@@ -11526,17 +11729,24 @@ async function writeMemory(record) {
11526
11729
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
11527
11730
  );
11528
11731
  }
11529
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
11530
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
11732
+ const governed = governMemoryRecord(record);
11733
+ if (governed.shouldDrop) return;
11734
+ record = governed.record;
11735
+ const contentHash = governed.contentHash;
11736
+ const memoryType = record.memory_type ?? "raw";
11737
+ if (_pendingRecords.some(
11738
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
11739
+ )) {
11531
11740
  return;
11532
11741
  }
11533
11742
  try {
11534
- const client = getClient();
11535
- const existing = await client.execute({
11536
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
11537
- args: [contentHash, record.agent_id]
11743
+ const existing = await findScopedDuplicate({
11744
+ contentHash,
11745
+ agentId: record.agent_id,
11746
+ projectName: record.project_name,
11747
+ memoryType
11538
11748
  });
11539
- if (existing.rows.length > 0) return;
11749
+ if (existing) return;
11540
11750
  } catch {
11541
11751
  }
11542
11752
  const dbRow = {
@@ -11567,7 +11777,7 @@ async function writeMemory(record) {
11567
11777
  tier: record.tier ?? classifyTier(record),
11568
11778
  supersedes_id: record.supersedes_id ?? null,
11569
11779
  draft: record.draft ? 1 : 0,
11570
- memory_type: record.memory_type ?? "raw",
11780
+ memory_type: memoryType,
11571
11781
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
11572
11782
  content_hash: contentHash,
11573
11783
  intent: record.intent ?? null,
@@ -11726,6 +11936,7 @@ async function flushBatch() {
11726
11936
  const globalClient = getClient();
11727
11937
  const globalStmts = batch.map(buildStmt);
11728
11938
  await globalClient.batch(globalStmts, "write");
11939
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
11729
11940
  _pendingRecords.splice(0, batch.length);
11730
11941
  try {
11731
11942
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -11846,7 +12057,11 @@ async function searchMemories(queryVector, agentId, options) {
11846
12057
  sql += ` AND timestamp >= ?`;
11847
12058
  args.push(options.since);
11848
12059
  }
11849
- if (options?.memoryType) {
12060
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
12061
+ const uniqueTypes = [...new Set(options.memoryTypes)];
12062
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
12063
+ args.push(...uniqueTypes);
12064
+ } else if (options?.memoryType) {
11850
12065
  sql += ` AND memory_type = ?`;
11851
12066
  args.push(options.memoryType);
11852
12067
  }
@@ -11984,6 +12199,7 @@ var init_store = __esm({
11984
12199
  init_keychain();
11985
12200
  init_config();
11986
12201
  init_state_bus();
12202
+ init_memory_write_governor();
11987
12203
  INIT_MAX_RETRIES = 3;
11988
12204
  INIT_RETRY_DELAY_MS = 1e3;
11989
12205
  _pendingRecords = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askexenow/exe-os",
3
- "version": "0.9.34",
3
+ "version": "0.9.36",
4
4
  "description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",