@askexenow/exe-os 0.9.35 → 0.9.37

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 (67) hide show
  1. package/dist/bin/backfill-conversations.js +5 -1
  2. package/dist/bin/backfill-responses.js +5 -1
  3. package/dist/bin/backfill-vectors.js +1 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +10 -2
  5. package/dist/bin/cli.js +48 -14
  6. package/dist/bin/exe-agent.js +1 -1
  7. package/dist/bin/exe-assign.js +10 -2
  8. package/dist/bin/exe-boot.js +1 -1
  9. package/dist/bin/exe-call.js +7 -5
  10. package/dist/bin/exe-dispatch.js +10 -2
  11. package/dist/bin/exe-doctor.js +1 -1
  12. package/dist/bin/exe-export-behaviors.js +87 -4
  13. package/dist/bin/exe-forget.js +31 -11
  14. package/dist/bin/exe-gateway.js +10 -2
  15. package/dist/bin/exe-heartbeat.js +10 -2
  16. package/dist/bin/exe-kill.js +10 -2
  17. package/dist/bin/exe-launch-agent.js +85 -7
  18. package/dist/bin/exe-new-employee.js +26 -2
  19. package/dist/bin/exe-pending-messages.js +10 -2
  20. package/dist/bin/exe-pending-notifications.js +10 -2
  21. package/dist/bin/exe-pending-reviews.js +10 -2
  22. package/dist/bin/exe-rename.js +1 -1
  23. package/dist/bin/exe-review.js +10 -2
  24. package/dist/bin/exe-search.js +38 -19
  25. package/dist/bin/exe-session-cleanup.js +14 -3
  26. package/dist/bin/exe-start-codex.js +69 -3
  27. package/dist/bin/exe-start-opencode.js +80 -3
  28. package/dist/bin/exe-status.js +10 -2
  29. package/dist/bin/exe-team.js +10 -2
  30. package/dist/bin/git-sweep.js +10 -2
  31. package/dist/bin/graph-backfill.js +2 -1
  32. package/dist/bin/graph-export.js +10 -2
  33. package/dist/bin/install.js +25 -1
  34. package/dist/bin/intercom-check.js +10 -2
  35. package/dist/bin/scan-tasks.js +10 -2
  36. package/dist/bin/setup.js +7 -5
  37. package/dist/bin/shard-migrate.js +2 -1
  38. package/dist/gateway/index.js +10 -2
  39. package/dist/hooks/bug-report-worker.js +10 -2
  40. package/dist/hooks/codex-stop-task-finalizer.js +10 -2
  41. package/dist/hooks/commit-complete.js +10 -2
  42. package/dist/hooks/error-recall.js +38 -19
  43. package/dist/hooks/ingest-worker.js +9 -2
  44. package/dist/hooks/ingest.js +10 -2
  45. package/dist/hooks/instructions-loaded.js +10 -2
  46. package/dist/hooks/notification.js +10 -2
  47. package/dist/hooks/post-compact.js +10 -2
  48. package/dist/hooks/post-tool-combined.js +47 -21
  49. package/dist/hooks/pre-compact.js +12 -3
  50. package/dist/hooks/pre-tool-use.js +20 -8
  51. package/dist/hooks/prompt-submit.js +133 -20
  52. package/dist/hooks/session-end.js +138 -5
  53. package/dist/hooks/session-start.js +216 -46
  54. package/dist/hooks/stop.js +14 -4
  55. package/dist/hooks/subagent-stop.js +10 -2
  56. package/dist/hooks/summary-worker.js +121 -19
  57. package/dist/index.js +32 -16
  58. package/dist/lib/employee-templates.js +7 -5
  59. package/dist/lib/exe-daemon.js +124 -34
  60. package/dist/lib/hybrid-search.js +38 -19
  61. package/dist/lib/schedules.js +1 -1
  62. package/dist/lib/store.js +10 -2
  63. package/dist/mcp/server.js +118 -34
  64. package/dist/runtime/index.js +32 -16
  65. package/dist/tui/App.js +10 -2
  66. package/package.json +1 -1
  67. package/src/commands/exe/save.md +52 -0
@@ -6946,6 +6946,9 @@ function classifyMemoryType(input2) {
6946
6946
  if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
6947
6947
  if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
6948
6948
  if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
6949
+ if (tool.includes("checkpoint") || text.startsWith("context checkpoint")) return "checkpoint";
6950
+ if (tool.includes("sessionsummary") || tool.includes("session-summary")) return "summary";
6951
+ if (tool.includes("sessionend") || text.startsWith("session ended")) return "summary";
6949
6952
  if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
6950
6953
  if (tool === "store_memory" || tool === "manual") return "observation";
6951
6954
  return "raw";
@@ -7088,6 +7091,7 @@ async function runPostWriteMemoryHygiene(memoryId) {
7088
7091
  }
7089
7092
  }
7090
7093
  function schedulePostWriteMemoryHygiene(memoryIds) {
7094
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
7091
7095
  if (memoryIds.length === 0) return;
7092
7096
  const run = () => {
7093
7097
  void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
@@ -7482,7 +7486,7 @@ var init_platform_procedures = __esm({
7482
7486
  title: "Chain of command \u2014 who talks to whom",
7483
7487
  domain: "workflow",
7484
7488
  priority: "p0",
7485
- 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."
7489
+ 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."
7486
7490
  },
7487
7491
  {
7488
7492
  title: "Single dispatch path \u2014 create_task only",
@@ -8154,7 +8158,11 @@ async function searchMemories(queryVector, agentId, options) {
8154
8158
  sql += ` AND timestamp >= ?`;
8155
8159
  args.push(options.since);
8156
8160
  }
8157
- if (options?.memoryType) {
8161
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
8162
+ const uniqueTypes = [...new Set(options.memoryTypes)];
8163
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
8164
+ args.push(...uniqueTypes);
8165
+ } else if (options?.memoryType) {
8158
8166
  sql += ` AND memory_type = ?`;
8159
8167
  args.push(options.memoryType);
8160
8168
  }
@@ -8914,6 +8922,88 @@ function clearCacheState(sessionKey) {
8914
8922
  // src/adapters/claude/hooks/session-end.ts
8915
8923
  init_task_scope();
8916
8924
  init_employees();
8925
+
8926
+ // src/lib/auto-checkpoint.ts
8927
+ var FILE_RE = /(?:^|\s)([\w./-]+\.(?:ts|tsx|js|jsx|json|md|yml|yaml|sql|go|py|css|scss|html|sh))(?:\b|$)/g;
8928
+ var DECISION_RE = /\b(decision:|decided:|we decided|founder directive|captured in .*architecture|source of truth)\b/i;
8929
+ function asString(value, fallback = "") {
8930
+ if (value == null) return fallback;
8931
+ return String(value);
8932
+ }
8933
+ function compactLine(text, max = 220) {
8934
+ return text.replace(/\s+/g, " ").trim().slice(0, max);
8935
+ }
8936
+ function topEntries(counts, limit) {
8937
+ return [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit).map(([name, count]) => `${name}(${count})`);
8938
+ }
8939
+ function buildAutoCheckpoint(input2) {
8940
+ const maxSamples = input2.maxSamples ?? 8;
8941
+ const projectCounts = /* @__PURE__ */ new Map();
8942
+ const toolCounts = /* @__PURE__ */ new Map();
8943
+ const files = /* @__PURE__ */ new Set();
8944
+ const errors = [];
8945
+ const samples = [];
8946
+ const decisionTexts = [];
8947
+ for (const row of input2.memories) {
8948
+ const tool = asString(row.tool_name, "unknown");
8949
+ const project = asString(row.project_name, input2.projectName || "unknown");
8950
+ const raw = asString(row.raw_text);
8951
+ const hasError = row.has_error === 1 || row.has_error === true;
8952
+ toolCounts.set(tool, (toolCounts.get(tool) ?? 0) + 1);
8953
+ projectCounts.set(project, (projectCounts.get(project) ?? 0) + 1);
8954
+ if (hasError && errors.length < 5) errors.push(compactLine(raw, 180));
8955
+ if (samples.length < maxSamples && raw.length > 30) {
8956
+ samples.push(`[${tool}] ${compactLine(raw)}`);
8957
+ }
8958
+ if (DECISION_RE.test(raw) && decisionTexts.length < 5) {
8959
+ decisionTexts.push(`AUTO DECISION CANDIDATE [${input2.agentId}]: ${compactLine(raw, 500)}`);
8960
+ }
8961
+ for (const match of raw.matchAll(FILE_RE)) {
8962
+ if (match[1]) files.add(match[1]);
8963
+ if (files.size >= 20) break;
8964
+ }
8965
+ }
8966
+ const taskLines = (input2.tasks ?? []).slice(0, 10).map((task) => {
8967
+ const status = asString(task.status, "unknown");
8968
+ const priority = asString(task.priority, "?").toUpperCase();
8969
+ const title = asString(task.title, "untitled");
8970
+ const taskFile = asString(task.task_file);
8971
+ return `- [${status}/${priority}] ${title}${taskFile ? ` (${taskFile})` : ""}`;
8972
+ });
8973
+ const parts = [
8974
+ `CONTEXT CHECKPOINT [auto:${input2.reason}]`,
8975
+ `Agent: ${input2.agentId} (${input2.agentRole})`,
8976
+ `Session: ${input2.sessionId}`,
8977
+ `Project: ${input2.projectName}`,
8978
+ `Time: ${(/* @__PURE__ */ new Date()).toISOString()}`,
8979
+ "",
8980
+ "## Recent Activity",
8981
+ `- Memories scanned: ${input2.memories.length}`,
8982
+ `- Projects: ${topEntries(projectCounts, 5).join(", ") || input2.projectName}`,
8983
+ `- Tools: ${topEntries(toolCounts, 8).join(", ") || "none"}`
8984
+ ];
8985
+ if (taskLines.length > 0) {
8986
+ parts.push("", "## Open / Active Tasks", ...taskLines);
8987
+ }
8988
+ if (files.size > 0) {
8989
+ parts.push("", "## Files Mentioned", ...[...files].slice(0, 20).map((f) => `- ${f}`));
8990
+ }
8991
+ if (samples.length > 0) {
8992
+ parts.push("", "## Important Recent Traces", ...samples.map((s) => `- ${s}`));
8993
+ }
8994
+ if (errors.length > 0) {
8995
+ parts.push("", "## Errors / Risks", ...errors.map((e) => `- ${e}`));
8996
+ }
8997
+ if (decisionTexts.length > 0) {
8998
+ parts.push("", "## Decision Candidates", ...decisionTexts.map((d) => `- ${d.replace(/^AUTO DECISION CANDIDATE \\[[^\\]]+\\]: /, "")}`));
8999
+ }
9000
+ return {
9001
+ checkpointText: parts.join("\n"),
9002
+ decisionTexts
9003
+ };
9004
+ }
9005
+
9006
+ // src/adapters/claude/hooks/session-end.ts
8917
9007
  if (!process.env.AGENT_ID) {
8918
9008
  process.env.AGENT_ID = "default";
8919
9009
  process.env.AGENT_ROLE = "employee";
@@ -8946,16 +9036,59 @@ process.stdin.on("end", async () => {
8946
9036
  });
8947
9037
  const orphanInfo = orphanResult.rows.length > 0 ? `
8948
9038
  Orphaned tasks at session end: ${orphanResult.rows.map((r) => `"${String(r.title)}" (${String(r.status)})`).join(", ")}` : "";
9039
+ const projectName = process.env.EXE_PROJECT_NAME ?? process.cwd().split("/").pop() ?? "unknown";
9040
+ const recent = await client.execute({
9041
+ sql: `SELECT tool_name, project_name, raw_text, has_error, timestamp
9042
+ FROM memories
9043
+ WHERE agent_id = ?
9044
+ ORDER BY timestamp DESC
9045
+ LIMIT 75`,
9046
+ args: [agent.agentId]
9047
+ });
9048
+ const taskRows = await client.execute({
9049
+ sql: `SELECT title, status, task_file, priority
9050
+ FROM tasks
9051
+ WHERE assigned_to = ? AND status IN ('open', 'in_progress')${seScope.sql}
9052
+ ORDER BY priority ASC, updated_at DESC LIMIT 10`,
9053
+ args: [agent.agentId, ...seScope.args]
9054
+ });
9055
+ const { checkpointText, decisionTexts } = buildAutoCheckpoint({
9056
+ agentId: agent.agentId,
9057
+ agentRole: agent.agentRole,
9058
+ sessionId: data.session_id,
9059
+ projectName,
9060
+ reason: "session-end",
9061
+ memories: recent.rows,
9062
+ tasks: taskRows.rows,
9063
+ maxSamples: 12
9064
+ });
8949
9065
  const { writeMemoryViaDaemon: writeMemoryViaDaemon2 } = await Promise.resolve().then(() => (init_memory_queue_client(), memory_queue_client_exports));
8950
9066
  await writeMemoryViaDaemon2({
8951
- raw_text: `Session ended for ${agent.agentId}.${orphanInfo}`,
9067
+ raw_text: `${checkpointText}${orphanInfo ? `
9068
+
9069
+ ## Session-End Warnings${orphanInfo}` : ""}`,
8952
9070
  agent_id: agent.agentId,
8953
9071
  agent_role: agent.agentRole,
8954
9072
  session_id: data.session_id,
8955
9073
  tool_name: "SessionEnd",
8956
- project_name: process.env.EXE_PROJECT_NAME ?? process.cwd().split("/").pop() ?? "unknown",
8957
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9074
+ project_name: projectName,
9075
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9076
+ importance: 8,
9077
+ memory_type: "checkpoint"
8958
9078
  });
9079
+ for (const decisionText of decisionTexts.slice(0, 5)) {
9080
+ await writeMemoryViaDaemon2({
9081
+ raw_text: decisionText,
9082
+ agent_id: agent.agentId,
9083
+ agent_role: agent.agentRole,
9084
+ session_id: data.session_id,
9085
+ tool_name: "auto-decision",
9086
+ project_name: projectName,
9087
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9088
+ importance: 8,
9089
+ memory_type: "decision"
9090
+ });
9091
+ }
8959
9092
  try {
8960
9093
  const { clearSessionFileReads: clearSessionFileReads2 } = await Promise.resolve().then(() => (init_git_staleness(), git_staleness_exports));
8961
9094
  await clearSessionFileReads2(data.session_id);
@@ -3290,8 +3290,8 @@ function deriveMachineKey() {
3290
3290
  }
3291
3291
  function readMachineId() {
3292
3292
  try {
3293
- const { readFileSync: readFileSync13 } = __require("fs");
3294
- return readFileSync13("/etc/machine-id", "utf-8").trim();
3293
+ const { readFileSync: readFileSync14 } = __require("fs");
3294
+ return readFileSync14("/etc/machine-id", "utf-8").trim();
3295
3295
  } catch {
3296
3296
  return "";
3297
3297
  }
@@ -3453,6 +3453,9 @@ function classifyMemoryType(input2) {
3453
3453
  if (tool.includes("commit") || text.includes("adr-") || text.includes("architectural decision")) return "adr";
3454
3454
  if (tool.includes("store_behavior") || tool.includes("behavior")) return "behavior";
3455
3455
  if (tool.includes("global_procedure") || text.includes("organization-wide procedures")) return "procedure";
3456
+ if (tool.includes("checkpoint") || text.startsWith("context checkpoint")) return "checkpoint";
3457
+ if (tool.includes("sessionsummary") || tool.includes("session-summary")) return "summary";
3458
+ if (tool.includes("sessionend") || text.startsWith("session ended")) return "summary";
3456
3459
  if (tool.includes("send_whatsapp") || tool.includes("conversation")) return "conversation";
3457
3460
  if (tool === "store_memory" || tool === "manual") return "observation";
3458
3461
  return "raw";
@@ -3545,10 +3548,10 @@ async function runPostWriteMemoryHygiene(memoryId) {
3545
3548
  const row = current.rows[0];
3546
3549
  if (!row) return;
3547
3550
  const memoryType = String(row.memory_type ?? "raw");
3548
- const contentHash = row.content_hash ? String(row.content_hash) : null;
3551
+ const contentHash2 = row.content_hash ? String(row.content_hash) : null;
3549
3552
  const agentId = String(row.agent_id);
3550
3553
  const projectName = String(row.project_name);
3551
- if (contentHash) {
3554
+ if (contentHash2) {
3552
3555
  await client.execute({
3553
3556
  sql: `UPDATE memories
3554
3557
  SET status = 'deleted',
@@ -3559,7 +3562,7 @@ async function runPostWriteMemoryHygiene(memoryId) {
3559
3562
  AND project_name = ?
3560
3563
  AND COALESCE(memory_type, 'raw') = ?
3561
3564
  AND COALESCE(status, 'active') = 'active'`,
3562
- args: [memoryId, contentHash, agentId, projectName, memoryType]
3565
+ args: [memoryId, contentHash2, agentId, projectName, memoryType]
3563
3566
  });
3564
3567
  }
3565
3568
  const supersedesId = row.supersedes_id ? String(row.supersedes_id) : null;
@@ -3595,6 +3598,7 @@ async function runPostWriteMemoryHygiene(memoryId) {
3595
3598
  }
3596
3599
  }
3597
3600
  function schedulePostWriteMemoryHygiene(memoryIds) {
3601
+ if (process.env.EXE_SKIP_MEMORY_HYGIENE === "1") return;
3598
3602
  if (memoryIds.length === 0) return;
3599
3603
  const run = () => {
3600
3604
  void Promise.all(memoryIds.map((id) => runPostWriteMemoryHygiene(id)));
@@ -3989,7 +3993,7 @@ var init_platform_procedures = __esm({
3989
3993
  title: "Chain of command \u2014 who talks to whom",
3990
3994
  domain: "workflow",
3991
3995
  priority: "p0",
3992
- 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."
3996
+ 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."
3993
3997
  },
3994
3998
  {
3995
3999
  title: "Single dispatch path \u2014 create_task only",
@@ -4336,16 +4340,16 @@ async function writeMemory(record) {
4336
4340
  const governed = governMemoryRecord(record);
4337
4341
  if (governed.shouldDrop) return;
4338
4342
  record = governed.record;
4339
- const contentHash = governed.contentHash;
4343
+ const contentHash2 = governed.contentHash;
4340
4344
  const memoryType = record.memory_type ?? "raw";
4341
4345
  if (_pendingRecords.some(
4342
- (r) => r.content_hash === contentHash && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4346
+ (r) => r.content_hash === contentHash2 && r.agent_id === record.agent_id && r.project_name === record.project_name && (r.memory_type ?? "raw") === memoryType
4343
4347
  )) {
4344
4348
  return;
4345
4349
  }
4346
4350
  try {
4347
4351
  const existing = await findScopedDuplicate({
4348
- contentHash,
4352
+ contentHash: contentHash2,
4349
4353
  agentId: record.agent_id,
4350
4354
  projectName: record.project_name,
4351
4355
  memoryType
@@ -4383,7 +4387,7 @@ async function writeMemory(record) {
4383
4387
  draft: record.draft ? 1 : 0,
4384
4388
  memory_type: memoryType,
4385
4389
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
4386
- content_hash: contentHash,
4390
+ content_hash: contentHash2,
4387
4391
  intent: record.intent ?? null,
4388
4392
  outcome: record.outcome ?? null,
4389
4393
  domain: record.domain ?? inferDomain(record),
@@ -4456,7 +4460,7 @@ async function flushBatch() {
4456
4460
  const draft = row.draft ? 1 : 0;
4457
4461
  const memoryType = row.memory_type ?? "raw";
4458
4462
  const trajectory = row.trajectory ?? null;
4459
- const contentHash = row.content_hash ?? null;
4463
+ const contentHash2 = row.content_hash ?? null;
4460
4464
  const intent = row.intent ?? null;
4461
4465
  const outcome = row.outcome ?? null;
4462
4466
  const domain = row.domain ?? null;
@@ -4528,7 +4532,7 @@ async function flushBatch() {
4528
4532
  draft,
4529
4533
  memoryType,
4530
4534
  trajectory,
4531
- contentHash
4535
+ contentHash2
4532
4536
  ];
4533
4537
  return {
4534
4538
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
@@ -4661,7 +4665,11 @@ async function searchMemories(queryVector, agentId, options) {
4661
4665
  sql += ` AND timestamp >= ?`;
4662
4666
  args.push(options.since);
4663
4667
  }
4664
- if (options?.memoryType) {
4668
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
4669
+ const uniqueTypes = [...new Set(options.memoryTypes)];
4670
+ sql += ` AND memory_type IN (${uniqueTypes.map(() => "?").join(",")})`;
4671
+ args.push(...uniqueTypes);
4672
+ } else if (options?.memoryType) {
4665
4673
  sql += ` AND memory_type = ?`;
4666
4674
  args.push(options.memoryType);
4667
4675
  }
@@ -5066,10 +5074,10 @@ async function disposeEmbedder() {
5066
5074
  async function embedDirect(text) {
5067
5075
  const llamaCpp = await import("node-llama-cpp");
5068
5076
  const { MODELS_DIR: MODELS_DIR2 } = await Promise.resolve().then(() => (init_config(), config_exports));
5069
- const { existsSync: existsSync16 } = await import("fs");
5070
- const path20 = await import("path");
5071
- const modelPath = path20.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
5072
- if (!existsSync16(modelPath)) {
5077
+ const { existsSync: existsSync17 } = await import("fs");
5078
+ const path21 = await import("path");
5079
+ const modelPath = path21.join(MODELS_DIR2, "jina-embeddings-v5-small-q4_k_m.gguf");
5080
+ if (!existsSync17(modelPath)) {
5073
5081
  throw new Error(`Embedding model not found at ${modelPath}. Run '/exe-setup' to download it.`);
5074
5082
  }
5075
5083
  const llama = await llamaCpp.getLlama();
@@ -5959,6 +5967,17 @@ __export(hybrid_search_exports, {
5959
5967
  rrfMerge: () => rrfMerge,
5960
5968
  rrfMergeMulti: () => rrfMergeMulti
5961
5969
  });
5970
+ function appendMemoryTypeFilter(sql, args, column, options) {
5971
+ if (options?.memoryTypes && options.memoryTypes.length > 0) {
5972
+ const uniqueTypes = [...new Set(options.memoryTypes)];
5973
+ sql += ` AND ${column} IN (${uniqueTypes.map(() => "?").join(",")})`;
5974
+ args.push(...uniqueTypes);
5975
+ } else if (options?.memoryType) {
5976
+ sql += ` AND ${column} = ?`;
5977
+ args.push(options.memoryType);
5978
+ }
5979
+ return sql;
5980
+ }
5962
5981
  async function hybridSearch(queryText, agentId, options) {
5963
5982
  const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
5964
5983
  const config = await loadConfig2();
@@ -6054,7 +6073,7 @@ async function hybridSearch(queryText, agentId, options) {
6054
6073
  }
6055
6074
  if (lists.length === 0) return [];
6056
6075
  if (lists.length === 1 && !effectiveIsBroad) return lists[0].slice(0, limit);
6057
- const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, 150) : limit;
6076
+ const rrfLimit = effectiveIsBroad ? Math.max(limit * 5, broadFetchTopK) : limit;
6058
6077
  let merged = lists.length === 1 ? lists[0].slice(0, rrfLimit) : rrfMergeMulti(lists, rrfLimit, RRF_K, weights);
6059
6078
  let graphContextMap = /* @__PURE__ */ new Map();
6060
6079
  let entityBoostRan = false;
@@ -6075,6 +6094,7 @@ async function hybridSearch(queryText, agentId, options) {
6075
6094
  returnTopK: 5
6076
6095
  };
6077
6096
  let rerankedAndBlended = null;
6097
+ const rerankReturnLimit = Math.max(limit, auto.returnTopK ?? 5);
6078
6098
  if (effectiveIsBroad && auto.enabled && rerankerAvailable) {
6079
6099
  const cardinality2 = await estimateCardinality(agentId, effectiveOptions);
6080
6100
  if (cardinality2 > auto.broadQueryMinCardinality) {
@@ -6086,16 +6106,16 @@ async function hybridSearch(queryText, agentId, options) {
6086
6106
  text: m.raw_text,
6087
6107
  context: graphContextMap.get(m.id)
6088
6108
  }));
6089
- const scored = await rerankWithContext2(effectiveQuery, candidates, auto.returnTopK);
6109
+ const scored = await rerankWithContext2(effectiveQuery, candidates, rerankReturnLimit);
6090
6110
  rerankedRecords = scored.map((s) => merged[s.index]);
6091
6111
  } else {
6092
6112
  const { rerank: rerank2 } = await Promise.resolve().then(() => (init_reranker(), reranker_exports));
6093
- rerankedRecords = await rerank2(effectiveQuery, merged, auto.returnTopK);
6113
+ rerankedRecords = await rerank2(effectiveQuery, merged, rerankReturnLimit);
6094
6114
  }
6095
6115
  if (rerankedRecords.length > 0) {
6096
6116
  rerankedAndBlended = rrfMergeMulti(
6097
6117
  [rerankedRecords],
6098
- auto.returnTopK,
6118
+ rerankReturnLimit,
6099
6119
  RRF_K
6100
6120
  );
6101
6121
  }
@@ -6103,10 +6123,7 @@ async function hybridSearch(queryText, agentId, options) {
6103
6123
  }
6104
6124
  }
6105
6125
  }
6106
- const finalResults = (rerankedAndBlended ?? merged).slice(
6107
- 0,
6108
- rerankedAndBlended ? auto.returnTopK : limit
6109
- );
6126
+ const finalResults = (rerankedAndBlended ?? merged).slice(0, limit);
6110
6127
  if (options?.includeSource && finalResults.length > 0) {
6111
6128
  await attachDocumentMetadata(finalResults);
6112
6129
  }
@@ -6137,10 +6154,10 @@ async function hybridSearch(queryText, agentId, options) {
6137
6154
  };
6138
6155
  try {
6139
6156
  const fs = await import("fs");
6140
- const path20 = await import("path");
6157
+ const path21 = await import("path");
6141
6158
  const os10 = await import("os");
6142
- const logPath = path20.join(os10.homedir(), ".exe-os", "search-quality.jsonl");
6143
- fs.mkdirSync(path20.dirname(logPath), { recursive: true });
6159
+ const logPath = path21.join(os10.homedir(), ".exe-os", "search-quality.jsonl");
6160
+ fs.mkdirSync(path21.dirname(logPath), { recursive: true });
6144
6161
  fs.appendFileSync(logPath, JSON.stringify(logEntry) + "\n");
6145
6162
  } catch {
6146
6163
  }
@@ -6187,6 +6204,7 @@ async function estimateCardinality(agentId, options) {
6187
6204
  sql += ` AND timestamp >= ?`;
6188
6205
  args.push(options.since);
6189
6206
  }
6207
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
6190
6208
  try {
6191
6209
  const result = await client.execute({ sql, args });
6192
6210
  return Number(result.rows[0]?.cnt) || 0;
@@ -6308,10 +6326,7 @@ async function ftsQuery(client, matchExpr, agentId, options, limit) {
6308
6326
  sql += ` AND m.timestamp >= ?`;
6309
6327
  args.push(options.since);
6310
6328
  }
6311
- if (options?.memoryType) {
6312
- sql += ` AND m.memory_type = ?`;
6313
- args.push(options.memoryType);
6314
- }
6329
+ sql = appendMemoryTypeFilter(sql, args, "m.memory_type", options);
6315
6330
  sql += ` ORDER BY rank LIMIT ?`;
6316
6331
  args.push(limit);
6317
6332
  const result = await client.execute({ sql, args });
@@ -6368,9 +6383,16 @@ async function recentRecords(agentId, options, limit, textFilter) {
6368
6383
  AND timestamp >= ? AND timestamp <= ?
6369
6384
  AND COALESCE(status, 'active') = 'active'
6370
6385
  AND ${options?.includeRaw === false ? "COALESCE(memory_type, 'raw') != 'raw'" : "1 = 1"}
6386
+ ${options?.memoryTypes?.length ? `AND memory_type IN (${options.memoryTypes.map(() => "?").join(",")})` : options?.memoryType ? "AND memory_type = ?" : ""}
6371
6387
  AND COALESCE(confidence, 0.7) >= 0.3
6372
6388
  ORDER BY timestamp DESC LIMIT ?`,
6373
- args: [agentId, windowStart, killedAt, boundarySlots]
6389
+ args: [
6390
+ agentId,
6391
+ windowStart,
6392
+ killedAt,
6393
+ ...options?.memoryTypes?.length ? [...new Set(options.memoryTypes)] : options?.memoryType ? [options.memoryType] : [],
6394
+ boundarySlots
6395
+ ]
6374
6396
  });
6375
6397
  for (const row of boundaryResult.rows) {
6376
6398
  sessionBoundaryMemories.push(rowToMemoryRecord(row));
@@ -6416,10 +6438,7 @@ async function recentRecords(agentId, options, limit, textFilter) {
6416
6438
  sql += ` AND timestamp >= ?`;
6417
6439
  args.push(options.since);
6418
6440
  }
6419
- if (options?.memoryType) {
6420
- sql += ` AND memory_type = ?`;
6421
- args.push(options.memoryType);
6422
- }
6441
+ sql = appendMemoryTypeFilter(sql, args, "memory_type", options);
6423
6442
  if (textFilter) {
6424
6443
  sql += ` AND raw_text LIKE '%' || ? || '%'`;
6425
6444
  args.push(textFilter);
@@ -7443,10 +7462,145 @@ var init_git_staleness = __esm({
7443
7462
  }
7444
7463
  });
7445
7464
 
7465
+ // src/lib/identity.ts
7466
+ var identity_exports = {};
7467
+ __export(identity_exports, {
7468
+ getIdentity: () => getIdentity,
7469
+ getIdentityInjection: () => getIdentityInjection,
7470
+ identityPath: () => identityPath,
7471
+ listIdentities: () => listIdentities,
7472
+ updateIdentity: () => updateIdentity
7473
+ });
7474
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync8 } from "fs";
7475
+ import { readdirSync as readdirSync5 } from "fs";
7476
+ import path19 from "path";
7477
+ import { createHash as createHash2 } from "crypto";
7478
+ function ensureDir() {
7479
+ if (!existsSync15(IDENTITY_DIR2)) {
7480
+ mkdirSync7(IDENTITY_DIR2, { recursive: true });
7481
+ }
7482
+ }
7483
+ function identityPath(agentId) {
7484
+ return path19.join(IDENTITY_DIR2, `${agentId}.md`);
7485
+ }
7486
+ function parseFrontmatter(raw) {
7487
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
7488
+ if (!match) {
7489
+ return {
7490
+ frontmatter: {
7491
+ role: "unknown",
7492
+ title: "Unknown",
7493
+ agent_id: "unknown",
7494
+ org_level: "specialist",
7495
+ created_by: "system",
7496
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
7497
+ },
7498
+ body: raw
7499
+ };
7500
+ }
7501
+ const yamlStr = match[1];
7502
+ const body = match[2].trim();
7503
+ const fm = {};
7504
+ for (const line of yamlStr.split("\n")) {
7505
+ const kv = line.match(/^(\w+):\s*(.+)$/);
7506
+ if (kv) fm[kv[1]] = kv[2].trim();
7507
+ }
7508
+ return {
7509
+ frontmatter: {
7510
+ role: fm.role ?? "unknown",
7511
+ title: fm.title ?? "Unknown",
7512
+ agent_id: fm.agent_id ?? "unknown",
7513
+ org_level: fm.org_level ?? "specialist",
7514
+ created_by: fm.created_by ?? "system",
7515
+ updated_at: fm.updated_at ?? (/* @__PURE__ */ new Date()).toISOString()
7516
+ },
7517
+ body
7518
+ };
7519
+ }
7520
+ function contentHash(content) {
7521
+ return createHash2("sha256").update(content).digest("hex").slice(0, 16);
7522
+ }
7523
+ function getIdentity(agentId) {
7524
+ const filePath = identityPath(agentId);
7525
+ if (!existsSync15(filePath)) return null;
7526
+ const raw = readFileSync12(filePath, "utf-8");
7527
+ const { frontmatter, body } = parseFrontmatter(raw);
7528
+ return {
7529
+ agentId,
7530
+ frontmatter,
7531
+ body,
7532
+ raw,
7533
+ contentHash: contentHash(raw)
7534
+ };
7535
+ }
7536
+ async function updateIdentity(agentId, content, updatedBy) {
7537
+ ensureDir();
7538
+ const filePath = identityPath(agentId);
7539
+ const hash = contentHash(content);
7540
+ writeFileSync8(filePath, content, "utf-8");
7541
+ try {
7542
+ const client = getClient();
7543
+ await client.execute({
7544
+ sql: `INSERT INTO identity (agent_id, content_hash, updated_at, updated_by)
7545
+ VALUES (?, ?, ?, ?)
7546
+ ON CONFLICT(agent_id) DO UPDATE SET
7547
+ content_hash = excluded.content_hash,
7548
+ updated_at = excluded.updated_at,
7549
+ updated_by = excluded.updated_by`,
7550
+ args: [agentId, hash, (/* @__PURE__ */ new Date()).toISOString(), updatedBy]
7551
+ });
7552
+ } catch {
7553
+ }
7554
+ }
7555
+ function listIdentities() {
7556
+ ensureDir();
7557
+ const files = readdirSync5(IDENTITY_DIR2).filter((f) => f.endsWith(".md"));
7558
+ const results = [];
7559
+ for (const file of files) {
7560
+ const agentId = file.replace(".md", "");
7561
+ const identity = getIdentity(agentId);
7562
+ if (!identity) continue;
7563
+ const lines = identity.body.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
7564
+ const summary = lines[0]?.trim().slice(0, 120) ?? identity.frontmatter.title;
7565
+ results.push({
7566
+ agentId,
7567
+ title: `${identity.frontmatter.title} (${identity.frontmatter.role.toUpperCase()})`,
7568
+ summary
7569
+ });
7570
+ }
7571
+ return results;
7572
+ }
7573
+ function getIdentityInjection(agentId) {
7574
+ const own = getIdentity(agentId);
7575
+ const all = listIdentities();
7576
+ const parts = [];
7577
+ if (own) {
7578
+ parts.push(`## Your Identity (exe.md)
7579
+ These define WHO YOU ARE. Non-negotiable. Permanent.
7580
+
7581
+ ${own.body}`);
7582
+ }
7583
+ const teamLines = all.filter((a) => a.agentId !== agentId).map((a) => `- ${a.agentId} (${a.title}): ${a.summary}`);
7584
+ if (teamLines.length > 0) {
7585
+ parts.push(`## Team Identities
7586
+ ${teamLines.join("\n")}`);
7587
+ }
7588
+ return parts.join("\n\n");
7589
+ }
7590
+ var IDENTITY_DIR2;
7591
+ var init_identity = __esm({
7592
+ "src/lib/identity.ts"() {
7593
+ "use strict";
7594
+ init_config();
7595
+ init_database();
7596
+ IDENTITY_DIR2 = path19.join(EXE_AI_DIR, "identity");
7597
+ }
7598
+ });
7599
+
7446
7600
  // src/adapters/claude/hooks/session-start.ts
7447
7601
  init_config();
7448
- import path19 from "path";
7449
- import { unlinkSync as unlinkSync4, existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
7602
+ import path20 from "path";
7603
+ import { unlinkSync as unlinkSync4, existsSync as existsSync16, readFileSync as readFileSync13 } from "fs";
7450
7604
  if (!process.env.AGENT_ID) {
7451
7605
  process.env.AGENT_ID = "default";
7452
7606
  process.env.AGENT_ROLE = "employee";
@@ -7479,8 +7633,8 @@ process.stdin.on("end", async () => {
7479
7633
  const sessionScope = getCurrentSessionScope2();
7480
7634
  if (source === "startup") {
7481
7635
  try {
7482
- const undefinedPath = path19.join(
7483
- process.env.EXE_OS_DIR ?? path19.join(process.env.HOME ?? "", ".exe-os"),
7636
+ const undefinedPath = path20.join(
7637
+ process.env.EXE_OS_DIR ?? path20.join(process.env.HOME ?? "", ".exe-os"),
7484
7638
  "session-cache",
7485
7639
  "active-agent-undefined.json"
7486
7640
  );
@@ -7494,13 +7648,13 @@ process.stdin.on("end", async () => {
7494
7648
  const agentId = agent.agentId;
7495
7649
  if (agentId !== "default") {
7496
7650
  try {
7497
- const cacheDir = path19.join(
7498
- process.env.EXE_OS_DIR ?? path19.join(process.env.HOME ?? "", ".exe-os"),
7651
+ const cacheDir = path20.join(
7652
+ process.env.EXE_OS_DIR ?? path20.join(process.env.HOME ?? "", ".exe-os"),
7499
7653
  "session-cache"
7500
7654
  );
7501
- const markerPath = path19.join(cacheDir, `current-task-${agentId}.json`);
7502
- if (existsSync15(markerPath)) {
7503
- const marker = JSON.parse(readFileSync12(markerPath, "utf-8"));
7655
+ const markerPath = path20.join(cacheDir, `current-task-${agentId}.json`);
7656
+ if (existsSync16(markerPath)) {
7657
+ const marker = JSON.parse(readFileSync13(markerPath, "utf-8"));
7504
7658
  if (marker.taskId) {
7505
7659
  const client = getClient2();
7506
7660
  const markerScope = sessionScopeFilter2(sessionScope);
@@ -7608,6 +7762,22 @@ flow, so bug fixes are fast.`;
7608
7762
  } catch {
7609
7763
  }
7610
7764
  let additionalContext = "";
7765
+ if (agentId !== "default") {
7766
+ try {
7767
+ const { getIdentity: getIdentity2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
7768
+ const identity = getIdentity2(agentId);
7769
+ if (identity) {
7770
+ const title = identity.frontmatter.title ?? identity.frontmatter.role ?? agentId;
7771
+ const role = identity.frontmatter.role ?? "";
7772
+ const firstLine = identity.body.split("\n").find((l) => l.trim().length > 0) ?? "";
7773
+ additionalContext += `## Your Identity
7774
+ You are **${title}**${role ? ` (${role})` : ""}. ${firstLine}
7775
+
7776
+ `;
7777
+ }
7778
+ } catch {
7779
+ }
7780
+ }
7611
7781
  if (memories.length > 0) {
7612
7782
  const brief = memories.map(
7613
7783
  (m) => `[${m.timestamp}] ${m.tool_name}: ${m.raw_text.slice(0, 200)}`