@askexenow/exe-os 0.8.80 → 0.8.82

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 (110) hide show
  1. package/dist/bin/backfill-conversations.js +359 -267
  2. package/dist/bin/backfill-responses.js +357 -265
  3. package/dist/bin/backfill-vectors.js +339 -264
  4. package/dist/bin/cleanup-stale-review-tasks.js +315 -256
  5. package/dist/bin/cli.js +494 -240
  6. package/dist/bin/exe-agent.js +141 -46
  7. package/dist/bin/exe-assign.js +151 -63
  8. package/dist/bin/exe-boot.js +294 -115
  9. package/dist/bin/exe-call.js +76 -51
  10. package/dist/bin/exe-cloud.js +58 -45
  11. package/dist/bin/exe-dispatch.js +434 -277
  12. package/dist/bin/exe-doctor.js +317 -246
  13. package/dist/bin/exe-export-behaviors.js +328 -248
  14. package/dist/bin/exe-forget.js +314 -231
  15. package/dist/bin/exe-gateway.js +2676 -1402
  16. package/dist/bin/exe-heartbeat.js +329 -264
  17. package/dist/bin/exe-kill.js +324 -244
  18. package/dist/bin/exe-launch-agent.js +574 -463
  19. package/dist/bin/exe-link.js +1055 -95
  20. package/dist/bin/exe-new-employee.js +49 -54
  21. package/dist/bin/exe-pending-messages.js +310 -253
  22. package/dist/bin/exe-pending-notifications.js +299 -228
  23. package/dist/bin/exe-pending-reviews.js +314 -245
  24. package/dist/bin/exe-rename.js +259 -195
  25. package/dist/bin/exe-review.js +140 -64
  26. package/dist/bin/exe-search.js +543 -356
  27. package/dist/bin/exe-session-cleanup.js +463 -382
  28. package/dist/bin/exe-settings.js +129 -99
  29. package/dist/bin/exe-start.sh +6 -6
  30. package/dist/bin/exe-status.js +95 -36
  31. package/dist/bin/exe-team.js +116 -51
  32. package/dist/bin/git-sweep.js +482 -307
  33. package/dist/bin/graph-backfill.js +357 -245
  34. package/dist/bin/graph-export.js +324 -244
  35. package/dist/bin/install.js +33 -10
  36. package/dist/bin/scan-tasks.js +481 -307
  37. package/dist/bin/setup.js +1147 -140
  38. package/dist/bin/shard-migrate.js +321 -241
  39. package/dist/bin/update.js +1 -7
  40. package/dist/bin/wiki-sync.js +318 -238
  41. package/dist/gateway/index.js +2656 -1383
  42. package/dist/hooks/bug-report-worker.js +641 -472
  43. package/dist/hooks/commit-complete.js +482 -307
  44. package/dist/hooks/error-recall.js +363 -135
  45. package/dist/hooks/exe-heartbeat-hook.js +97 -27
  46. package/dist/hooks/ingest-worker.js +584 -397
  47. package/dist/hooks/ingest.js +123 -58
  48. package/dist/hooks/instructions-loaded.js +212 -82
  49. package/dist/hooks/notification.js +200 -70
  50. package/dist/hooks/post-compact.js +199 -81
  51. package/dist/hooks/pre-compact.js +352 -140
  52. package/dist/hooks/pre-tool-use.js +416 -278
  53. package/dist/hooks/prompt-ingest-worker.js +376 -299
  54. package/dist/hooks/prompt-submit.js +414 -188
  55. package/dist/hooks/response-ingest-worker.js +408 -338
  56. package/dist/hooks/session-end.js +209 -83
  57. package/dist/hooks/session-start.js +382 -158
  58. package/dist/hooks/stop.js +209 -83
  59. package/dist/hooks/subagent-stop.js +209 -85
  60. package/dist/hooks/summary-worker.js +606 -510
  61. package/dist/index.js +2133 -855
  62. package/dist/lib/cloud-sync.js +1175 -184
  63. package/dist/lib/config.js +1 -9
  64. package/dist/lib/consolidation.js +71 -34
  65. package/dist/lib/database.js +166 -14
  66. package/dist/lib/device-registry.js +189 -117
  67. package/dist/lib/embedder.js +6 -10
  68. package/dist/lib/employee-templates.js +134 -39
  69. package/dist/lib/employees.js +30 -7
  70. package/dist/lib/exe-daemon-client.js +5 -7
  71. package/dist/lib/exe-daemon.js +514 -152
  72. package/dist/lib/hybrid-search.js +543 -356
  73. package/dist/lib/identity-templates.js +15 -15
  74. package/dist/lib/identity.js +19 -15
  75. package/dist/lib/license.js +1 -7
  76. package/dist/lib/messaging.js +157 -135
  77. package/dist/lib/reminders.js +97 -0
  78. package/dist/lib/schedules.js +302 -231
  79. package/dist/lib/skill-learning.js +33 -27
  80. package/dist/lib/status-brief.js +11 -14
  81. package/dist/lib/store.js +326 -237
  82. package/dist/lib/task-router.js +105 -1
  83. package/dist/lib/tasks.js +233 -116
  84. package/dist/lib/tmux-routing.js +173 -56
  85. package/dist/lib/ws-client.js +13 -3
  86. package/dist/mcp/server.js +2009 -1015
  87. package/dist/mcp/tools/complete-reminder.js +97 -0
  88. package/dist/mcp/tools/create-reminder.js +97 -0
  89. package/dist/mcp/tools/create-task.js +426 -262
  90. package/dist/mcp/tools/deactivate-behavior.js +119 -44
  91. package/dist/mcp/tools/list-reminders.js +97 -0
  92. package/dist/mcp/tools/list-tasks.js +56 -57
  93. package/dist/mcp/tools/send-message.js +206 -143
  94. package/dist/mcp/tools/update-task.js +259 -85
  95. package/dist/runtime/index.js +495 -316
  96. package/dist/tui/App.js +1128 -919
  97. package/package.json +2 -10
  98. package/src/commands/exe/afk.md +8 -8
  99. package/src/commands/exe/assign.md +1 -1
  100. package/src/commands/exe/build-adv.md +1 -1
  101. package/src/commands/exe/call.md +10 -10
  102. package/src/commands/exe/employee-heartbeat.md +9 -6
  103. package/src/commands/exe/heartbeat.md +5 -5
  104. package/src/commands/exe/intercom.md +26 -15
  105. package/src/commands/exe/launch.md +2 -2
  106. package/src/commands/exe/new-employee.md +1 -1
  107. package/src/commands/exe/review.md +2 -2
  108. package/src/commands/exe/schedule.md +1 -1
  109. package/src/commands/exe/sessions.md +2 -2
  110. package/src/commands/exe.md +22 -20
@@ -165,13 +165,7 @@ var init_config = __esm({
165
165
  wikiUrl: "",
166
166
  wikiApiKey: "",
167
167
  wikiSyncIntervalMs: 30 * 60 * 1e3,
168
- wikiWorkspaceMapping: {
169
- exe: "Executive",
170
- yoshi: "Engineering",
171
- mari: "Marketing",
172
- tom: "Engineering",
173
- sasha: "Production"
174
- },
168
+ wikiWorkspaceMapping: {},
175
169
  wikiAutoUpdate: true,
176
170
  wikiAutoUpdateThreshold: 0.5,
177
171
  wikiAutoUpdateCreateNew: true,
@@ -243,13 +237,64 @@ var init_session_key = __esm({
243
237
  }
244
238
  });
245
239
 
246
- // src/lib/session-registry.ts
247
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
248
- import path3 from "path";
240
+ // src/lib/employees.ts
241
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
242
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
243
+ import { execSync as execSync2 } from "child_process";
244
+ import path2 from "path";
249
245
  import os2 from "os";
246
+ function normalizeRole(role) {
247
+ return (role ?? "").trim().toLowerCase();
248
+ }
249
+ function isCoordinatorRole(role) {
250
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
251
+ }
252
+ function getCoordinatorEmployee(employees) {
253
+ return employees.find((e) => isCoordinatorRole(e.role));
254
+ }
255
+ function getCoordinatorName(employees = loadEmployeesSync()) {
256
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
257
+ }
258
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
259
+ if (!agentName) return false;
260
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
261
+ }
262
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
263
+ if (!existsSync2(employeesPath)) return [];
264
+ try {
265
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
266
+ } catch {
267
+ return [];
268
+ }
269
+ }
270
+ function getEmployee(employees, name) {
271
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
272
+ }
273
+ function isMultiInstance(agentName, employees) {
274
+ const roster = employees ?? loadEmployeesSync();
275
+ const emp = getEmployee(roster, agentName);
276
+ if (!emp) return false;
277
+ return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
278
+ }
279
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
280
+ var init_employees = __esm({
281
+ "src/lib/employees.ts"() {
282
+ "use strict";
283
+ init_config();
284
+ EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
285
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
286
+ COORDINATOR_ROLE = "COO";
287
+ MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
288
+ }
289
+ });
290
+
291
+ // src/lib/session-registry.ts
292
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
293
+ import path4 from "path";
294
+ import os3 from "os";
250
295
  function registerSession(entry) {
251
- const dir = path3.dirname(REGISTRY_PATH);
252
- if (!existsSync2(dir)) {
296
+ const dir = path4.dirname(REGISTRY_PATH);
297
+ if (!existsSync3(dir)) {
253
298
  mkdirSync2(dir, { recursive: true });
254
299
  }
255
300
  const sessions = listSessions();
@@ -259,11 +304,11 @@ function registerSession(entry) {
259
304
  } else {
260
305
  sessions.push(entry);
261
306
  }
262
- writeFileSync2(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
307
+ writeFileSync3(REGISTRY_PATH, JSON.stringify(sessions, null, 2));
263
308
  }
264
309
  function listSessions() {
265
310
  try {
266
- const raw = readFileSync3(REGISTRY_PATH, "utf8");
311
+ const raw = readFileSync4(REGISTRY_PATH, "utf8");
267
312
  return JSON.parse(raw);
268
313
  } catch {
269
314
  return [];
@@ -273,7 +318,7 @@ var REGISTRY_PATH;
273
318
  var init_session_registry = __esm({
274
319
  "src/lib/session-registry.ts"() {
275
320
  "use strict";
276
- REGISTRY_PATH = path3.join(os2.homedir(), ".exe-os", "session-registry.json");
321
+ REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
277
322
  }
278
323
  });
279
324
 
@@ -385,14 +430,14 @@ var init_transport = __esm({
385
430
  });
386
431
 
387
432
  // src/lib/cc-agent-support.ts
388
- import { execSync as execSync3 } from "child_process";
433
+ import { execSync as execSync4 } from "child_process";
389
434
  function _resetCcAgentSupportCache() {
390
435
  _cachedSupport = null;
391
436
  }
392
437
  function claudeSupportsAgentFlag() {
393
438
  if (_cachedSupport !== null) return _cachedSupport;
394
439
  try {
395
- const helpOutput = execSync3("claude --help 2>&1", {
440
+ const helpOutput = execSync4("claude --help 2>&1", {
396
441
  encoding: "utf-8",
397
442
  timeout: 5e3
398
443
  });
@@ -458,17 +503,17 @@ var init_provider_table = __esm({
458
503
  });
459
504
 
460
505
  // src/lib/intercom-queue.ts
461
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync as renameSync2, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
462
- import path4 from "path";
463
- import os3 from "os";
506
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, renameSync as renameSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
507
+ import path5 from "path";
508
+ import os4 from "os";
464
509
  function ensureDir() {
465
- const dir = path4.dirname(QUEUE_PATH);
466
- if (!existsSync3(dir)) mkdirSync3(dir, { recursive: true });
510
+ const dir = path5.dirname(QUEUE_PATH);
511
+ if (!existsSync4(dir)) mkdirSync3(dir, { recursive: true });
467
512
  }
468
513
  function readQueue() {
469
514
  try {
470
- if (!existsSync3(QUEUE_PATH)) return [];
471
- return JSON.parse(readFileSync4(QUEUE_PATH, "utf8"));
515
+ if (!existsSync4(QUEUE_PATH)) return [];
516
+ return JSON.parse(readFileSync5(QUEUE_PATH, "utf8"));
472
517
  } catch {
473
518
  return [];
474
519
  }
@@ -476,8 +521,8 @@ function readQueue() {
476
521
  function writeQueue(queue) {
477
522
  ensureDir();
478
523
  const tmp = `${QUEUE_PATH}.tmp`;
479
- writeFileSync3(tmp, JSON.stringify(queue, null, 2));
480
- renameSync2(tmp, QUEUE_PATH);
524
+ writeFileSync4(tmp, JSON.stringify(queue, null, 2));
525
+ renameSync3(tmp, QUEUE_PATH);
481
526
  }
482
527
  function queueIntercom(targetSession, reason) {
483
528
  const queue = readQueue();
@@ -500,9 +545,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
500
545
  var init_intercom_queue = __esm({
501
546
  "src/lib/intercom-queue.ts"() {
502
547
  "use strict";
503
- QUEUE_PATH = path4.join(os3.homedir(), ".exe-os", "intercom-queue.json");
548
+ QUEUE_PATH = path5.join(os4.homedir(), ".exe-os", "intercom-queue.json");
504
549
  TTL_MS = 60 * 60 * 1e3;
505
- INTERCOM_LOG = path4.join(os3.homedir(), ".exe-os", "intercom.log");
550
+ INTERCOM_LOG = path5.join(os4.homedir(), ".exe-os", "intercom.log");
506
551
  }
507
552
  });
508
553
 
@@ -545,7 +590,7 @@ function wrapWithRetry(client) {
545
590
  return (sql) => retryOnBusy(() => target.execute(sql), "execute");
546
591
  }
547
592
  if (prop === "batch") {
548
- return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
593
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
549
594
  }
550
595
  return Reflect.get(target, prop, receiver);
551
596
  }
@@ -708,22 +753,24 @@ async function ensureSchema() {
708
753
  ON behaviors(agent_id, active);
709
754
  `);
710
755
  try {
756
+ const coordinatorName = getCoordinatorName();
711
757
  const existing = await client.execute({
712
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
713
- args: []
758
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
759
+ args: [coordinatorName]
714
760
  });
715
761
  if (Number(existing.rows[0]?.cnt) === 0) {
716
- await client.executeMultiple(`
717
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
718
- VALUES
719
- (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Don''t ask "keep going?" \u2014 just keep executing phases/plans autonomously', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
720
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
721
- VALUES
722
- (hex(randomblob(16)), 'exe', NULL, 'tool-use', 'Always use create_task MCP tool, never write .md files directly for task creation', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
723
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
724
- VALUES
725
- (hex(randomblob(16)), 'exe', NULL, 'workflow', 'Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission', 1, '2026-03-25T00:00:00Z', '2026-03-25T00:00:00Z');
726
- `);
762
+ const seededAt = "2026-03-25T00:00:00Z";
763
+ for (const [domain, content] of [
764
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
765
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
766
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
767
+ ]) {
768
+ await client.execute({
769
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
770
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
771
+ args: [coordinatorName, domain, content, seededAt, seededAt]
772
+ });
773
+ }
727
774
  }
728
775
  } catch {
729
776
  }
@@ -1415,6 +1462,39 @@ async function ensureSchema() {
1415
1462
  } catch {
1416
1463
  }
1417
1464
  }
1465
+ try {
1466
+ await client.execute({
1467
+ sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
1468
+ args: []
1469
+ });
1470
+ } catch {
1471
+ }
1472
+ try {
1473
+ await client.execute(
1474
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
1475
+ );
1476
+ } catch {
1477
+ }
1478
+ try {
1479
+ await client.execute({
1480
+ sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
1481
+ args: []
1482
+ });
1483
+ } catch {
1484
+ }
1485
+ try {
1486
+ await client.execute(
1487
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
1488
+ );
1489
+ } catch {
1490
+ }
1491
+ try {
1492
+ await client.execute({
1493
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
1494
+ args: []
1495
+ });
1496
+ } catch {
1497
+ }
1418
1498
  }
1419
1499
  async function disposeDatabase() {
1420
1500
  if (_client) {
@@ -1428,6 +1508,7 @@ var init_database = __esm({
1428
1508
  "src/lib/database.ts"() {
1429
1509
  "use strict";
1430
1510
  init_db_retry();
1511
+ init_employees();
1431
1512
  _client = null;
1432
1513
  _resilientClient = null;
1433
1514
  initTurso = initDatabase;
@@ -1435,39 +1516,6 @@ var init_database = __esm({
1435
1516
  }
1436
1517
  });
1437
1518
 
1438
- // src/lib/employees.ts
1439
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1440
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync5, renameSync as renameSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
1441
- import { execSync as execSync4 } from "child_process";
1442
- import path5 from "path";
1443
- import os4 from "os";
1444
- function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1445
- if (!existsSync4(employeesPath)) return [];
1446
- try {
1447
- return JSON.parse(readFileSync5(employeesPath, "utf-8"));
1448
- } catch {
1449
- return [];
1450
- }
1451
- }
1452
- function getEmployee(employees, name) {
1453
- return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
1454
- }
1455
- function isMultiInstance(agentName, employees) {
1456
- const roster = employees ?? loadEmployeesSync();
1457
- const emp = getEmployee(roster, agentName);
1458
- if (!emp) return false;
1459
- return MULTI_INSTANCE_ROLES.has(emp.role.toLowerCase());
1460
- }
1461
- var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
1462
- var init_employees = __esm({
1463
- "src/lib/employees.ts"() {
1464
- "use strict";
1465
- init_config();
1466
- EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
1467
- MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
1468
- }
1469
- });
1470
-
1471
1519
  // src/lib/license.ts
1472
1520
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "fs";
1473
1521
  import { randomUUID } from "crypto";
@@ -1955,6 +2003,36 @@ async function listTasks(input2) {
1955
2003
  tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
1956
2004
  }));
1957
2005
  }
2006
+ function isTmuxSessionAlive(identifier) {
2007
+ if (!identifier || identifier === "unknown") return true;
2008
+ try {
2009
+ if (identifier.startsWith("%")) {
2010
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
2011
+ timeout: 2e3,
2012
+ encoding: "utf8",
2013
+ stdio: ["pipe", "pipe", "pipe"]
2014
+ });
2015
+ return output.split("\n").some((l) => l.trim() === identifier);
2016
+ } else {
2017
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
2018
+ timeout: 2e3,
2019
+ stdio: ["pipe", "pipe", "pipe"]
2020
+ });
2021
+ return true;
2022
+ }
2023
+ } catch {
2024
+ if (identifier.startsWith("%")) return true;
2025
+ try {
2026
+ execSync5("tmux list-sessions", {
2027
+ timeout: 2e3,
2028
+ stdio: ["pipe", "pipe", "pipe"]
2029
+ });
2030
+ return false;
2031
+ } catch {
2032
+ return true;
2033
+ }
2034
+ }
2035
+ }
1958
2036
  function checkStaleCompletion(taskContext, taskCreatedAt) {
1959
2037
  if (!taskContext) return null;
1960
2038
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
@@ -2017,13 +2095,59 @@ ${input2.result}` : `\u26A0\uFE0F ${warning}`;
2017
2095
  });
2018
2096
  if (claim.rowsAffected === 0) {
2019
2097
  const current = await client.execute({
2020
- sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
2098
+ sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
2021
2099
  args: [taskId]
2022
2100
  });
2023
2101
  const cur = current.rows[0];
2024
- const status = cur?.status ?? "unknown";
2025
- const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
2026
- throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
2102
+ const curStatus = cur?.status ?? "unknown";
2103
+ const claimedBySession = cur?.assigned_tmux ?? "";
2104
+ const assignedBy = cur?.assigned_by ?? "";
2105
+ if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
2106
+ process.stderr.write(
2107
+ `[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
2108
+ `
2109
+ );
2110
+ await client.execute({
2111
+ sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
2112
+ args: [now, taskId]
2113
+ });
2114
+ const retried = await client.execute({
2115
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
2116
+ args: [tmuxSession, now, taskId]
2117
+ });
2118
+ if (retried.rowsAffected > 0) {
2119
+ try {
2120
+ await writeCheckpoint({
2121
+ taskId,
2122
+ step: "reclaimed_dead_session",
2123
+ contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
2124
+ });
2125
+ } catch {
2126
+ }
2127
+ return { row, taskFile, now, taskId };
2128
+ }
2129
+ }
2130
+ if (curStatus === "in_progress" && input2.callerAgentId && (input2.callerAgentId === assignedBy || input2.callerAgentId === "exe")) {
2131
+ process.stderr.write(
2132
+ `[tasks] Assigner override: ${input2.callerAgentId} reclaiming ${taskId}
2133
+ `
2134
+ );
2135
+ await client.execute({
2136
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
2137
+ args: [tmuxSession, now, taskId]
2138
+ });
2139
+ try {
2140
+ await writeCheckpoint({
2141
+ taskId,
2142
+ step: "assigner_override",
2143
+ contextSummary: `Task force-reclaimed by assigner ${input2.callerAgentId}.`
2144
+ });
2145
+ } catch {
2146
+ }
2147
+ return { row, taskFile, now, taskId };
2148
+ }
2149
+ const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
2150
+ throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
2027
2151
  }
2028
2152
  try {
2029
2153
  await writeCheckpoint({
@@ -2121,7 +2245,7 @@ var init_tasks_crud = __esm({
2121
2245
  "use strict";
2122
2246
  init_database();
2123
2247
  init_task_scope();
2124
- DELEGATION_KEYWORDS = /parallel|delegate|wave|tom\d*-exe/i;
2248
+ DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
2125
2249
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
2126
2250
  }
2127
2251
  });
@@ -2478,7 +2602,7 @@ function findSessionForProject(projectName) {
2478
2602
  const sessions = listSessions();
2479
2603
  for (const s of sessions) {
2480
2604
  const proj = s.projectDir.split("/").filter(Boolean).pop();
2481
- if (proj === projectName && s.agentId === "exe") return s;
2605
+ if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
2482
2606
  }
2483
2607
  return null;
2484
2608
  }
@@ -2518,12 +2642,13 @@ var init_session_scope = __esm({
2518
2642
  init_session_registry();
2519
2643
  init_project_name();
2520
2644
  init_tmux_routing();
2645
+ init_employees();
2521
2646
  }
2522
2647
  });
2523
2648
 
2524
2649
  // src/lib/tasks-notify.ts
2525
2650
  async function dispatchTaskToEmployee(input2) {
2526
- if (input2.assignedTo === "exe") return { dispatched: "skipped" };
2651
+ if (input2.assignedTo === "exe" || isCoordinatorName(input2.assignedTo)) return { dispatched: "skipped" };
2527
2652
  let crossProject = false;
2528
2653
  if (input2.projectName) {
2529
2654
  try {
@@ -2966,6 +3091,24 @@ async function updateTask(input2) {
2966
3091
  });
2967
3092
  } catch {
2968
3093
  }
3094
+ const assignedAgent = String(row.assigned_to);
3095
+ if (!isCoordinatorName(assignedAgent)) {
3096
+ try {
3097
+ const draftClient = getClient();
3098
+ if (input2.status === "done") {
3099
+ await draftClient.execute({
3100
+ sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
3101
+ args: [assignedAgent]
3102
+ });
3103
+ } else if (input2.status === "cancelled") {
3104
+ await draftClient.execute({
3105
+ sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
3106
+ args: [assignedAgent]
3107
+ });
3108
+ }
3109
+ } catch {
3110
+ }
3111
+ }
2969
3112
  try {
2970
3113
  const client = getClient();
2971
3114
  const cascaded = await client.execute({
@@ -2984,8 +3127,8 @@ async function updateTask(input2) {
2984
3127
  }
2985
3128
  const isTerminal = input2.status === "done" || input2.status === "needs_review";
2986
3129
  if (isTerminal) {
2987
- const isExe = String(row.assigned_to) === "exe";
2988
- if (!isExe) {
3130
+ const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
3131
+ if (!isCoordinator) {
2989
3132
  notifyTaskDone();
2990
3133
  }
2991
3134
  await markTaskNotificationsRead(taskFile);
@@ -3009,7 +3152,7 @@ async function updateTask(input2) {
3009
3152
  }
3010
3153
  }
3011
3154
  }
3012
- if (input2.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
3155
+ if (input2.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
3013
3156
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
3014
3157
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
3015
3158
  taskId,
@@ -3025,7 +3168,7 @@ async function updateTask(input2) {
3025
3168
  });
3026
3169
  }
3027
3170
  let nextTask;
3028
- if (isTerminal && String(row.assigned_to) !== "exe") {
3171
+ if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
3029
3172
  try {
3030
3173
  nextTask = await findNextTask(String(row.assigned_to));
3031
3174
  } catch {
@@ -3052,12 +3195,14 @@ async function updateTask(input2) {
3052
3195
  async function deleteTask(taskId, baseDir) {
3053
3196
  const client = getClient();
3054
3197
  const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
3055
- const reviewer = assignedBy || "exe";
3198
+ const coordinatorName = getCoordinatorName();
3199
+ const reviewer = assignedBy || coordinatorName;
3056
3200
  const reviewSlug = `review-${assignedTo}-${taskSlug}`;
3057
3201
  const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
3202
+ const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
3058
3203
  await client.execute({
3059
- sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
3060
- args: [reviewFile, `exe/exe/${reviewSlug}.md`]
3204
+ sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
3205
+ args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
3061
3206
  });
3062
3207
  await markAsReadByTaskFile(taskFile);
3063
3208
  await markAsReadByTaskFile(reviewFile);
@@ -3069,6 +3214,7 @@ var init_tasks = __esm({
3069
3214
  init_config();
3070
3215
  init_notifications();
3071
3216
  init_state_bus();
3217
+ init_employees();
3072
3218
  init_tasks_crud();
3073
3219
  init_tasks_review();
3074
3220
  init_tasks_crud();
@@ -3154,7 +3300,7 @@ function _resetLastRelaunchCache() {
3154
3300
  }
3155
3301
  async function lastResumeCreatedAtMs(agentId) {
3156
3302
  const client = getClient();
3157
- const cmScope = sessionScopeFilter();
3303
+ const cmScope = sessionScopeFilter(null);
3158
3304
  const result = await client.execute({
3159
3305
  sql: `SELECT MAX(created_at) AS last_created_at
3160
3306
  FROM tasks
@@ -3179,7 +3325,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
3179
3325
  const client = getClient();
3180
3326
  const now = (/* @__PURE__ */ new Date()).toISOString();
3181
3327
  const context = buildResumeContext(agentId, openTasks);
3182
- const rdScope = sessionScopeFilter();
3328
+ const rdScope = sessionScopeFilter(null);
3183
3329
  const existing = await client.execute({
3184
3330
  sql: `SELECT id FROM tasks
3185
3331
  WHERE assigned_to = ?
@@ -3213,7 +3359,7 @@ async function pollCapacityDead() {
3213
3359
  const transport = getTransport();
3214
3360
  const relaunched = [];
3215
3361
  const registered = listSessions().filter(
3216
- (s) => s.agentId !== "exe"
3362
+ (s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
3217
3363
  );
3218
3364
  if (registered.length === 0) return [];
3219
3365
  let liveSessions;
@@ -3273,7 +3419,7 @@ async function pollCapacityDead() {
3273
3419
  reason: "capacity"
3274
3420
  });
3275
3421
  const client = getClient();
3276
- const rlScope = sessionScopeFilter();
3422
+ const rlScope = sessionScopeFilter(null);
3277
3423
  const openTasks = await client.execute({
3278
3424
  sql: `SELECT id, title, priority, task_file, status
3279
3425
  FROM tasks
@@ -3327,6 +3473,7 @@ var init_capacity_monitor = __esm({
3327
3473
  init_session_kill_telemetry();
3328
3474
  init_tmux_routing();
3329
3475
  init_task_scope();
3476
+ init_employees();
3330
3477
  CAPACITY_PATTERNS = [
3331
3478
  /conversation is too long/i,
3332
3479
  /maximum context length/i,
@@ -3476,7 +3623,7 @@ function employeeSessionName(employee, exeSession, instance) {
3476
3623
  exeSession = root;
3477
3624
  } else {
3478
3625
  throw new Error(
3479
- `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
3626
+ `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
3480
3627
  );
3481
3628
  }
3482
3629
  }
@@ -3496,8 +3643,10 @@ function parseParentExe(sessionName, agentId) {
3496
3643
  return match?.[1] ?? null;
3497
3644
  }
3498
3645
  function extractRootExe(name) {
3499
- const match = name.match(/(exe\d+)$/);
3500
- return match?.[1] ?? null;
3646
+ if (!name) return null;
3647
+ if (!name.includes("-")) return name;
3648
+ const parts = name.split("-").filter(Boolean);
3649
+ return parts.length > 0 ? parts[parts.length - 1] : null;
3501
3650
  }
3502
3651
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3503
3652
  if (!existsSync10(SESSION_CACHE)) {
@@ -3642,12 +3791,14 @@ function isSessionBusy(sessionName) {
3642
3791
  return state === "thinking" || state === "tool";
3643
3792
  }
3644
3793
  function isExeSession(sessionName) {
3645
- return /^exe\d*$/.test(sessionName);
3794
+ const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
3795
+ const coordinatorName = getCoordinatorName();
3796
+ return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
3646
3797
  }
3647
3798
  function sendIntercom(targetSession) {
3648
3799
  const transport = getTransport();
3649
3800
  if (isExeSession(targetSession)) {
3650
- logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
3801
+ logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
3651
3802
  return "skipped_exe";
3652
3803
  }
3653
3804
  if (isDebounced(targetSession)) {
@@ -3699,7 +3850,7 @@ function notifyParentExe(sessionKey) {
3699
3850
  if (result === "failed") {
3700
3851
  const rootExe = resolveExeSession();
3701
3852
  if (rootExe && rootExe !== target) {
3702
- process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
3853
+ process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
3703
3854
  `);
3704
3855
  const fallback = sendIntercom(rootExe);
3705
3856
  return fallback !== "failed";
@@ -3709,8 +3860,8 @@ function notifyParentExe(sessionKey) {
3709
3860
  return true;
3710
3861
  }
3711
3862
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3712
- if (employeeName === "exe") {
3713
- return { status: "failed", sessionName: "", error: "exe is the COO, not a dispatchable employee" };
3863
+ if (employeeName === "exe" || isCoordinatorName(employeeName)) {
3864
+ return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
3714
3865
  }
3715
3866
  try {
3716
3867
  assertEmployeeLimitSync();
@@ -3719,8 +3870,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3719
3870
  return { status: "failed", sessionName: "", error: err.message };
3720
3871
  }
3721
3872
  }
3722
- if (/-exe\d*$/.test(employeeName)) {
3723
- const bare = employeeName.replace(/-exe\d*$/, "").replace(/\d+$/, "");
3873
+ if (employeeName.includes("-")) {
3874
+ const bare = employeeName.split("-")[0].replace(/\d+$/, "");
3724
3875
  return {
3725
3876
  status: "failed",
3726
3877
  sessionName: "",
@@ -3739,7 +3890,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
3739
3890
  return {
3740
3891
  status: "failed",
3741
3892
  sessionName: "",
3742
- error: `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
3893
+ error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
3743
3894
  };
3744
3895
  }
3745
3896
  }
@@ -3896,8 +4047,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3896
4047
  const ctxContent = [
3897
4048
  `## Session Context`,
3898
4049
  `You are running in tmux session: ${sessionName}.`,
3899
- `Your parent exe session is ${exeSession}.`,
3900
- `Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
4050
+ `Your parent coordinator session is ${exeSession}.`,
4051
+ `Your employees (if any) use the -${exeSession} suffix.`
3901
4052
  ].join("\n");
3902
4053
  writeFileSync7(ctxFile, ctxContent);
3903
4054
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
@@ -4001,6 +4152,7 @@ var init_tmux_routing = __esm({
4001
4152
  init_provider_table();
4002
4153
  init_intercom_queue();
4003
4154
  init_plan_limits();
4155
+ init_employees();
4004
4156
  SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
4005
4157
  SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
4006
4158
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
@@ -4218,7 +4370,11 @@ async function ensureShardSchema(client) {
4218
4370
  "ALTER TABLE memories ADD COLUMN source_path TEXT",
4219
4371
  "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
4220
4372
  "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
4221
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
4373
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
4374
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
4375
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
4376
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
4377
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT"
4222
4378
  ]) {
4223
4379
  try {
4224
4380
  await client.execute(col);
@@ -4348,26 +4504,26 @@ var init_platform_procedures = __esm({
4348
4504
  title: "What is exe-os \u2014 the operating model every agent must understand",
4349
4505
  domain: "architecture",
4350
4506
  priority: "p0",
4351
- content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO (exe), CTO (yoshi), CMO (mari), engineers (tom), content (sasha). Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
4507
+ content: "Exe OS is an AI employee operating system. A founder runs 5-10 AI agents as a real org: COO, CTO, CMO, engineers, and content production specialists. Each agent has identity, expertise, and experience layers \u2014 persistent memory that makes them better over time. All data is local-first, E2EE, owned by the user. The MCP server is the ONLY data interface \u2014 never access the DB directly."
4352
4508
  },
4353
4509
  {
4354
4510
  title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
4355
4511
  domain: "architecture",
4356
4512
  priority: "p0",
4357
- content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC, runs /exe to boot the COO. exe manages employees in tmux sessions. Each exeN is a separate CC window/project. Employees (yoshi, tom, mari) run in their own tmux panes via create_task auto-spawn. The founder talks to exe; exe orchestrates the team. CC is the shell, exe-os is the brain."
4513
+ content: "Mode 1: exe-os runs AS hooks + MCP + skills inside Claude Code. The founder opens CC and boots the COO. The COO manages employees in tmux sessions. Each coordinator session is a separate CC window/project. Employees run in their own tmux panes via create_task auto-spawn. The founder talks to the COO; the COO orchestrates the team. CC is the shell, exe-os is the brain."
4358
4514
  },
4359
4515
  {
4360
- title: "Sessions explained \u2014 what exeN means and how projects work",
4516
+ title: "Sessions explained \u2014 coordinator session names and projects",
4361
4517
  domain: "architecture",
4362
4518
  priority: "p0",
4363
- content: "Each exeN (exe1, exe2, exe3) is an isolated project session. exe1 might be exe-os development, exe2 might be exe-wiki. Each session spawns its own employees: exe1\u2192yoshi-exe1\u2192tom-exe1. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
4519
+ content: "Each coordinator session is an isolated project session. One might be exe-os development, another might be exe-wiki. Each session spawns its own employees using {employee}-{coordinatorSession}. Sessions share the same memory DB but tasks are scoped to the session that created them. A founder can run multiple projects simultaneously. Sessions never interfere with each other."
4364
4520
  },
4365
4521
  // --- Hierarchy and dispatch ---
4366
4522
  {
4367
4523
  title: "Chain of command \u2014 who talks to whom",
4368
4524
  domain: "workflow",
4369
4525
  priority: "p0",
4370
- content: "Founder \u2192 exe (COO) \u2192 yoshi (CTO) / mari (CMO). Yoshi \u2192 tom (engineer). Mari \u2192 sasha (content). Never skip levels: exe never assigns directly to tom. Tom never reports directly to exe. 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."
4526
+ 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."
4371
4527
  },
4372
4528
  {
4373
4529
  title: "Single dispatch path \u2014 create_task only",
@@ -4377,30 +4533,30 @@ var init_platform_procedures = __esm({
4377
4533
  },
4378
4534
  // --- Session isolation ---
4379
4535
  {
4380
- title: "Session scoping \u2014 stay in your exe boundary",
4536
+ title: "Session scoping \u2014 stay in your coordinator boundary",
4381
4537
  domain: "security",
4382
4538
  priority: "p0",
4383
- content: "Session scoping is mandatory. Managers dispatch to workers within their own exe session ONLY. exe1\u2192yoshi-exe1\u2192tom-exe1. exe2\u2192yoshi-exe2\u2192tom2-exe2. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating exe session."
4539
+ content: "Session scoping is mandatory. Managers dispatch to workers within their own coordinator session ONLY. Employee sessions use {employee}-{coordinatorSession}. Cross-session dispatch is blocked by the system. Verify session names before dispatch. Tasks are scoped to the creating coordinator session."
4384
4540
  },
4385
4541
  {
4386
4542
  title: "Session isolation \u2014 never touch another session's work",
4387
4543
  domain: "workflow",
4388
4544
  priority: "p0",
4389
- content: `Sessions are isolated. exeN owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another exe session. (2) Never review work from a different session \u2014 report "belongs to exeN" and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: yoshi-exe1 works ONLY on exe1 tasks. Cross-session work is a system violation.`
4545
+ content: "Sessions are isolated. A coordinator session owns ONLY tasks it dispatched. (1) Never close/update/cancel tasks from another coordinator session. (2) Never review work from a different session \u2014 report that it belongs to another session and skip. (3) Ignore other sessions' items in list_tasks results. (4) Employees inherit session: employee sessions work ONLY on their parent coordinator session's tasks. Cross-session work is a system violation."
4390
4546
  },
4391
4547
  // --- Engineering: session scoping in code ---
4392
4548
  {
4393
4549
  title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
4394
4550
  domain: "architecture",
4395
4551
  priority: "p0",
4396
- content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching current exeN. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ exe sessions simultaneously."
4552
+ content: "Every DB query, notification, review count, and task operation MUST be scoped on 3 dimensions: (1) Session \u2014 filter by session_scope matching the current coordinator session. (2) Project \u2014 filter by project_name. (3) Role \u2014 agents only see data at their hierarchy level. When writing ANY function that touches tasks, reviews, messages, or notifications: always accept a sessionScope parameter and pass it to the SQL WHERE clause. Unscoped queries are bugs. Test by running 2+ coordinator sessions simultaneously."
4397
4553
  },
4398
4554
  // --- Hard constraints ---
4399
4555
  {
4400
4556
  title: "What you CANNOT do in exe-os \u2014 hard constraints",
4401
4557
  domain: "security",
4402
4558
  priority: "p0",
4403
- content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 exe reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
4559
+ content: "NEVER: (1) Access the database directly \u2014 it's SQLCipher encrypted, always fails. Use MCP tools only. (2) Manually spawn tmux sessions \u2014 create_task handles it. (3) Run git checkout main \u2014 agents work in worktrees. (4) Modify another agent's in-progress task. (5) Push to remote \u2014 the COO reviews and pushes. (6) Skip update_task(done) \u2014 it's the ONLY way your work gets reviewed. (7) Run git init."
4404
4560
  },
4405
4561
  // --- Operations ---
4406
4562
  {
@@ -4640,7 +4796,10 @@ async function writeMemory(record) {
4640
4796
  source_path: record.source_path ?? null,
4641
4797
  source_type: record.source_type ?? null,
4642
4798
  tier: record.tier ?? classifyTier(record),
4643
- supersedes_id: record.supersedes_id ?? null
4799
+ supersedes_id: record.supersedes_id ?? null,
4800
+ draft: record.draft ? 1 : 0,
4801
+ memory_type: record.memory_type ?? "raw",
4802
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
4644
4803
  };
4645
4804
  _pendingRecords.push(dbRow);
4646
4805
  orgBus.emit({
@@ -4695,6 +4854,9 @@ async function flushBatch() {
4695
4854
  const sourceType = row.source_type ?? null;
4696
4855
  const tier = row.tier ?? 3;
4697
4856
  const supersedesId = row.supersedes_id ?? null;
4857
+ const draft = row.draft ? 1 : 0;
4858
+ const memoryType = row.memory_type ?? "raw";
4859
+ const trajectory = row.trajectory ?? null;
4698
4860
  return {
4699
4861
  sql: hasVector ? `INSERT OR IGNORE INTO memories
4700
4862
  (id, agent_id, agent_role, session_id, timestamp,
@@ -4702,15 +4864,15 @@ async function flushBatch() {
4702
4864
  has_error, raw_text, vector, version, task_id, importance, status,
4703
4865
  confidence, last_accessed,
4704
4866
  workspace_id, document_id, user_id, char_offset, page_number,
4705
- source_path, source_type, tier, supersedes_id)
4706
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
4867
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4868
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
4707
4869
  (id, agent_id, agent_role, session_id, timestamp,
4708
4870
  tool_name, project_name,
4709
4871
  has_error, raw_text, vector, version, task_id, importance, status,
4710
4872
  confidence, last_accessed,
4711
4873
  workspace_id, document_id, user_id, char_offset, page_number,
4712
- source_path, source_type, tier, supersedes_id)
4713
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4874
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
4875
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4714
4876
  args: hasVector ? [
4715
4877
  row.id,
4716
4878
  row.agent_id,
@@ -4736,7 +4898,10 @@ async function flushBatch() {
4736
4898
  sourcePath,
4737
4899
  sourceType,
4738
4900
  tier,
4739
- supersedesId
4901
+ supersedesId,
4902
+ draft,
4903
+ memoryType,
4904
+ trajectory
4740
4905
  ] : [
4741
4906
  row.id,
4742
4907
  row.agent_id,
@@ -4761,7 +4926,10 @@ async function flushBatch() {
4761
4926
  sourcePath,
4762
4927
  sourceType,
4763
4928
  tier,
4764
- supersedesId
4929
+ supersedesId,
4930
+ draft,
4931
+ memoryType,
4932
+ trajectory
4765
4933
  ]
4766
4934
  };
4767
4935
  };
@@ -4830,6 +4998,8 @@ async function searchMemories(queryVector, agentId, options) {
4830
4998
  const limit = options?.limit ?? 10;
4831
4999
  const statusFilter = options?.includeArchived ? "" : `
4832
5000
  AND COALESCE(status, 'active') = 'active'`;
5001
+ const draftFilter = options?.includeDrafts ? "" : `
5002
+ AND (draft = 0 OR draft IS NULL)`;
4833
5003
  let sql = `SELECT id, agent_id, agent_role, session_id, timestamp,
4834
5004
  tool_name, project_name,
4835
5005
  has_error, raw_text, vector, importance, status,
@@ -4839,7 +5009,7 @@ async function searchMemories(queryVector, agentId, options) {
4839
5009
  source_path, source_type
4840
5010
  FROM memories
4841
5011
  WHERE agent_id = ?
4842
- AND vector IS NOT NULL${statusFilter}
5012
+ AND vector IS NOT NULL${statusFilter}${draftFilter}
4843
5013
  AND COALESCE(confidence, 0.7) >= 0.3`;
4844
5014
  const args = [agentId];
4845
5015
  const scope = buildWikiScopeFilter(options, "");
@@ -4861,6 +5031,10 @@ async function searchMemories(queryVector, agentId, options) {
4861
5031
  sql += ` AND timestamp >= ?`;
4862
5032
  args.push(options.since);
4863
5033
  }
5034
+ if (options?.memoryType) {
5035
+ sql += ` AND memory_type = ?`;
5036
+ args.push(options.memoryType);
5037
+ }
4864
5038
  sql += ` ORDER BY vector_distance_cos(vector, vector32(?))`;
4865
5039
  args.push(vectorToBlob(queryVector));
4866
5040
  sql += ` LIMIT ?`;
@@ -5011,30 +5185,73 @@ import crypto6 from "crypto";
5011
5185
 
5012
5186
  // src/adapters/claude/active-agent.ts
5013
5187
  init_config();
5014
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync, unlinkSync, readdirSync } from "fs";
5015
- import { execSync as execSync2 } from "child_process";
5016
- import path2 from "path";
5188
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, unlinkSync as unlinkSync2, readdirSync } from "fs";
5189
+ import { execSync as execSync3 } from "child_process";
5190
+ import path3 from "path";
5017
5191
 
5018
5192
  // src/adapters/claude/session-key.ts
5019
5193
  init_session_key();
5020
5194
 
5021
5195
  // src/adapters/claude/active-agent.ts
5022
- var CACHE_DIR = path2.join(EXE_AI_DIR, "session-cache");
5196
+ init_employees();
5197
+ var CACHE_DIR = path3.join(EXE_AI_DIR, "session-cache");
5023
5198
  var STALE_MS = 24 * 60 * 60 * 1e3;
5199
+ function isNameWithOptionalInstance(candidate, baseName) {
5200
+ if (candidate === baseName) return true;
5201
+ if (!candidate.startsWith(baseName)) return false;
5202
+ return /^\d+$/.test(candidate.slice(baseName.length));
5203
+ }
5204
+ function resolveEmployeeFromSessionPrefix(prefix, employees) {
5205
+ const sorted = [...employees].sort((a, b) => b.name.length - a.name.length);
5206
+ for (const employee of sorted) {
5207
+ if (isNameWithOptionalInstance(prefix, employee.name)) {
5208
+ return { agentId: employee.name, agentRole: employee.role };
5209
+ }
5210
+ }
5211
+ return null;
5212
+ }
5213
+ function resolveActiveAgentFromTmuxSession(sessionName) {
5214
+ const employees = loadEmployeesSync();
5215
+ const coordinator = getCoordinatorEmployee(employees);
5216
+ const coordinatorName = coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
5217
+ if (isNameWithOptionalInstance(sessionName, coordinatorName)) {
5218
+ return {
5219
+ agentId: coordinatorName,
5220
+ agentRole: coordinator?.role ?? "COO"
5221
+ };
5222
+ }
5223
+ if (isNameWithOptionalInstance(sessionName, DEFAULT_COORDINATOR_TEMPLATE_NAME)) {
5224
+ return {
5225
+ agentId: coordinator?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME,
5226
+ agentRole: coordinator?.role ?? "COO"
5227
+ };
5228
+ }
5229
+ if (sessionName.includes("-")) {
5230
+ const prefix = sessionName.split("-")[0] ?? "";
5231
+ const employee = resolveEmployeeFromSessionPrefix(prefix, employees);
5232
+ if (employee) return employee;
5233
+ const legacy = prefix.match(/^([a-zA-Z]+)\d*$/);
5234
+ if (legacy?.[1] && legacy[1] !== DEFAULT_COORDINATOR_TEMPLATE_NAME) {
5235
+ const emp = getEmployee(employees, legacy[1]);
5236
+ return { agentId: emp?.name ?? legacy[1], agentRole: emp?.role ?? "employee" };
5237
+ }
5238
+ }
5239
+ return null;
5240
+ }
5024
5241
  function getMarkerPath() {
5025
- return path2.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
5242
+ return path3.join(CACHE_DIR, `active-agent-${getSessionKey()}.json`);
5026
5243
  }
5027
5244
  function getActiveAgent() {
5028
5245
  try {
5029
5246
  const markerPath = getMarkerPath();
5030
- const raw = readFileSync2(markerPath, "utf8");
5247
+ const raw = readFileSync3(markerPath, "utf8");
5031
5248
  const data = JSON.parse(raw);
5032
5249
  if (data.agentId) {
5033
5250
  if (data.startedAt) {
5034
5251
  const age = Date.now() - new Date(data.startedAt).getTime();
5035
5252
  if (age > STALE_MS) {
5036
5253
  try {
5037
- unlinkSync(markerPath);
5254
+ unlinkSync2(markerPath);
5038
5255
  } catch {
5039
5256
  }
5040
5257
  } else {
@@ -5053,17 +5270,12 @@ function getActiveAgent() {
5053
5270
  } catch {
5054
5271
  }
5055
5272
  try {
5056
- const sessionName = execSync2(
5273
+ const sessionName = execSync3(
5057
5274
  "tmux display-message -p '#{session_name}' 2>/dev/null",
5058
5275
  { encoding: "utf8", timeout: 2e3 }
5059
5276
  ).trim();
5060
- const empMatch = sessionName.match(/^([a-zA-Z]+)\d*-exe\d+$/);
5061
- if (empMatch && empMatch[1] !== "exe") {
5062
- return { agentId: empMatch[1], agentRole: "employee" };
5063
- }
5064
- if (/^exe\d+$/.test(sessionName)) {
5065
- return { agentId: "exe", agentRole: "COO" };
5066
- }
5277
+ const resolved = resolveActiveAgentFromTmuxSession(sessionName);
5278
+ if (resolved) return resolved;
5067
5279
  } catch {
5068
5280
  }
5069
5281
  return {