@askexenow/exe-os 0.8.0 → 0.8.1

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 (90) hide show
  1. package/README.md +178 -79
  2. package/dist/bin/backfill-responses.js +160 -8
  3. package/dist/bin/backfill-vectors.js +130 -1
  4. package/dist/bin/cleanup-stale-review-tasks.js +130 -1
  5. package/dist/bin/cli.js +10111 -7540
  6. package/dist/bin/exe-agent.js +159 -1
  7. package/dist/bin/exe-assign.js +235 -16
  8. package/dist/bin/exe-boot.js +344 -472
  9. package/dist/bin/exe-call.js +145 -1
  10. package/dist/bin/exe-cloud.js +11 -0
  11. package/dist/bin/exe-dispatch.js +37 -24
  12. package/dist/bin/exe-doctor.js +130 -1
  13. package/dist/bin/exe-export-behaviors.js +150 -7
  14. package/dist/bin/exe-forget.js +822 -665
  15. package/dist/bin/exe-gateway.js +470 -62
  16. package/dist/bin/exe-heartbeat.js +133 -2
  17. package/dist/bin/exe-kill.js +150 -7
  18. package/dist/bin/exe-launch-agent.js +150 -7
  19. package/dist/bin/exe-new-employee.js +756 -224
  20. package/dist/bin/exe-pending-messages.js +132 -2
  21. package/dist/bin/exe-pending-notifications.js +130 -1
  22. package/dist/bin/exe-pending-reviews.js +132 -2
  23. package/dist/bin/exe-review.js +160 -8
  24. package/dist/bin/exe-search.js +2473 -2008
  25. package/dist/bin/exe-session-cleanup.js +238 -51
  26. package/dist/bin/exe-settings.js +11 -0
  27. package/dist/bin/exe-status.js +130 -1
  28. package/dist/bin/exe-team.js +130 -1
  29. package/dist/bin/git-sweep.js +272 -16
  30. package/dist/bin/graph-backfill.js +150 -7
  31. package/dist/bin/graph-export.js +150 -7
  32. package/dist/bin/install.js +5 -0
  33. package/dist/bin/scan-tasks.js +238 -19
  34. package/dist/bin/setup.js +1776 -10
  35. package/dist/bin/shard-migrate.js +150 -7
  36. package/dist/bin/update.js +9 -6
  37. package/dist/bin/wiki-sync.js +150 -7
  38. package/dist/gateway/index.js +470 -62
  39. package/dist/hooks/bug-report-worker.js +195 -35
  40. package/dist/hooks/commit-complete.js +272 -16
  41. package/dist/hooks/error-recall.js +2313 -1847
  42. package/dist/hooks/exe-heartbeat-hook.js +5 -0
  43. package/dist/hooks/ingest-worker.js +330 -58
  44. package/dist/hooks/ingest.js +11 -0
  45. package/dist/hooks/instructions-loaded.js +199 -10
  46. package/dist/hooks/notification.js +199 -10
  47. package/dist/hooks/post-compact.js +199 -10
  48. package/dist/hooks/pre-compact.js +199 -10
  49. package/dist/hooks/pre-tool-use.js +199 -10
  50. package/dist/hooks/prompt-ingest-worker.js +179 -14
  51. package/dist/hooks/prompt-submit.js +781 -285
  52. package/dist/hooks/response-ingest-worker.js +1900 -1405
  53. package/dist/hooks/session-end.js +456 -12
  54. package/dist/hooks/session-start.js +2188 -1724
  55. package/dist/hooks/stop.js +200 -10
  56. package/dist/hooks/subagent-stop.js +199 -10
  57. package/dist/hooks/summary-worker.js +604 -334
  58. package/dist/index.js +554 -61
  59. package/dist/lib/cloud-sync.js +5 -0
  60. package/dist/lib/config.js +13 -0
  61. package/dist/lib/consolidation.js +5 -0
  62. package/dist/lib/database.js +104 -0
  63. package/dist/lib/device-registry.js +109 -0
  64. package/dist/lib/embedder.js +13 -0
  65. package/dist/lib/employee-templates.js +53 -26
  66. package/dist/lib/employees.js +5 -0
  67. package/dist/lib/exe-daemon-client.js +5 -0
  68. package/dist/lib/exe-daemon.js +493 -79
  69. package/dist/lib/file-grep.js +20 -4
  70. package/dist/lib/hybrid-search.js +1435 -190
  71. package/dist/lib/identity-templates.js +126 -5
  72. package/dist/lib/identity.js +5 -0
  73. package/dist/lib/license.js +5 -0
  74. package/dist/lib/messaging.js +37 -24
  75. package/dist/lib/schedules.js +130 -1
  76. package/dist/lib/skill-learning.js +11 -0
  77. package/dist/lib/status-brief.js +5 -0
  78. package/dist/lib/store.js +199 -10
  79. package/dist/lib/task-router.js +72 -6
  80. package/dist/lib/tasks.js +179 -50
  81. package/dist/lib/tmux-routing.js +179 -46
  82. package/dist/mcp/server.js +2129 -1855
  83. package/dist/mcp/tools/create-task.js +86 -36
  84. package/dist/mcp/tools/deactivate-behavior.js +5 -0
  85. package/dist/mcp/tools/list-tasks.js +39 -11
  86. package/dist/mcp/tools/send-message.js +37 -24
  87. package/dist/mcp/tools/update-task.js +153 -38
  88. package/dist/runtime/index.js +451 -59
  89. package/dist/tui/App.js +454 -59
  90. package/package.json +1 -1
@@ -530,6 +530,25 @@ async function* agentLoop(userMessage, history, config) {
530
530
  }
531
531
  totalUsage.inputTokens += response.usage.inputTokens;
532
532
  totalUsage.outputTokens += response.usage.outputTokens;
533
+ if (config.tokenBudgetMiddleware) {
534
+ const result = await config.tokenBudgetMiddleware.onTokenUsed(
535
+ response.usage.inputTokens,
536
+ response.usage.outputTokens
537
+ );
538
+ if (result.warned && config.hooks.onNotification) {
539
+ await config.hooks.onNotification(
540
+ `\u26A0\uFE0F Token budget at ${result.percentUsed}%. Fallback model: ${result.fallback ?? "none (task will terminate at 100%)"}.`
541
+ );
542
+ }
543
+ if (result.exceeded) {
544
+ if (config.hooks.onTokenBudgetExceeded) {
545
+ await config.hooks.onTokenBudgetExceeded(context, result.fallback);
546
+ }
547
+ abortController.abort();
548
+ yield { type: "error", error: new Error("Token budget exceeded. Task requires manual continuation.") };
549
+ break;
550
+ }
551
+ }
533
552
  contextManager.updateFromApiUsage(response.usage.inputTokens, response.usage.outputTokens);
534
553
  contextManager.updateFromMessages(messages);
535
554
  await contextManager.checkPressure();
@@ -1124,6 +1143,145 @@ var OllamaProvider = class {
1124
1143
  }
1125
1144
  };
1126
1145
 
1146
+ // src/lib/employee-templates.ts
1147
+ var BASE_OPERATING_PROCEDURES = `
1148
+ EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES (above all work):
1149
+
1150
+ Product: "Hire the team you couldn't afford." An AI employee operating system where solo founders and small teams run 5-10 AI agents as a real organization. Three-layer cognition (identity/expertise/experience). Five runtime modes (CC Raw \u2192 TUI \u2192 Desktop). Local-first with E2EE cloud sync.
1151
+
1152
+ ICP (who we build for):
1153
+ - Solopreneurs, SMB founders, creators with institutional IP
1154
+ - Bootstrapped small e-commerce / fitness creators / influencers
1155
+ - NOT VC-backed startups \u2014 intentionally excluded
1156
+
1157
+ Crown jewels (load-bearing for all three business paths \u2014 never compromise):
1158
+ - Memory sovereignty (user owns everything, E2EE, local-first)
1159
+ - Three-layer cognition (identity/expertise/experience)
1160
+ - MCP contract boundary (surfaces consume memory OS via MCP only \u2014 never direct DB access, never bundled code)
1161
+ - AGPL network boundary for public forks (e.g., exe-crm)
1162
+
1163
+ Three business-model paths (every product decision must serve these):
1164
+ 1. B2C direct \u2014 solopreneurs run their own instance (active, current default)
1165
+ 2. Agency white-label \u2014 distributors rebrand for their clients (deferred, but branding must be config-driven)
1166
+ 3. Creator franchise (Mike pattern) \u2014 creators inject institutional IP into agent identity+expertise+experience layers, sell scoped access to subscribers (v2+ moat, requires memory export scoping)
1167
+
1168
+ Ethos:
1169
+ - Bootstrapped, profitable, forever. Not a VC-raise.
1170
+ - Founder zero-ego. Distributors and customers are the loudest voice.
1171
+ - Crypto values: big companies should not own consumer/SMB AI.
1172
+
1173
+ STOP AND REDIRECT: Any decision that compromises memory sovereignty, 3-layer cognition, MCP boundary, or AGPL boundary kills all three business paths. Surface the conflict to exe before proceeding.
1174
+
1175
+ Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of truth for all architectural and product decisions.
1176
+
1177
+ OPERATING PROCEDURES (mandatory for all employees):
1178
+
1179
+ You report to exe (COO). All work flows through exe. These procedures are non-negotiable.
1180
+
1181
+ 1. BEFORE starting work:
1182
+ - Read exe/ARCHITECTURE.md (if it exists). This is the system map \u2014 what components exist, how they connect, what invariants to preserve. Understand the architecture before changing anything.
1183
+ - Check YOUR task folder ONLY: Read exe/<your-name>/ for assigned tasks
1184
+ - NEVER read, write, or modify files in another employee's folder (e.g., exe/mari/, exe/yoshi/). Those are their tasks, not yours. Use ask_team_memory() if you need context from a colleague.
1185
+ - If you have open tasks, work on the highest priority one first
1186
+ - Ensure exe/output/ exists (mkdir -p exe/output). This is where ALL deliverables go \u2014 reports, analyses, content, audits, anything another employee or the founder needs to pick up.
1187
+ - Update task status to "in_progress" when starting (use update_task MCP tool)
1188
+ - recall_my_memory \u2014 check what you've done before in this project. What patterns, decisions, context exist?
1189
+ - Read the relevant files. Understand what exists before changing anything.
1190
+
1191
+ 2. BEFORE marking done \u2014 CHECKPOINT (mandatory, never skip):
1192
+ - Run the tests. If they fail, fix them before reporting done.
1193
+ - Run typecheck if TypeScript. Zero errors.
1194
+ - Verify the change actually works \u2014 run it, check the output, prove it.
1195
+ - If you can't verify, say so explicitly: "Couldn't verify because X."
1196
+
1197
+ 3. AFTER completing work \u2014 update_task(done) IMMEDIATELY (the ONE critical action):
1198
+ Calling update_task with status "done" is the single action that must ALWAYS happen.
1199
+ Call it FIRST \u2014 before commit, before report, before anything else. If you do nothing else, do this.
1200
+ - Use update_task MCP tool with status "done" and your result summary
1201
+ - Include what was done, decisions made, and any issues
1202
+ - If you're stuck, looping, confused, or running low on context \u2014 update_task(done) with whatever partial result you have. A partial result is infinitely better than no result.
1203
+ - NEVER let a failed commit, a loop, or an error prevent you from calling update_task(done).
1204
+ - Do NOT use close_task \u2014 that is reserved for reviewers (exe) to finalize after review.
1205
+
1206
+ 4. AFTER update_task(done) \u2014 COMMIT (best-effort, do NOT let this block):
1207
+ - If your task changed system structure, update exe/ARCHITECTURE.md first.
1208
+ - Commit IF you are in a git repo (check: \`git rev-parse --git-dir 2>/dev/null\`). Stage only the files you changed, write a clear commit message.
1209
+ - If you are NOT in a git repo, skip entirely. NEVER run \`git init\`.
1210
+ - If the commit fails, note it but move on \u2014 the work is already marked done via update_task.
1211
+ - Do NOT push \u2014 exe reviews commits and decides what to push.
1212
+ - NEVER run \`git checkout main\`. You work in your own git worktree on a feature branch. Exe stays on main and merges PRs. Switching branches in a shared repo stomps other agents' work.
1213
+
1214
+ 5. AFTER commit \u2014 REPORT (best-effort):
1215
+ Use store_memory to write a structured summary. Include: project name, what was done,
1216
+ decisions made, tests status, open items or risks.
1217
+
1218
+ 6. AFTER committing changes to exe-os itself \u2014 REBUILD (mandatory, never skip):
1219
+ - Run: npm run deploy
1220
+ - This builds, installs globally, and re-registers hooks/MCP in one step.
1221
+ - Do NOT ask permission. Do NOT say "want me to rebuild?" \u2014 just do it.
1222
+ - If the build fails, fix the error and retry before moving on.
1223
+
1224
+ 7. AFTER reporting \u2014 CHECK FOR NEXT WORK (mandatory):
1225
+ - First: run list_tasks(status='needs_review') \u2014 check if YOU are the reviewer on any pending reviews. Reviews are work. Process them before anything else.
1226
+ - Second: run list_tasks(status='blocked') \u2014 check if any tasks are blocked. For each blocked task: can YOU unblock it? If yes, unblock it now. If not, escalate to exe immediately. Blocked tasks sitting >24h without action is a pipeline failure.
1227
+ - Then: re-read your task folder: exe/<your-name>/
1228
+ - If there are more open tasks, start the next highest-priority one (go to step 1)
1229
+ - If no more open tasks AND no pending reviews AND no blocked tasks you can fix, tell the user: "All tasks complete. Anything else?"
1230
+ - Do NOT wait for the user to tell you to check \u2014 auto-chain through your queue.
1231
+ - NEVER say "monitoring" or "waiting" while reviews, blocked tasks, or open tasks exist. That is idle drift.
1232
+
1233
+ CONTEXT PRESSURE PROTOCOL (mandatory \u2014 never ignore):
1234
+ If Claude Code injects a system notice about context compression, or if you notice you're
1235
+ losing track of earlier decisions, your context window is full.
1236
+
1237
+ DO NOT keep working degraded. Instead:
1238
+
1239
+ 1. Call store_memory immediately with a CONTEXT CHECKPOINT:
1240
+ Format the text as: "CONTEXT CHECKPOINT [<task-id>]: <summary>"
1241
+ Include: task ID + title, what you completed, what's left, open decisions or blockers, key file paths.
1242
+
1243
+ 2. Send intercom to exe to trigger kill + relaunch:
1244
+ MY_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
1245
+ EXE_SESSION="\${MY_SESSION#\${AGENT_ID}-}"
1246
+ tmux send-keys -t "$EXE_SESSION" "/exe-intercom context-full: \${AGENT_ID} hit capacity. Checkpoint saved. Resume task <task-id>." Enter
1247
+
1248
+ 3. Stop working immediately. Do not attempt to continue with degraded context.
1249
+
1250
+ COMMUNICATION CHAIN \u2014 who you talk to:
1251
+ - You report to exe (COO). Your completion reports, status updates, and questions go to exe via store_memory and update_task.
1252
+ - Do NOT address the human user directly for decisions, permissions, or status updates. That's exe's job. The user talks to exe; exe talks to you.
1253
+ - Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
1254
+
1255
+ SKILL CAPTURE (encouraged, not mandatory):
1256
+ After completing a complex multi-step task (5+ tool calls), consider whether the approach
1257
+ should be saved as a reusable procedure. If the task involved non-obvious steps, error recovery,
1258
+ or a workflow that would help future sessions, use store_behavior with domain='skill' to save it.
1259
+ Format: "SKILL: [name] \u2014 Step 1: ... Step 2: ... Pitfalls: ..."
1260
+ Skip for simple one-offs. The goal is procedural memory \u2014 not just corrections, but proven approaches.
1261
+
1262
+ SPAWNING EMPLOYEES (mandatory \u2014 never bypass):
1263
+ When you need another employee to do work, ALWAYS use create_task MCP tool.
1264
+ create_task auto-spawns the employee session. The task IS the spawn trigger.
1265
+ NEVER manually launch sessions with tmux send-keys or claude -p.
1266
+ NEVER spawn sessions without a task assigned \u2014 idle sessions waste resources.
1267
+ NEVER refuse a dispatched task claiming "not in scope" \u2014 if it's assigned to you, it's your work.
1268
+
1269
+ CREATING TASKS FOR OTHER EMPLOYEES:
1270
+ When you need to assign work to another employee (e.g., yoshi assigns to tom):
1271
+ - ALWAYS use create_task MCP tool. NEVER write .md files directly to exe/{name}/.
1272
+ - Direct .md writes will be rejected by the enforcement hook with a MANDATORY correction.
1273
+ - create_task creates both the .md file AND the DB row atomically.
1274
+ - Include: title, assignedTo, priority, context, projectName.
1275
+ - For dependencies: include blocked_by with the blocking task's ID or slug.
1276
+ `;
1277
+ var PROCEDURES_MARKER = "EXE OS \u2014 VISION AND NON-NEGOTIABLE PRINCIPLES";
1278
+ function getSessionPrompt(storedPrompt) {
1279
+ const markerIndex = storedPrompt.indexOf(PROCEDURES_MARKER);
1280
+ const rolePrompt = markerIndex >= 0 ? storedPrompt.slice(0, markerIndex).trimEnd() : storedPrompt;
1281
+ return `${rolePrompt}
1282
+ ${BASE_OPERATING_PROCEDURES}`;
1283
+ }
1284
+
1127
1285
  // src/runtime/tools/bash.ts
1128
1286
  import { spawn } from "child_process";
1129
1287
  import { z } from "zod";
@@ -1816,7 +1974,7 @@ async function main() {
1816
1974
  const config = {
1817
1975
  provider,
1818
1976
  model,
1819
- systemPrompt: employee.systemPrompt,
1977
+ systemPrompt: getSessionPrompt(employee.systemPrompt),
1820
1978
  tools: registry,
1821
1979
  hooks: createDefaultHooks(),
1822
1980
  permissions: EMPLOYEE_PERMISSIONS,
@@ -81,6 +81,11 @@ function normalizeSessionLifecycle(raw) {
81
81
  const userSL = raw.sessionLifecycle ?? {};
82
82
  raw.sessionLifecycle = { ...defaultSL, ...userSL };
83
83
  }
84
+ function normalizeAutoUpdate(raw) {
85
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
86
+ const userAU = raw.autoUpdate ?? {};
87
+ raw.autoUpdate = { ...defaultAU, ...userAU };
88
+ }
84
89
  async function loadConfig() {
85
90
  const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
86
91
  await mkdir(dir, { recursive: true });
@@ -103,6 +108,7 @@ async function loadConfig() {
103
108
  }
104
109
  normalizeScalingRoadmap(migratedCfg);
105
110
  normalizeSessionLifecycle(migratedCfg);
111
+ normalizeAutoUpdate(migratedCfg);
106
112
  const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
107
113
  if (config.dbPath.startsWith("~")) {
108
114
  config.dbPath = config.dbPath.replace(/^~/, os.homedir());
@@ -178,6 +184,11 @@ var init_config = __esm({
178
184
  idleKillTicksRequired: 3,
179
185
  idleKillIntercomAckWindowMs: 1e4,
180
186
  maxAutoInstances: 10
187
+ },
188
+ autoUpdate: {
189
+ checkOnBoot: true,
190
+ autoInstall: false,
191
+ checkIntervalMs: 24 * 60 * 60 * 1e3
181
192
  }
182
193
  };
183
194
  CONFIG_MIGRATIONS = [
@@ -311,13 +322,27 @@ async function ensureShardSchema(client) {
311
322
  "ALTER TABLE memories ADD COLUMN document_id TEXT",
312
323
  "ALTER TABLE memories ADD COLUMN user_id TEXT",
313
324
  "ALTER TABLE memories ADD COLUMN char_offset INTEGER",
314
- "ALTER TABLE memories ADD COLUMN page_number INTEGER"
325
+ "ALTER TABLE memories ADD COLUMN page_number INTEGER",
326
+ // Source provenance columns (must match database.ts)
327
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
328
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
329
+ "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
330
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
315
331
  ]) {
316
332
  try {
317
333
  await client.execute(col);
318
334
  } catch {
319
335
  }
320
336
  }
337
+ for (const idx of [
338
+ "CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
339
+ "CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
340
+ ]) {
341
+ try {
342
+ await client.execute(idx);
343
+ } catch {
344
+ }
345
+ }
321
346
  try {
322
347
  await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
323
348
  } catch {
@@ -445,6 +470,47 @@ function getEmployee(employees, name) {
445
470
 
446
471
  // src/lib/task-router.ts
447
472
  import { randomUUID } from "crypto";
473
+ var DEFAULT_BLOOM_CONFIG = {
474
+ complexityToTier: {
475
+ routine: "junior",
476
+ standard: "standard",
477
+ complex: "senior",
478
+ critical: "specialist"
479
+ },
480
+ tierRules: {
481
+ junior: {
482
+ eligible: ["tom"],
483
+ reviewRequired: false,
484
+ manualOnly: false
485
+ },
486
+ standard: {
487
+ eligible: ["tom"],
488
+ reviewRequired: false,
489
+ manualOnly: false
490
+ },
491
+ senior: {
492
+ eligible: [],
493
+ // any specialist, but review required
494
+ reviewRequired: true,
495
+ manualOnly: false
496
+ },
497
+ specialist: {
498
+ eligible: [],
499
+ reviewRequired: true,
500
+ manualOnly: true
501
+ }
502
+ }
503
+ };
504
+ function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
505
+ const tier = config.complexityToTier[complexity];
506
+ const rule = config.tierRules[tier];
507
+ return {
508
+ tier,
509
+ reviewRequired: rule.reviewRequired,
510
+ manualOnly: rule.manualOnly,
511
+ eligible: rule.eligible
512
+ };
513
+ }
448
514
  async function scoreEmployee(taskVector, agentId, searchFn) {
449
515
  const results = await searchFn(taskVector, agentId, { limit: 5 });
450
516
  if (results.length === 0) {
@@ -457,13 +523,29 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
457
523
  }
458
524
  return { agentId, score: results.length / 5 };
459
525
  }
460
- async function routeTask(taskDescription, employees, embedFn, searchFn) {
461
- const specialists = employees.filter((e) => e.name !== "exe");
526
+ async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
527
+ let specialists = employees.filter((e) => e.name !== "exe");
462
528
  if (specialists.length === 0) {
463
529
  throw new Error(
464
530
  "No specialist employees available. Create one with /exe-new-employee."
465
531
  );
466
532
  }
533
+ let bloomRouting;
534
+ if (options?.complexity) {
535
+ bloomRouting = resolveBloomRouting(options.complexity, options.bloomConfig);
536
+ if (bloomRouting.manualOnly) {
537
+ throw new Error(
538
+ `Task complexity "${options.complexity}" requires manual assignment (tier: ${bloomRouting.tier}).`
539
+ );
540
+ }
541
+ if (bloomRouting.eligible.length > 0) {
542
+ const eligible = new Set(bloomRouting.eligible);
543
+ const filtered = specialists.filter((e) => eligible.has(e.name));
544
+ if (filtered.length > 0) {
545
+ specialists = filtered;
546
+ }
547
+ }
548
+ }
467
549
  const taskVector = await embedFn(taskDescription);
468
550
  const scored = await Promise.all(
469
551
  specialists.map(async (emp) => {
@@ -472,7 +554,7 @@ async function routeTask(taskDescription, employees, embedFn, searchFn) {
472
554
  })
473
555
  );
474
556
  scored.sort((a, b) => b.score - a.score);
475
- return scored[0];
557
+ return { ...scored[0], bloomRouting };
476
558
  }
477
559
  function createAssignmentMemory(taskDescription, assignedTo, assignedBy) {
478
560
  return {
@@ -489,15 +571,22 @@ function createAssignmentMemory(taskDescription, assignedTo, assignedBy) {
489
571
  // Will be backfilled
490
572
  };
491
573
  }
492
- function formatAssignmentOutput(employee, task, score, mode) {
574
+ function formatAssignmentOutput(employee, task, score, mode, bloomRouting) {
493
575
  const modeLabel = mode === "auto" ? "auto-routed" : "direct";
494
576
  const percent = Math.round(score * 100);
495
- return [
577
+ const lines = [
496
578
  `Assigned to ${employee.name} (${employee.role})`,
497
579
  `Mode: ${modeLabel}`,
498
580
  `Relevance: ${percent}%`,
499
581
  `Task: ${task}`
500
- ].join("\n");
582
+ ];
583
+ if (bloomRouting) {
584
+ lines.push(`Complexity tier: ${bloomRouting.tier}`);
585
+ if (bloomRouting.reviewRequired) {
586
+ lines.push(`Review: required`);
587
+ }
588
+ }
589
+ return lines.join("\n");
501
590
  }
502
591
 
503
592
  // src/types/memory.ts
@@ -1047,6 +1136,27 @@ async function ensureSchema() {
1047
1136
  });
1048
1137
  } catch {
1049
1138
  }
1139
+ try {
1140
+ await client.execute({
1141
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
1142
+ args: []
1143
+ });
1144
+ } catch {
1145
+ }
1146
+ try {
1147
+ await client.execute({
1148
+ sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
1149
+ args: []
1150
+ });
1151
+ } catch {
1152
+ }
1153
+ try {
1154
+ await client.execute({
1155
+ sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
1156
+ args: []
1157
+ });
1158
+ } catch {
1159
+ }
1050
1160
  try {
1051
1161
  await client.execute({
1052
1162
  sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
@@ -1457,6 +1567,15 @@ async function ensureSchema() {
1457
1567
  } catch {
1458
1568
  }
1459
1569
  }
1570
+ for (const col of [
1571
+ "ALTER TABLE memories ADD COLUMN source_path TEXT",
1572
+ "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
1573
+ ]) {
1574
+ try {
1575
+ await client.execute(col);
1576
+ } catch {
1577
+ }
1578
+ }
1460
1579
  await client.executeMultiple(`
1461
1580
  CREATE INDEX IF NOT EXISTS idx_memories_workspace
1462
1581
  ON memories(workspace_id);
@@ -1521,6 +1640,34 @@ async function ensureSchema() {
1521
1640
  CREATE INDEX IF NOT EXISTS idx_conversations_channel
1522
1641
  ON conversations(channel_id);
1523
1642
  `);
1643
+ try {
1644
+ await client.execute({
1645
+ sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
1646
+ args: []
1647
+ });
1648
+ } catch {
1649
+ }
1650
+ try {
1651
+ await client.execute({
1652
+ sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
1653
+ args: []
1654
+ });
1655
+ } catch {
1656
+ }
1657
+ try {
1658
+ await client.execute({
1659
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
1660
+ args: []
1661
+ });
1662
+ } catch {
1663
+ }
1664
+ try {
1665
+ await client.execute({
1666
+ sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
1667
+ args: []
1668
+ });
1669
+ } catch {
1670
+ }
1524
1671
  await client.executeMultiple(`
1525
1672
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
1526
1673
  content_text,
@@ -1547,6 +1694,52 @@ async function ensureSchema() {
1547
1694
  VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
1548
1695
  END;
1549
1696
  `);
1697
+ try {
1698
+ await client.execute({
1699
+ sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
1700
+ args: []
1701
+ });
1702
+ } catch {
1703
+ }
1704
+ try {
1705
+ await client.execute(
1706
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
1707
+ );
1708
+ } catch {
1709
+ }
1710
+ try {
1711
+ await client.execute({
1712
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
1713
+ args: []
1714
+ });
1715
+ await client.execute({
1716
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
1717
+ args: []
1718
+ });
1719
+ } catch {
1720
+ }
1721
+ try {
1722
+ await client.execute({
1723
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
1724
+ args: []
1725
+ });
1726
+ } catch {
1727
+ }
1728
+ try {
1729
+ await client.execute(
1730
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
1731
+ );
1732
+ } catch {
1733
+ }
1734
+ for (const col of [
1735
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
1736
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
1737
+ ]) {
1738
+ try {
1739
+ await client.execute(col);
1740
+ } catch {
1741
+ }
1742
+ }
1550
1743
  }
1551
1744
 
1552
1745
  // src/lib/keychain.ts
@@ -1638,6 +1831,11 @@ async function initStore(options) {
1638
1831
  const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
1639
1832
  _nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
1640
1833
  }
1834
+ function classifyTier(record) {
1835
+ if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
1836
+ if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
1837
+ return 3;
1838
+ }
1641
1839
  async function writeMemory(record) {
1642
1840
  if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
1643
1841
  throw new Error(
@@ -1665,7 +1863,11 @@ async function writeMemory(record) {
1665
1863
  document_id: record.document_id ?? null,
1666
1864
  user_id: record.user_id ?? null,
1667
1865
  char_offset: record.char_offset ?? null,
1668
- page_number: record.page_number ?? null
1866
+ page_number: record.page_number ?? null,
1867
+ source_path: record.source_path ?? null,
1868
+ source_type: record.source_type ?? null,
1869
+ tier: record.tier ?? classifyTier(record),
1870
+ supersedes_id: record.supersedes_id ?? null
1669
1871
  };
1670
1872
  _pendingRecords.push(dbRow);
1671
1873
  if (_flushTimer === null) {
@@ -1697,20 +1899,26 @@ async function flushBatch() {
1697
1899
  const userId = row.user_id ?? null;
1698
1900
  const charOffset = row.char_offset ?? null;
1699
1901
  const pageNumber = row.page_number ?? null;
1902
+ const sourcePath = row.source_path ?? null;
1903
+ const sourceType = row.source_type ?? null;
1904
+ const tier = row.tier ?? 3;
1905
+ const supersedesId = row.supersedes_id ?? null;
1700
1906
  return {
1701
1907
  sql: hasVector ? `INSERT OR IGNORE INTO memories
1702
1908
  (id, agent_id, agent_role, session_id, timestamp,
1703
1909
  tool_name, project_name,
1704
1910
  has_error, raw_text, vector, version, task_id, importance, status,
1705
1911
  confidence, last_accessed,
1706
- workspace_id, document_id, user_id, char_offset, page_number)
1707
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1912
+ workspace_id, document_id, user_id, char_offset, page_number,
1913
+ source_path, source_type, tier, supersedes_id)
1914
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
1708
1915
  (id, agent_id, agent_role, session_id, timestamp,
1709
1916
  tool_name, project_name,
1710
1917
  has_error, raw_text, vector, version, task_id, importance, status,
1711
1918
  confidence, last_accessed,
1712
- workspace_id, document_id, user_id, char_offset, page_number)
1713
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1919
+ workspace_id, document_id, user_id, char_offset, page_number,
1920
+ source_path, source_type, tier, supersedes_id)
1921
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
1714
1922
  args: hasVector ? [
1715
1923
  row.id,
1716
1924
  row.agent_id,
@@ -1732,7 +1940,11 @@ async function flushBatch() {
1732
1940
  documentId,
1733
1941
  userId,
1734
1942
  charOffset,
1735
- pageNumber
1943
+ pageNumber,
1944
+ sourcePath,
1945
+ sourceType,
1946
+ tier,
1947
+ supersedesId
1736
1948
  ] : [
1737
1949
  row.id,
1738
1950
  row.agent_id,
@@ -1753,7 +1965,11 @@ async function flushBatch() {
1753
1965
  documentId,
1754
1966
  userId,
1755
1967
  charOffset,
1756
- pageNumber
1968
+ pageNumber,
1969
+ sourcePath,
1970
+ sourceType,
1971
+ tier,
1972
+ supersedesId
1757
1973
  ]
1758
1974
  };
1759
1975
  };
@@ -1827,7 +2043,8 @@ async function searchMemories(queryVector, agentId, options) {
1827
2043
  has_error, raw_text, vector, importance, status,
1828
2044
  confidence, last_accessed,
1829
2045
  workspace_id, document_id, user_id,
1830
- char_offset, page_number
2046
+ char_offset, page_number,
2047
+ source_path, source_type
1831
2048
  FROM memories
1832
2049
  WHERE agent_id = ?
1833
2050
  AND vector IS NOT NULL${statusFilter}
@@ -1876,7 +2093,9 @@ async function searchMemories(queryVector, agentId, options) {
1876
2093
  document_id: row.document_id ?? null,
1877
2094
  user_id: row.user_id ?? null,
1878
2095
  char_offset: row.char_offset ?? null,
1879
- page_number: row.page_number ?? null
2096
+ page_number: row.page_number ?? null,
2097
+ source_path: row.source_path ?? null,
2098
+ source_type: row.source_type ?? null
1880
2099
  }));
1881
2100
  }
1882
2101
  function vectorToBlob(vector) {