@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
@@ -2585,6 +2585,14 @@ async function ensureSchema() {
2585
2585
  );
2586
2586
  } catch {
2587
2587
  }
2588
+ try {
2589
+ await client.execute(
2590
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2591
+ ON memories(content_hash, agent_id, project_name, memory_type)
2592
+ WHERE content_hash IS NOT NULL`
2593
+ );
2594
+ } catch {
2595
+ }
2588
2596
  await client.executeMultiple(`
2589
2597
  CREATE TABLE IF NOT EXISTS entities (
2590
2598
  id TEXT PRIMARY KEY,
@@ -3455,6 +3463,197 @@ var init_state_bus = __esm({
3455
3463
  }
3456
3464
  });
3457
3465
 
3466
+ // src/lib/memory-write-governor.ts
3467
+ import { createHash } from "crypto";
3468
+ function normalizeMemoryText(text) {
3469
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3470
+ }
3471
+ function classifyMemoryType(input2) {
3472
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3473
+ const tool = input2.tool_name.toLowerCase();
3474
+ const text = input2.raw_text.toLowerCase();
3475
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3476
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3477
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3478
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3479
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3480
+ if (tool === "store_memory" || tool === "manual") return "observation";
3481
+ return "raw";
3482
+ }
3483
+ function shouldDropMemory(text) {
3484
+ const normalized = normalizeMemoryText(text);
3485
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3486
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3487
+ return { drop: true, reason: "known_boilerplate_noise" };
3488
+ }
3489
+ return { drop: false };
3490
+ }
3491
+ function shouldSkipEmbedding(input2) {
3492
+ const type = classifyMemoryType(input2);
3493
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3494
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3495
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3496
+ return false;
3497
+ }
3498
+ function hashMemoryContent(text) {
3499
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3500
+ }
3501
+ function scopedDedupArgs(input2) {
3502
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3503
+ }
3504
+ function governMemoryRecord(record) {
3505
+ const normalized = normalizeMemoryText(record.raw_text);
3506
+ const memoryType = classifyMemoryType({
3507
+ raw_text: normalized,
3508
+ agent_id: record.agent_id,
3509
+ project_name: record.project_name,
3510
+ tool_name: record.tool_name,
3511
+ memory_type: record.memory_type
3512
+ });
3513
+ const drop = shouldDropMemory(normalized);
3514
+ const skipEmbedding = shouldSkipEmbedding({
3515
+ raw_text: normalized,
3516
+ agent_id: record.agent_id,
3517
+ project_name: record.project_name,
3518
+ tool_name: record.tool_name,
3519
+ memory_type: memoryType
3520
+ });
3521
+ return {
3522
+ record: {
3523
+ ...record,
3524
+ raw_text: normalized,
3525
+ memory_type: memoryType,
3526
+ vector: skipEmbedding ? null : record.vector
3527
+ },
3528
+ contentHash: hashMemoryContent(normalized),
3529
+ shouldDrop: drop.drop,
3530
+ dropReason: drop.reason,
3531
+ skipEmbedding,
3532
+ hygiene: {
3533
+ dedup: true,
3534
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3535
+ }
3536
+ };
3537
+ }
3538
+ async function findScopedDuplicate(input2) {
3539
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3540
+ const client = getClient2();
3541
+ const args = scopedDedupArgs(input2);
3542
+ let sql = `SELECT id FROM memories
3543
+ WHERE content_hash = ?
3544
+ AND agent_id = ?
3545
+ AND project_name = ?
3546
+ AND COALESCE(memory_type, 'raw') = ?
3547
+ AND COALESCE(status, 'active') != 'deleted'`;
3548
+ if (input2.excludeId) {
3549
+ sql += " AND id != ?";
3550
+ args.push(input2.excludeId);
3551
+ }
3552
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3553
+ const result = await client.execute({ sql, args });
3554
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3555
+ }
3556
+ async function runPostWriteMemoryHygiene(memoryId) {
3557
+ try {
3558
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3559
+ const client = getClient2();
3560
+ const current = await client.execute({
3561
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3562
+ importance, timestamp
3563
+ FROM memories
3564
+ WHERE id = ?
3565
+ LIMIT 1`,
3566
+ args: [memoryId]
3567
+ });
3568
+ const row = current.rows[0];
3569
+ if (!row) return;
3570
+ const memoryType = String(row.memory_type ?? "raw");
3571
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3572
+ const agentId = String(row.agent_id);
3573
+ const projectName = String(row.project_name);
3574
+ if (contentHash) {
3575
+ await client.execute({
3576
+ sql: `UPDATE memories
3577
+ SET status = 'deleted',
3578
+ outcome = COALESCE(outcome, 'superseded')
3579
+ WHERE id != ?
3580
+ AND content_hash = ?
3581
+ AND agent_id = ?
3582
+ AND project_name = ?
3583
+ AND COALESCE(memory_type, 'raw') = ?
3584
+ AND COALESCE(status, 'active') = 'active'`,
3585
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3586
+ });
3587
+ }
3588
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3589
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3590
+ const old = await client.execute({
3591
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3592
+ args: [supersedesId]
3593
+ });
3594
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3595
+ const newImportance = Number(row.importance ?? 0);
3596
+ await client.batch([
3597
+ {
3598
+ sql: `UPDATE memories
3599
+ SET status = 'archived',
3600
+ outcome = COALESCE(outcome, 'superseded')
3601
+ WHERE id = ?`,
3602
+ args: [supersedesId]
3603
+ },
3604
+ {
3605
+ sql: `UPDATE memories
3606
+ SET importance = MAX(COALESCE(importance, 5), ?),
3607
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3608
+ WHERE id = ?`,
3609
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3610
+ }
3611
+ ], "write");
3612
+ }
3613
+ } catch (err) {
3614
+ process.stderr.write(
3615
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3616
+ `
3617
+ );
3618
+ }
3619
+ }
3620
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3621
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3622
+ if (memoryIds.length === 0) return;
3623
+ const run = () => {
3624
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3625
+ };
3626
+ if (typeof setImmediate === "function") setImmediate(run);
3627
+ else setTimeout(run, 0);
3628
+ }
3629
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3630
+ var init_memory_write_governor = __esm({
3631
+ "src/lib/memory-write-governor.ts"() {
3632
+ "use strict";
3633
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3634
+ "decision",
3635
+ "adr",
3636
+ "behavior",
3637
+ "procedure"
3638
+ ]);
3639
+ NOISE_DROP_PATTERNS = [
3640
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3641
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3642
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3643
+ /^\s*Intercom is a speedup, not delivery/im,
3644
+ /^\s*Context bar reads as USAGE not remaining/im
3645
+ ];
3646
+ SKIP_EMBED_PATTERNS = [
3647
+ /tmux capture-pane\b/i,
3648
+ /docker ps\b/i,
3649
+ /docker images\b/i,
3650
+ /git status\b/i,
3651
+ /grep .*node_modules/i,
3652
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3653
+ ];
3654
+ }
3655
+ });
3656
+
3458
3657
  // src/lib/shard-manager.ts
3459
3658
  var shard_manager_exports = {};
3460
3659
  __export(shard_manager_exports, {
@@ -3619,7 +3818,8 @@ async function ensureShardSchema(client) {
3619
3818
  }
3620
3819
  for (const idx of [
3621
3820
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3622
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3821
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3822
+ "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"
3623
3823
  ]) {
3624
3824
  try {
3625
3825
  await client.execute(idx);
@@ -3813,7 +4013,7 @@ var init_platform_procedures = __esm({
3813
4013
  title: "Chain of command \u2014 who talks to whom",
3814
4014
  domain: "workflow",
3815
4015
  priority: "p0",
3816
- 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."
4016
+ 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."
3817
4017
  },
3818
4018
  {
3819
4019
  title: "Single dispatch path \u2014 create_task only",
@@ -4044,7 +4244,6 @@ __export(store_exports, {
4044
4244
  vectorToBlob: () => vectorToBlob,
4045
4245
  writeMemory: () => writeMemory
4046
4246
  });
4047
- import { createHash } from "crypto";
4048
4247
  function isBusyError2(err) {
4049
4248
  if (err instanceof Error) {
4050
4249
  const msg = err.message.toLowerCase();
@@ -4158,17 +4357,24 @@ async function writeMemory(record) {
4158
4357
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
4159
4358
  );
4160
4359
  }
4161
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
4162
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4360
+ const governed = governMemoryRecord(record);
4361
+ if (governed.shouldDrop) return;
4362
+ record = governed.record;
4363
+ const contentHash = governed.contentHash;
4364
+ const memoryType = record.memory_type ?? "raw";
4365
+ if (_pendingRecords.some(
4366
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4367
+ )) {
4163
4368
  return;
4164
4369
  }
4165
4370
  try {
4166
- const client = getClient();
4167
- const existing = await client.execute({
4168
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
4169
- args: [contentHash, record.agent_id]
4371
+ const existing = await findScopedDuplicate({
4372
+ contentHash,
4373
+ agentId: record.agent_id,
4374
+ projectName: record.project_name,
4375
+ memoryType
4170
4376
  });
4171
- if (existing.rows.length > 0) return;
4377
+ if (existing) return;
4172
4378
  } catch {
4173
4379
  }
4174
4380
  const dbRow = {
@@ -4199,7 +4405,7 @@ async function writeMemory(record) {
4199
4405
  tier: record.tier ?? classifyTier(record),
4200
4406
  supersedes_id: record.supersedes_id ?? null,
4201
4407
  draft: record.draft ? 1 : 0,
4202
- memory_type: record.memory_type ?? "raw",
4408
+ memory_type: memoryType,
4203
4409
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4204
4410
  content_hash: contentHash,
4205
4411
  intent: record.intent ?? null,
@@ -4358,6 +4564,7 @@ async function flushBatch() {
4358
4564
  const globalClient = getClient();
4359
4565
  const globalStmts = batch.map(buildStmt);
4360
4566
  await globalClient.batch(globalStmts, "write");
4567
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4361
4568
  _pendingRecords.splice(0, batch.length);
4362
4569
  try {
4363
4570
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4478,7 +4685,11 @@ async function searchMemories(queryVector, agentId, options) {
4478
4685
  sql += ` AND timestamp >= ?`;
4479
4686
  args.push(options.since);
4480
4687
  }
4481
- if (options?.memoryType) {
4688
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4689
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4690
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4691
+ args.push(...uniqueTypes);
4692
+ } else if (options?.memoryType) {
4482
4693
  sql += ` AND memory_type = ?`;
4483
4694
  args.push(options.memoryType);
4484
4695
  }
@@ -4616,6 +4827,7 @@ var init_store = __esm({
4616
4827
  init_keychain();
4617
4828
  init_config();
4618
4829
  init_state_bus();
4830
+ init_memory_write_governor();
4619
4831
  INIT_MAX_RETRIES = 3;
4620
4832
  INIT_RETRY_DELAY_MS = 1e3;
4621
4833
  _pendingRecords = [];
@@ -2454,6 +2454,14 @@ async function ensureSchema() {
2454
2454
  );
2455
2455
  } catch {
2456
2456
  }
2457
+ try {
2458
+ await client.execute(
2459
+ `CREATE INDEX IF NOT EXISTS idx_memories_scoped_content_hash
2460
+ ON memories(content_hash, agent_id, project_name, memory_type)
2461
+ WHERE content_hash IS NOT NULL`
2462
+ );
2463
+ } catch {
2464
+ }
2457
2465
  await client.executeMultiple(`
2458
2466
  CREATE TABLE IF NOT EXISTS entities (
2459
2467
  id TEXT PRIMARY KEY,
@@ -3199,6 +3207,197 @@ var init_state_bus = __esm({
3199
3207
  }
3200
3208
  });
3201
3209
 
3210
+ // src/lib/memory-write-governor.ts
3211
+ import { createHash } from "crypto";
3212
+ function normalizeMemoryText(text) {
3213
+ return text.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").replace(/\n{4,}/g, "\n\n\n").trim();
3214
+ }
3215
+ function classifyMemoryType(input2) {
3216
+ if (input2.memory_type && input2.memory_type.trim()) return input2.memory_type.trim();
3217
+ const tool = input2.tool_name.toLowerCase();
3218
+ const text = input2.raw_text.toLowerCase();
3219
+ if (tool.includes("store_decision") || tool.includes("decision")) return "decision";
3220
+ if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3221
+ if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3222
+ if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3223
+ if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3224
+ if (tool === "store_memory" || tool === "manual") return "observation";
3225
+ return "raw";
3226
+ }
3227
+ function shouldDropMemory(text) {
3228
+ const normalized = normalizeMemoryText(text);
3229
+ if (normalized.length < 10) return { drop: true, reason: "too_short" };
3230
+ if (NOISE_DROP_PATTERNS.some((pattern) => pattern.test(normalized))) {
3231
+ return { drop: true, reason: "known_boilerplate_noise" };
3232
+ }
3233
+ return { drop: false };
3234
+ }
3235
+ function shouldSkipEmbedding(input2) {
3236
+ const type = classifyMemoryType(input2);
3237
+ if (HIGH_VALUE_SUPERSESSION_TYPES.has(type)) return false;
3238
+ if (type === "raw" && input2.raw_text.length > 2e4) return true;
3239
+ if (SKIP_EMBED_PATTERNS.some((pattern) => pattern.test(input2.raw_text))) return true;
3240
+ return false;
3241
+ }
3242
+ function hashMemoryContent(text) {
3243
+ return createHash("sha256").update(normalizeMemoryText(text)).digest("hex");
3244
+ }
3245
+ function scopedDedupArgs(input2) {
3246
+ return [input2.contentHash, input2.agentId, input2.projectName, input2.memoryType];
3247
+ }
3248
+ function governMemoryRecord(record) {
3249
+ const normalized = normalizeMemoryText(record.raw_text);
3250
+ const memoryType = classifyMemoryType({
3251
+ raw_text: normalized,
3252
+ agent_id: record.agent_id,
3253
+ project_name: record.project_name,
3254
+ tool_name: record.tool_name,
3255
+ memory_type: record.memory_type
3256
+ });
3257
+ const drop = shouldDropMemory(normalized);
3258
+ const skipEmbedding = shouldSkipEmbedding({
3259
+ raw_text: normalized,
3260
+ agent_id: record.agent_id,
3261
+ project_name: record.project_name,
3262
+ tool_name: record.tool_name,
3263
+ memory_type: memoryType
3264
+ });
3265
+ return {
3266
+ record: {
3267
+ ...record,
3268
+ raw_text: normalized,
3269
+ memory_type: memoryType,
3270
+ vector: skipEmbedding ? null : record.vector
3271
+ },
3272
+ contentHash: hashMemoryContent(normalized),
3273
+ shouldDrop: drop.drop,
3274
+ dropReason: drop.reason,
3275
+ skipEmbedding,
3276
+ hygiene: {
3277
+ dedup: true,
3278
+ supersession: HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)
3279
+ }
3280
+ };
3281
+ }
3282
+ async function findScopedDuplicate(input2) {
3283
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3284
+ const client = getClient2();
3285
+ const args = scopedDedupArgs(input2);
3286
+ let sql = `SELECT id FROM memories
3287
+ WHERE content_hash = ?
3288
+ AND agent_id = ?
3289
+ AND project_name = ?
3290
+ AND COALESCE(memory_type, 'raw') = ?
3291
+ AND COALESCE(status, 'active') != 'deleted'`;
3292
+ if (input2.excludeId) {
3293
+ sql += " AND id != ?";
3294
+ args.push(input2.excludeId);
3295
+ }
3296
+ sql += " ORDER BY timestamp DESC LIMIT 1";
3297
+ const result = await client.execute({ sql, args });
3298
+ return result.rows[0]?.id ? String(result.rows[0].id) : null;
3299
+ }
3300
+ async function runPostWriteMemoryHygiene(memoryId) {
3301
+ try {
3302
+ const { getClient: getClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3303
+ const client = getClient2();
3304
+ const current = await client.execute({
3305
+ sql: `SELECT id, agent_id, project_name, memory_type, content_hash, supersedes_id,
3306
+ importance, timestamp
3307
+ FROM memories
3308
+ WHERE id = ?
3309
+ LIMIT 1`,
3310
+ args: [memoryId]
3311
+ });
3312
+ const row = current.rows[0];
3313
+ if (!row) return;
3314
+ const memoryType = String(row.memory_type ?? "raw");
3315
+ const contentHash = row.content_hash ? String(row.content_hash) : null;
3316
+ const agentId = String(row.agent_id);
3317
+ const projectName = String(row.project_name);
3318
+ if (contentHash) {
3319
+ await client.execute({
3320
+ sql: `UPDATE memories
3321
+ SET status = 'deleted',
3322
+ outcome = COALESCE(outcome, 'superseded')
3323
+ WHERE id != ?
3324
+ AND content_hash = ?
3325
+ AND agent_id = ?
3326
+ AND project_name = ?
3327
+ AND COALESCE(memory_type, 'raw') = ?
3328
+ AND COALESCE(status, 'active') = 'active'`,
3329
+ args: [memoryId, contentHash, agentId, projectName, memoryType]
3330
+ });
3331
+ }
3332
+ const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
3333
+ if (supersedesId && HIGH_VALUE_SUPERSESSION_TYPES.has(memoryType)) {
3334
+ const old = await client.execute({
3335
+ sql: `SELECT importance FROM memories WHERE id = ? LIMIT 1`,
3336
+ args: [supersedesId]
3337
+ });
3338
+ const oldImportance = Number(old.rows[0]?.importance ?? 0);
3339
+ const newImportance = Number(row.importance ?? 0);
3340
+ await client.batch([
3341
+ {
3342
+ sql: `UPDATE memories
3343
+ SET status = 'archived',
3344
+ outcome = COALESCE(outcome, 'superseded')
3345
+ WHERE id = ?`,
3346
+ args: [supersedesId]
3347
+ },
3348
+ {
3349
+ sql: `UPDATE memories
3350
+ SET importance = MAX(COALESCE(importance, 5), ?),
3351
+ parent_memory_id = COALESCE(parent_memory_id, ?)
3352
+ WHERE id = ?`,
3353
+ args: [Math.max(oldImportance, newImportance), supersedesId, memoryId]
3354
+ }
3355
+ ], "write");
3356
+ }
3357
+ } catch (err) {
3358
+ process.stderr.write(
3359
+ `[memory-governor] post-write hygiene failed for ${memoryId}: ${err instanceof Error ? err.message : String(err)}
3360
+ `
3361
+ );
3362
+ }
3363
+ }
3364
+ function schedulePostWriteMemoryHygiene(memoryIds) {
3365
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3366
+ if (memoryIds.length === 0) return;
3367
+ const run = () => {
3368
+ void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
3369
+ };
3370
+ if (typeof setImmediate === "function") setImmediate(run);
3371
+ else setTimeout(run, 0);
3372
+ }
3373
+ var HIGH_VALUE_SUPERSESSION_TYPES, NOISE_DROP_PATTERNS, SKIP_EMBED_PATTERNS;
3374
+ var init_memory_write_governor = __esm({
3375
+ "src/lib/memory-write-governor.ts"() {
3376
+ "use strict";
3377
+ HIGH_VALUE_SUPERSESSION_TYPES = /* @__PURE__ */ new Set([
3378
+ "decision",
3379
+ "adr",
3380
+ "behavior",
3381
+ "procedure"
3382
+ ]);
3383
+ NOISE_DROP_PATTERNS = [
3384
+ /^\s*\[📋\s+\d+\s+reviews?\s+pending\b/im,
3385
+ /^\s*<system-reminder>[\s\S]*?<\/system-reminder>\s*$/im,
3386
+ /^\s*The UserPromptSubmit hook checks the DB for new tasks/im,
3387
+ /^\s*Intercom is a speedup, not delivery/im,
3388
+ /^\s*Context bar reads as USAGE not remaining/im
3389
+ ];
3390
+ SKIP_EMBED_PATTERNS = [
3391
+ /tmux capture-pane\b/i,
3392
+ /docker ps\b/i,
3393
+ /docker images\b/i,
3394
+ /git status\b/i,
3395
+ /grep .*node_modules/i,
3396
+ /npm (install|ci)\b[\s\S]*(added \d+ packages|audited \d+ packages)/i
3397
+ ];
3398
+ }
3399
+ });
3400
+
3202
3401
  // src/lib/shard-manager.ts
3203
3402
  var shard_manager_exports = {};
3204
3403
  __export(shard_manager_exports, {
@@ -3363,7 +3562,8 @@ async function ensureShardSchema(client) {
3363
3562
  }
3364
3563
  for (const idx of [
3365
3564
  "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
3366
- "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
3565
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL",
3566
+ "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"
3367
3567
  ]) {
3368
3568
  try {
3369
3569
  await client.execute(idx);
@@ -3557,7 +3757,7 @@ var init_platform_procedures = __esm({
3557
3757
  title: "Chain of command \u2014 who talks to whom",
3558
3758
  domain: "workflow",
3559
3759
  priority: "p0",
3560
- 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."
3760
+ 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."
3561
3761
  },
3562
3762
  {
3563
3763
  title: "Single dispatch path \u2014 create_task only",
@@ -3788,7 +3988,6 @@ __export(store_exports, {
3788
3988
  vectorToBlob: () => vectorToBlob,
3789
3989
  writeMemory: () => writeMemory
3790
3990
  });
3791
- import { createHash } from "crypto";
3792
3991
  function isBusyError2(err) {
3793
3992
  if (err instanceof Error) {
3794
3993
  const msg = err.message.toLowerCase();
@@ -3902,17 +4101,24 @@ async function writeMemory(record) {
3902
4101
  `Expected ${EMBEDDING_DIM}-dim vector, got ${record.vector.length}`
3903
4102
  );
3904
4103
  }
3905
- const contentHash = createHash("md5").update(record.raw_text).digest("hex");
3906
- if (_pendingRecords.some((r) => r.content_hash === contentHash && r.agent_id === record.agent_id)) {
4104
+ const governed = governMemoryRecord(record);
4105
+ if (governed.shouldDrop) return;
4106
+ record = governed.record;
4107
+ const contentHash = governed.contentHash;
4108
+ const memoryType = record.memory_type ?? "raw";
4109
+ if (_pendingRecords.some(
4110
+ (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4111
+ )) {
3907
4112
  return;
3908
4113
  }
3909
4114
  try {
3910
- const client = getClient();
3911
- const existing = await client.execute({
3912
- sql: "SELECT id FROM memories WHERE content_hash = ? AND agent_id = ? LIMIT 1",
3913
- args: [contentHash, record.agent_id]
4115
+ const existing = await findScopedDuplicate({
4116
+ contentHash,
4117
+ agentId: record.agent_id,
4118
+ projectName: record.project_name,
4119
+ memoryType
3914
4120
  });
3915
- if (existing.rows.length > 0) return;
4121
+ if (existing) return;
3916
4122
  } catch {
3917
4123
  }
3918
4124
  const dbRow = {
@@ -3943,7 +4149,7 @@ async function writeMemory(record) {
3943
4149
  tier: record.tier ?? classifyTier(record),
3944
4150
  supersedes_id: record.supersedes_id ?? null,
3945
4151
  draft: record.draft ? 1 : 0,
3946
- memory_type: record.memory_type ?? "raw",
4152
+ memory_type: memoryType,
3947
4153
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
3948
4154
  content_hash: contentHash,
3949
4155
  intent: record.intent ?? null,
@@ -4102,6 +4308,7 @@ async function flushBatch() {
4102
4308
  const globalClient = getClient();
4103
4309
  const globalStmts = batch.map(buildStmt);
4104
4310
  await globalClient.batch(globalStmts, "write");
4311
+ schedulePostWriteMemoryHygiene(batch.map((row) => row.id));
4105
4312
  _pendingRecords.splice(0, batch.length);
4106
4313
  try {
4107
4314
  const { isShardingEnabled: isShardingEnabled2, getReadyShardClient: getReadyShardClient2 } = await Promise.resolve().then(() => (init_shard_manager(), shard_manager_exports));
@@ -4222,7 +4429,11 @@ async function searchMemories(queryVector, agentId, options) {
4222
4429
  sql += ` AND timestamp >= ?`;
4223
4430
  args.push(options.since);
4224
4431
  }
4225
- if (options?.memoryType) {
4432
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4433
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4434
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4435
+ args.push(...uniqueTypes);
4436
+ } else if (options?.memoryType) {
4226
4437
  sql += ` AND memory_type = ?`;
4227
4438
  args.push(options.memoryType);
4228
4439
  }
@@ -4360,6 +4571,7 @@ var init_store = __esm({
4360
4571
  init_keychain();
4361
4572
  init_config();
4362
4573
  init_state_bus();
4574
+ init_memory_write_governor();
4363
4575
  INIT_MAX_RETRIES = 3;
4364
4576
  INIT_RETRY_DELAY_MS = 1e3;
4365
4577
  _pendingRecords = [];
@@ -5519,6 +5731,17 @@ __export(hybrid_search_exports, {
5519
5731
  rrfMerge: () => rrfMerge,
5520
5732
  rrfMergeMulti: () => rrfMergeMulti
5521
5733
  });
5734
+ function appendMemoryTypeFilter(sql, args, column, options) {
5735
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
5736
+ const uniqueTypes = [...new Set(options.memoryTypes)];
5737
+ sql += ` AND ${column} IN (${uniqueTypes.map(() => "?").join(",")})`;
5738
+ args.push(...uniqueTypes);
5739
+ } else if (options?.memoryType) {
5740
+ sql += ` AND ${column} = ?`;
5741
+ args.push(options.memoryType);
5742
+ }
5743
+ return sql;
5744
+ }
5522
5745
  async function hybridSearch(queryText, agentId, options) {
5523
5746
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
5524
5747
  const config = await loadConfig2();
@@ -5747,6 +5970,7 @@ async function estimateCardinality(agentId, options) {
5747
5970
  sql += ` AND timestamp >= ?`;
5748
5971
  args.push(options.since);
5749
5972
  }
5973
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
5750
5974
  try {
5751
5975
  const result = await client.execute({ sql, args });
5752
5976
  return Number(result.rows[0]?.cnt) || 0;
@@ -5868,10 +6092,7 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
5868
6092
  sql += ` AND m.timestamp >= ?`;
5869
6093
  args.push(options.since);
5870
6094
  }
5871
- if (options?.memoryType) {
5872
- sql += ` AND m.memory_type = ?`;
5873
- args.push(options.memoryType);
5874
- }
6095
+ sql = appendMemoryTypeFilter(sql, args, "m.memory_type", options);
5875
6096
  sql += ` ORDER BY rank LIMIT ?`;
5876
6097
  args.push(limit);
5877
6098
  const result = await client.execute({ sql, args });
@@ -5928,9 +6149,16 @@ async function recentRecords(agentId, options, limit, textFilter) {
5928
6149
  AND timestamp >= ? AND timestamp <= ?
5929
6150
  AND COALESCE(status, 'active') = 'active'
5930
6151
  AND ${options?.includeRaw === false ? "COALESCE(memory_type, 'raw') != 'raw'" : "1 = 1"}
6152
+ ${options?.memoryTypes?.length ? `AND memory_type IN (${options.memoryTypes.map(() => "?").join(",")})` : options?.memoryType ? "AND memory_type = ?" : ""}
5931
6153
  AND COALESCE(confidence, 0.7) >= 0.3
5932
6154
  ORDER BY timestamp DESC LIMIT ?`,
5933
- args: [agentId, windowStart, killedAt, boundarySlots]
6155
+ args: [
6156
+ agentId,
6157
+ windowStart,
6158
+ killedAt,
6159
+ ...options?.memoryTypes?.length ? [...new Set(options.memoryTypes)] : options?.memoryType ? [options.memoryType] : [],
6160
+ boundarySlots
6161
+ ]
5934
6162
  });
5935
6163
  for (const row of boundaryResult.rows) {
5936
6164
  sessionBoundaryMemories.push(rowToMemoryRecord(row));
@@ -5976,10 +6204,7 @@ async function recentRecords(agentId, options, limit, textFilter) {
5976
6204
  sql += ` AND timestamp >= ?`;
5977
6205
  args.push(options.since);
5978
6206
  }
5979
- if (options?.memoryType) {
5980
- sql += ` AND memory_type = ?`;
5981
- args.push(options.memoryType);
5982
- }
6207
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
5983
6208
  if (textFilter) {
5984
6209
  sql += ` AND raw_text LIKE '%' || ? || '%'`;
5985
6210
  args.push(textFilter);