@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
@@ -31,7 +31,6 @@ var config_exports = {};
31
31
  __export(config_exports, {
32
32
  CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
33
33
  CONFIG_PATH: () => CONFIG_PATH,
34
- COO_AGENT_NAME: () => COO_AGENT_NAME,
35
34
  CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
36
35
  DB_PATH: () => DB_PATH,
37
36
  EXE_AI_DIR: () => EXE_AI_DIR,
@@ -187,7 +186,7 @@ async function loadConfigFrom(configPath) {
187
186
  return { ...DEFAULT_CONFIG };
188
187
  }
189
188
  }
190
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
189
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
191
190
  var init_config = __esm({
192
191
  "src/lib/config.ts"() {
193
192
  "use strict";
@@ -195,7 +194,6 @@ var init_config = __esm({
195
194
  DB_PATH = path.join(EXE_AI_DIR, "memories.db");
196
195
  MODELS_DIR = path.join(EXE_AI_DIR, "models");
197
196
  CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
198
- COO_AGENT_NAME = "exe";
199
197
  LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
200
198
  CURRENT_CONFIG_VERSION = 1;
201
199
  DEFAULT_CONFIG = {
@@ -231,13 +229,7 @@ var init_config = __esm({
231
229
  wikiUrl: "",
232
230
  wikiApiKey: "",
233
231
  wikiSyncIntervalMs: 30 * 60 * 1e3,
234
- wikiWorkspaceMapping: {
235
- exe: "Executive",
236
- yoshi: "Engineering",
237
- mari: "Marketing",
238
- tom: "Engineering",
239
- sasha: "Production"
240
- },
232
+ wikiWorkspaceMapping: {},
241
233
  wikiAutoUpdate: true,
242
234
  wikiAutoUpdateThreshold: 0.5,
243
235
  wikiAutoUpdateCreateNew: true,
@@ -280,6 +272,22 @@ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as r
280
272
  import { execSync } from "child_process";
281
273
  import path2 from "path";
282
274
  import os2 from "os";
275
+ function normalizeRole(role) {
276
+ return (role ?? "").trim().toLowerCase();
277
+ }
278
+ function isCoordinatorRole(role) {
279
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
280
+ }
281
+ function getCoordinatorEmployee(employees) {
282
+ return employees.find((e) => isCoordinatorRole(e.role));
283
+ }
284
+ function getCoordinatorName(employees = loadEmployeesSync()) {
285
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
286
+ }
287
+ function isCoordinatorName(agentName, employees = loadEmployeesSync()) {
288
+ if (!agentName) return false;
289
+ return agentName.toLowerCase() === getCoordinatorName(employees).toLowerCase();
290
+ }
283
291
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
284
292
  if (!existsSync2(employeesPath)) {
285
293
  return [];
@@ -352,12 +360,14 @@ function registerBinSymlinks(name) {
352
360
  }
353
361
  return { created, skipped, errors };
354
362
  }
355
- var EMPLOYEES_PATH, MULTI_INSTANCE_ROLES;
363
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE, MULTI_INSTANCE_ROLES;
356
364
  var init_employees = __esm({
357
365
  "src/lib/employees.ts"() {
358
366
  "use strict";
359
367
  init_config();
360
368
  EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
369
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
370
+ COORDINATOR_ROLE = "COO";
361
371
  MULTI_INSTANCE_ROLES = /* @__PURE__ */ new Set(["principal engineer", "content production specialist", "staff code reviewer"]);
362
372
  }
363
373
  });
@@ -401,7 +411,7 @@ function wrapWithRetry(client) {
401
411
  return (sql) => retryOnBusy(() => target.execute(sql), "execute");
402
412
  }
403
413
  if (prop === "batch") {
404
- return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
414
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
405
415
  }
406
416
  return Reflect.get(target, prop, receiver);
407
417
  }
@@ -418,6 +428,17 @@ var init_db_retry = __esm({
418
428
  });
419
429
 
420
430
  // src/lib/database.ts
431
+ var database_exports = {};
432
+ __export(database_exports, {
433
+ disposeDatabase: () => disposeDatabase,
434
+ disposeTurso: () => disposeTurso,
435
+ ensureSchema: () => ensureSchema,
436
+ getClient: () => getClient,
437
+ getRawClient: () => getRawClient,
438
+ initDatabase: () => initDatabase,
439
+ initTurso: () => initTurso,
440
+ isInitialized: () => isInitialized
441
+ });
421
442
  import { createClient } from "@libsql/client";
422
443
  async function initDatabase(config) {
423
444
  if (_client) {
@@ -553,22 +574,24 @@ async function ensureSchema() {
553
574
  ON behaviors(agent_id, active);
554
575
  `);
555
576
  try {
577
+ const coordinatorName = getCoordinatorName();
556
578
  const existing = await client.execute({
557
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
558
- args: []
579
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
580
+ args: [coordinatorName]
559
581
  });
560
582
  if (Number(existing.rows[0]?.cnt) === 0) {
561
- await client.executeMultiple(`
562
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
563
- VALUES
564
- (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');
565
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
566
- VALUES
567
- (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');
568
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
569
- VALUES
570
- (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');
571
- `);
583
+ const seededAt = "2026-03-25T00:00:00Z";
584
+ for (const [domain, content] of [
585
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
586
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
587
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
588
+ ]) {
589
+ await client.execute({
590
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
591
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
592
+ args: [coordinatorName, domain, content, seededAt, seededAt]
593
+ });
594
+ }
572
595
  }
573
596
  } catch {
574
597
  }
@@ -1260,15 +1283,57 @@ async function ensureSchema() {
1260
1283
  } catch {
1261
1284
  }
1262
1285
  }
1286
+ try {
1287
+ await client.execute({
1288
+ sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
1289
+ args: []
1290
+ });
1291
+ } catch {
1292
+ }
1293
+ try {
1294
+ await client.execute(
1295
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
1296
+ );
1297
+ } catch {
1298
+ }
1299
+ try {
1300
+ await client.execute({
1301
+ sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
1302
+ args: []
1303
+ });
1304
+ } catch {
1305
+ }
1306
+ try {
1307
+ await client.execute(
1308
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
1309
+ );
1310
+ } catch {
1311
+ }
1312
+ try {
1313
+ await client.execute({
1314
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
1315
+ args: []
1316
+ });
1317
+ } catch {
1318
+ }
1319
+ }
1320
+ async function disposeDatabase() {
1321
+ if (_client) {
1322
+ _client.close();
1323
+ _client = null;
1324
+ _resilientClient = null;
1325
+ }
1263
1326
  }
1264
- var _client, _resilientClient, initTurso;
1327
+ var _client, _resilientClient, initTurso, disposeTurso;
1265
1328
  var init_database = __esm({
1266
1329
  "src/lib/database.ts"() {
1267
1330
  "use strict";
1268
1331
  init_db_retry();
1332
+ init_employees();
1269
1333
  _client = null;
1270
1334
  _resilientClient = null;
1271
1335
  initTurso = initDatabase;
1336
+ disposeTurso = disposeDatabase;
1272
1337
  }
1273
1338
  });
1274
1339
 
@@ -1283,26 +1348,26 @@ var init_platform_procedures = __esm({
1283
1348
  title: "What is exe-os \u2014 the operating model every agent must understand",
1284
1349
  domain: "architecture",
1285
1350
  priority: "p0",
1286
- 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."
1351
+ 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."
1287
1352
  },
1288
1353
  {
1289
1354
  title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
1290
1355
  domain: "architecture",
1291
1356
  priority: "p0",
1292
- 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."
1357
+ 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."
1293
1358
  },
1294
1359
  {
1295
- title: "Sessions explained \u2014 what exeN means and how projects work",
1360
+ title: "Sessions explained \u2014 coordinator session names and projects",
1296
1361
  domain: "architecture",
1297
1362
  priority: "p0",
1298
- 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."
1363
+ 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."
1299
1364
  },
1300
1365
  // --- Hierarchy and dispatch ---
1301
1366
  {
1302
1367
  title: "Chain of command \u2014 who talks to whom",
1303
1368
  domain: "workflow",
1304
1369
  priority: "p0",
1305
- 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."
1370
+ 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."
1306
1371
  },
1307
1372
  {
1308
1373
  title: "Single dispatch path \u2014 create_task only",
@@ -1312,30 +1377,30 @@ var init_platform_procedures = __esm({
1312
1377
  },
1313
1378
  // --- Session isolation ---
1314
1379
  {
1315
- title: "Session scoping \u2014 stay in your exe boundary",
1380
+ title: "Session scoping \u2014 stay in your coordinator boundary",
1316
1381
  domain: "security",
1317
1382
  priority: "p0",
1318
- 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."
1383
+ 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."
1319
1384
  },
1320
1385
  {
1321
1386
  title: "Session isolation \u2014 never touch another session's work",
1322
1387
  domain: "workflow",
1323
1388
  priority: "p0",
1324
- 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.`
1389
+ 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."
1325
1390
  },
1326
1391
  // --- Engineering: session scoping in code ---
1327
1392
  {
1328
1393
  title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
1329
1394
  domain: "architecture",
1330
1395
  priority: "p0",
1331
- 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."
1396
+ 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."
1332
1397
  },
1333
1398
  // --- Hard constraints ---
1334
1399
  {
1335
1400
  title: "What you CANNOT do in exe-os \u2014 hard constraints",
1336
1401
  domain: "security",
1337
1402
  priority: "p0",
1338
- 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."
1403
+ 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."
1339
1404
  },
1340
1405
  // --- Operations ---
1341
1406
  {
@@ -1752,7 +1817,11 @@ async function ensureShardSchema(client) {
1752
1817
  "ALTER TABLE memories ADD COLUMN source_path TEXT",
1753
1818
  "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
1754
1819
  "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
1755
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
1820
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
1821
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1822
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1823
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1824
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT"
1756
1825
  ]) {
1757
1826
  try {
1758
1827
  await client.execute(col);
@@ -3271,6 +3340,36 @@ async function listTasks(input) {
3271
3340
  tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
3272
3341
  }));
3273
3342
  }
3343
+ function isTmuxSessionAlive(identifier) {
3344
+ if (!identifier || identifier === "unknown") return true;
3345
+ try {
3346
+ if (identifier.startsWith("%")) {
3347
+ const output = execSync5("tmux list-panes -a -F '#{pane_id}'", {
3348
+ timeout: 2e3,
3349
+ encoding: "utf8",
3350
+ stdio: ["pipe", "pipe", "pipe"]
3351
+ });
3352
+ return output.split("\n").some((l) => l.trim() === identifier);
3353
+ } else {
3354
+ execSync5(`tmux has-session -t ${JSON.stringify(identifier)}`, {
3355
+ timeout: 2e3,
3356
+ stdio: ["pipe", "pipe", "pipe"]
3357
+ });
3358
+ return true;
3359
+ }
3360
+ } catch {
3361
+ if (identifier.startsWith("%")) return true;
3362
+ try {
3363
+ execSync5("tmux list-sessions", {
3364
+ timeout: 2e3,
3365
+ stdio: ["pipe", "pipe", "pipe"]
3366
+ });
3367
+ return false;
3368
+ } catch {
3369
+ return true;
3370
+ }
3371
+ }
3372
+ }
3274
3373
  function checkStaleCompletion(taskContext, taskCreatedAt) {
3275
3374
  if (!taskContext) return null;
3276
3375
  if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
@@ -3333,13 +3432,59 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
3333
3432
  });
3334
3433
  if (claim.rowsAffected === 0) {
3335
3434
  const current = await client.execute({
3336
- sql: "SELECT status, assigned_tmux FROM tasks WHERE id = ?",
3435
+ sql: "SELECT status, assigned_tmux, assigned_by FROM tasks WHERE id = ?",
3337
3436
  args: [taskId]
3338
3437
  });
3339
3438
  const cur = current.rows[0];
3340
- const status = cur?.status ?? "unknown";
3341
- const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
3342
- throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
3439
+ const curStatus = cur?.status ?? "unknown";
3440
+ const claimedBySession = cur?.assigned_tmux ?? "";
3441
+ const assignedBy = cur?.assigned_by ?? "";
3442
+ if (curStatus === "in_progress" && claimedBySession && !isTmuxSessionAlive(claimedBySession)) {
3443
+ process.stderr.write(
3444
+ `[tasks] Auto-releasing dead claim on ${taskId} (was ${claimedBySession})
3445
+ `
3446
+ );
3447
+ await client.execute({
3448
+ sql: "UPDATE tasks SET status = 'open', assigned_tmux = NULL, updated_at = ? WHERE id = ?",
3449
+ args: [now, taskId]
3450
+ });
3451
+ const retried = await client.execute({
3452
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ? AND status = 'open'`,
3453
+ args: [tmuxSession, now, taskId]
3454
+ });
3455
+ if (retried.rowsAffected > 0) {
3456
+ try {
3457
+ await writeCheckpoint({
3458
+ taskId,
3459
+ step: "reclaimed_dead_session",
3460
+ contextSummary: `Task reclaimed after dead session ${claimedBySession} released.`
3461
+ });
3462
+ } catch {
3463
+ }
3464
+ return { row, taskFile, now, taskId };
3465
+ }
3466
+ }
3467
+ if (curStatus === "in_progress" && input.callerAgentId && (input.callerAgentId === assignedBy || input.callerAgentId === "exe")) {
3468
+ process.stderr.write(
3469
+ `[tasks] Assigner override: ${input.callerAgentId} reclaiming ${taskId}
3470
+ `
3471
+ );
3472
+ await client.execute({
3473
+ sql: `UPDATE tasks SET status = 'in_progress', assigned_tmux = ?, updated_at = ? WHERE id = ?`,
3474
+ args: [tmuxSession, now, taskId]
3475
+ });
3476
+ try {
3477
+ await writeCheckpoint({
3478
+ taskId,
3479
+ step: "assigner_override",
3480
+ contextSummary: `Task force-reclaimed by assigner ${input.callerAgentId}.`
3481
+ });
3482
+ } catch {
3483
+ }
3484
+ return { row, taskFile, now, taskId };
3485
+ }
3486
+ const claimedBy = claimedBySession ? ` (claimed by ${claimedBySession})` : "";
3487
+ throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${curStatus}${claimedBy}`);
3343
3488
  }
3344
3489
  try {
3345
3490
  await writeCheckpoint({
@@ -3437,7 +3582,7 @@ var init_tasks_crud = __esm({
3437
3582
  "use strict";
3438
3583
  init_database();
3439
3584
  init_task_scope();
3440
- DELEGATION_KEYWORDS = /parallel|delegate|wave|tom\d*-exe/i;
3585
+ DELEGATION_KEYWORDS = /parallel|delegate|wave|worktree|multi-instance/i;
3441
3586
  TASK_ALREADY_CLAIMED_PREFIX = "TASK_ALREADY_CLAIMED";
3442
3587
  }
3443
3588
  });
@@ -3803,7 +3948,7 @@ function findSessionForProject(projectName) {
3803
3948
  const sessions = listSessions();
3804
3949
  for (const s of sessions) {
3805
3950
  const proj = s.projectDir.split("/").filter(Boolean).pop();
3806
- if (proj === projectName && s.agentId === "exe") return s;
3951
+ if (proj === projectName && (s.agentId === "exe" || isCoordinatorName(s.agentId))) return s;
3807
3952
  }
3808
3953
  return null;
3809
3954
  }
@@ -3843,12 +3988,13 @@ var init_session_scope = __esm({
3843
3988
  init_session_registry();
3844
3989
  init_project_name();
3845
3990
  init_tmux_routing();
3991
+ init_employees();
3846
3992
  }
3847
3993
  });
3848
3994
 
3849
3995
  // src/lib/tasks-notify.ts
3850
3996
  async function dispatchTaskToEmployee(input) {
3851
- if (input.assignedTo === "exe") return { dispatched: "skipped" };
3997
+ if (input.assignedTo === "exe" || isCoordinatorName(input.assignedTo)) return { dispatched: "skipped" };
3852
3998
  let crossProject = false;
3853
3999
  if (input.projectName) {
3854
4000
  try {
@@ -4291,6 +4437,24 @@ async function updateTask(input) {
4291
4437
  });
4292
4438
  } catch {
4293
4439
  }
4440
+ const assignedAgent = String(row.assigned_to);
4441
+ if (!isCoordinatorName(assignedAgent)) {
4442
+ try {
4443
+ const draftClient = getClient();
4444
+ if (input.status === "done") {
4445
+ await draftClient.execute({
4446
+ sql: `UPDATE memories SET draft = 0 WHERE agent_id = ? AND draft = 1`,
4447
+ args: [assignedAgent]
4448
+ });
4449
+ } else if (input.status === "cancelled") {
4450
+ await draftClient.execute({
4451
+ sql: `DELETE FROM memories WHERE agent_id = ? AND draft = 1`,
4452
+ args: [assignedAgent]
4453
+ });
4454
+ }
4455
+ } catch {
4456
+ }
4457
+ }
4294
4458
  try {
4295
4459
  const client = getClient();
4296
4460
  const cascaded = await client.execute({
@@ -4309,8 +4473,8 @@ async function updateTask(input) {
4309
4473
  }
4310
4474
  const isTerminal = input.status === "done" || input.status === "needs_review";
4311
4475
  if (isTerminal) {
4312
- const isExe = String(row.assigned_to) === "exe";
4313
- if (!isExe) {
4476
+ const isCoordinator = String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to));
4477
+ if (!isCoordinator) {
4314
4478
  notifyTaskDone();
4315
4479
  }
4316
4480
  await markTaskNotificationsRead(taskFile);
@@ -4334,7 +4498,7 @@ async function updateTask(input) {
4334
4498
  }
4335
4499
  }
4336
4500
  }
4337
- if (input.status === "done" && String(row.assigned_to) !== "exe" && !process.env.VITEST) {
4501
+ if (input.status === "done" && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to)) && !process.env.VITEST) {
4338
4502
  Promise.resolve().then(() => (init_skill_learning(), skill_learning_exports)).then(
4339
4503
  ({ captureAndLearn: captureAndLearn2 }) => captureAndLearn2({
4340
4504
  taskId,
@@ -4350,7 +4514,7 @@ async function updateTask(input) {
4350
4514
  });
4351
4515
  }
4352
4516
  let nextTask;
4353
- if (isTerminal && String(row.assigned_to) !== "exe") {
4517
+ if (isTerminal && String(row.assigned_to) !== "exe" && !isCoordinatorName(String(row.assigned_to))) {
4354
4518
  try {
4355
4519
  nextTask = await findNextTask(String(row.assigned_to));
4356
4520
  } catch {
@@ -4377,12 +4541,14 @@ async function updateTask(input) {
4377
4541
  async function deleteTask(taskId, baseDir) {
4378
4542
  const client = getClient();
4379
4543
  const { taskFile, assignedTo, assignedBy, taskSlug } = await deleteTaskCore(taskId, baseDir);
4380
- const reviewer = assignedBy || "exe";
4544
+ const coordinatorName = getCoordinatorName();
4545
+ const reviewer = assignedBy || coordinatorName;
4381
4546
  const reviewSlug = `review-${assignedTo}-${taskSlug}`;
4382
4547
  const reviewFile = `exe/${reviewer}/${reviewSlug}.md`;
4548
+ const legacyReviewFile = `exe/${coordinatorName}/${reviewSlug}.md`;
4383
4549
  await client.execute({
4384
- sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ?",
4385
- args: [reviewFile, `exe/exe/${reviewSlug}.md`]
4550
+ sql: "DELETE FROM tasks WHERE task_file = ? OR task_file = ? OR task_file = ?",
4551
+ args: [reviewFile, legacyReviewFile, `exe/exe/${reviewSlug}.md`]
4386
4552
  });
4387
4553
  await markAsReadByTaskFile(taskFile);
4388
4554
  await markAsReadByTaskFile(reviewFile);
@@ -4394,6 +4560,7 @@ var init_tasks = __esm({
4394
4560
  init_config();
4395
4561
  init_notifications();
4396
4562
  init_state_bus();
4563
+ init_employees();
4397
4564
  init_tasks_crud();
4398
4565
  init_tasks_review();
4399
4566
  init_tasks_crud();
@@ -4479,7 +4646,7 @@ function _resetLastRelaunchCache() {
4479
4646
  }
4480
4647
  async function lastResumeCreatedAtMs(agentId) {
4481
4648
  const client = getClient();
4482
- const cmScope = sessionScopeFilter();
4649
+ const cmScope = sessionScopeFilter(null);
4483
4650
  const result = await client.execute({
4484
4651
  sql: `SELECT MAX(created_at) AS last_created_at
4485
4652
  FROM tasks
@@ -4504,7 +4671,7 @@ async function createOrRefreshResumeTask(agentId, projectDir, openTasks) {
4504
4671
  const client = getClient();
4505
4672
  const now = (/* @__PURE__ */ new Date()).toISOString();
4506
4673
  const context = buildResumeContext(agentId, openTasks);
4507
- const rdScope = sessionScopeFilter();
4674
+ const rdScope = sessionScopeFilter(null);
4508
4675
  const existing = await client.execute({
4509
4676
  sql: `SELECT id FROM tasks
4510
4677
  WHERE assigned_to = ?
@@ -4538,7 +4705,7 @@ async function pollCapacityDead() {
4538
4705
  const transport = getTransport();
4539
4706
  const relaunched = [];
4540
4707
  const registered = listSessions().filter(
4541
- (s) => s.agentId !== "exe"
4708
+ (s) => s.agentId !== "exe" && !isCoordinatorName(s.agentId)
4542
4709
  );
4543
4710
  if (registered.length === 0) return [];
4544
4711
  let liveSessions;
@@ -4598,7 +4765,7 @@ async function pollCapacityDead() {
4598
4765
  reason: "capacity"
4599
4766
  });
4600
4767
  const client = getClient();
4601
- const rlScope = sessionScopeFilter();
4768
+ const rlScope = sessionScopeFilter(null);
4602
4769
  const openTasks = await client.execute({
4603
4770
  sql: `SELECT id, title, priority, task_file, status
4604
4771
  FROM tasks
@@ -4652,6 +4819,7 @@ var init_capacity_monitor = __esm({
4652
4819
  init_session_kill_telemetry();
4653
4820
  init_tmux_routing();
4654
4821
  init_task_scope();
4822
+ init_employees();
4655
4823
  CAPACITY_PATTERNS = [
4656
4824
  /conversation is too long/i,
4657
4825
  /maximum context length/i,
@@ -4801,7 +4969,7 @@ function employeeSessionName(employee, exeSession, instance) {
4801
4969
  exeSession = root;
4802
4970
  } else {
4803
4971
  throw new Error(
4804
- `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
4972
+ `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
4805
4973
  );
4806
4974
  }
4807
4975
  }
@@ -4821,8 +4989,10 @@ function parseParentExe(sessionName, agentId) {
4821
4989
  return match?.[1] ?? null;
4822
4990
  }
4823
4991
  function extractRootExe(name) {
4824
- const match = name.match(/(exe\d+)$/);
4825
- return match?.[1] ?? null;
4992
+ if (!name) return null;
4993
+ if (!name.includes("-")) return name;
4994
+ const parts = name.split("-").filter(Boolean);
4995
+ return parts.length > 0 ? parts[parts.length - 1] : null;
4826
4996
  }
4827
4997
  function registerParentExe(sessionKey, parentExe, dispatchedBy) {
4828
4998
  if (!existsSync12(SESSION_CACHE)) {
@@ -4967,12 +5137,14 @@ function isSessionBusy(sessionName) {
4967
5137
  return state === "thinking" || state === "tool";
4968
5138
  }
4969
5139
  function isExeSession(sessionName) {
4970
- return /^exe\d*$/.test(sessionName);
5140
+ const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
5141
+ const coordinatorName = getCoordinatorName();
5142
+ return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
4971
5143
  }
4972
5144
  function sendIntercom(targetSession) {
4973
5145
  const transport = getTransport();
4974
5146
  if (isExeSession(targetSession)) {
4975
- logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
5147
+ logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
4976
5148
  return "skipped_exe";
4977
5149
  }
4978
5150
  if (isDebounced(targetSession)) {
@@ -5024,7 +5196,7 @@ function notifyParentExe(sessionKey) {
5024
5196
  if (result === "failed") {
5025
5197
  const rootExe = resolveExeSession();
5026
5198
  if (rootExe && rootExe !== target) {
5027
- process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
5199
+ process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root coordinator session ${rootExe}
5028
5200
  `);
5029
5201
  const fallback = sendIntercom(rootExe);
5030
5202
  return fallback !== "failed";
@@ -5034,8 +5206,8 @@ function notifyParentExe(sessionKey) {
5034
5206
  return true;
5035
5207
  }
5036
5208
  function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5037
- if (employeeName === "exe") {
5038
- return { status: "failed", sessionName: "", error: "exe is the COO, not a dispatchable employee" };
5209
+ if (employeeName === "exe" || isCoordinatorName(employeeName)) {
5210
+ return { status: "failed", sessionName: "", error: "The COO is not a dispatchable employee" };
5039
5211
  }
5040
5212
  try {
5041
5213
  assertEmployeeLimitSync();
@@ -5044,8 +5216,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5044
5216
  return { status: "failed", sessionName: "", error: err.message };
5045
5217
  }
5046
5218
  }
5047
- if (/-exe\d*$/.test(employeeName)) {
5048
- const bare = employeeName.replace(/-exe\d*$/, "").replace(/\d+$/, "");
5219
+ if (employeeName.includes("-")) {
5220
+ const bare = employeeName.split("-")[0].replace(/\d+$/, "");
5049
5221
  return {
5050
5222
  status: "failed",
5051
5223
  sessionName: "",
@@ -5064,7 +5236,7 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
5064
5236
  return {
5065
5237
  status: "failed",
5066
5238
  sessionName: "",
5067
- error: `Invalid exeSession "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name (e.g., "exe1", "work", "yoda1")`
5239
+ error: `Invalid coordinator session "${exeSession}" \u2014 contains a dash but no recognizable root session. Pass a root session name.`
5068
5240
  };
5069
5241
  }
5070
5242
  }
@@ -5221,8 +5393,8 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
5221
5393
  const ctxContent = [
5222
5394
  `## Session Context`,
5223
5395
  `You are running in tmux session: ${sessionName}.`,
5224
- `Your parent exe session is ${exeSession}.`,
5225
- `Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
5396
+ `Your parent coordinator session is ${exeSession}.`,
5397
+ `Your employees (if any) use the -${exeSession} suffix.`
5226
5398
  ].join("\n");
5227
5399
  writeFileSync6(ctxFile, ctxContent);
5228
5400
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
@@ -5326,6 +5498,7 @@ var init_tmux_routing = __esm({
5326
5498
  init_provider_table();
5327
5499
  init_intercom_queue();
5328
5500
  init_plan_limits();
5501
+ init_employees();
5329
5502
  SPAWN_LOCK_DIR = path15.join(os7.homedir(), ".exe-os", "spawn-locks");
5330
5503
  SESSION_CACHE = path15.join(os7.homedir(), ".exe-os", "session-cache");
5331
5504
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
@@ -5884,6 +6057,11 @@ async function cloudSync(config) {
5884
6057
  } catch {
5885
6058
  throw new Error("[cloud-sync] Database not initialized. Call initStore() before cloudSync().");
5886
6059
  }
6060
+ try {
6061
+ const { getRawClient: getRawClient2 } = await Promise.resolve().then(() => (init_database(), database_exports));
6062
+ await getRawClient2().execute("PRAGMA wal_checkpoint(PASSIVE)");
6063
+ } catch {
6064
+ }
5887
6065
  try {
5888
6066
  await client.execute(
5889
6067
  "CREATE TABLE IF NOT EXISTS sync_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL)"
@@ -6926,13 +7104,13 @@ Ethos:
6926
7104
  - Founder zero-ego. Distributors and customers are the loudest voice.
6927
7105
  - Crypto values: big companies should not own consumer/SMB AI.
6928
7106
 
6929
- 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.
7107
+ 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 the COO before proceeding.
6930
7108
 
6931
7109
  Always reference .planning/ARCHITECTURE.md and .planning/PROJECT.md as source of truth for all architectural and product decisions.
6932
7110
 
6933
7111
  OPERATING PROCEDURES (mandatory for all employees):
6934
7112
 
6935
- You report to the COO. All work flows through exe. These procedures are non-negotiable.
7113
+ You report to the COO. All work flows through the COO. These procedures are non-negotiable.
6936
7114
 
6937
7115
  1. BEFORE starting work:
6938
7116
  - 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.
@@ -6957,15 +7135,15 @@ You report to the COO. All work flows through exe. These procedures are non-nego
6957
7135
  - Include what was done, decisions made, and any issues
6958
7136
  - 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.
6959
7137
  - NEVER let a failed commit, a loop, or an error prevent you from calling update_task(done).
6960
- - Do NOT use close_task \u2014 that is reserved for reviewers (exe) to finalize after review.
7138
+ - Do NOT use close_task \u2014 that is reserved for reviewers to finalize after review.
6961
7139
 
6962
7140
  4. AFTER update_task(done) \u2014 COMMIT (best-effort, do NOT let this block):
6963
7141
  - If your task changed system structure, update exe/ARCHITECTURE.md first.
6964
7142
  - 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.
6965
7143
  - If you are NOT in a git repo, skip entirely. NEVER run \`git init\`.
6966
7144
  - If the commit fails, note it but move on \u2014 the work is already marked done via update_task.
6967
- - Do NOT push \u2014 exe reviews commits and decides what to push.
6968
- - 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.
7145
+ - Do NOT push \u2014 the COO reviews commits and decides what to push.
7146
+ - NEVER run \`git checkout main\`. You work in your own git worktree on a feature branch. The COO stays on main and merges PRs. Switching branches in a shared repo stomps other agents' work.
6969
7147
 
6970
7148
  5. AFTER commit \u2014 REPORT (best-effort):
6971
7149
  Use store_memory to write a structured summary. Include: project name, what was done,
@@ -6979,7 +7157,7 @@ You report to the COO. All work flows through exe. These procedures are non-nego
6979
7157
 
6980
7158
  7. AFTER reporting \u2014 CHECK FOR NEXT WORK (mandatory):
6981
7159
  - 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.
6982
- - 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.
7160
+ - 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 the COO immediately. Blocked tasks sitting >24h without action is a pipeline failure.
6983
7161
  - Then: re-read your task folder: exe/<your-name>/
6984
7162
  - If there are more open tasks, start the next highest-priority one (go to step 1)
6985
7163
  - If no more open tasks AND no pending reviews AND no blocked tasks you can fix, tell the user: "All tasks complete. Anything else?"
@@ -6996,7 +7174,7 @@ DO NOT keep working degraded. Instead:
6996
7174
  Format the text as: "CONTEXT CHECKPOINT [<task-id>]: <summary>"
6997
7175
  Include: task ID + title, what you completed, what's left, open decisions or blockers, key file paths.
6998
7176
 
6999
- 2. Send intercom to exe to trigger kill + relaunch:
7177
+ 2. Send intercom to the COO session to trigger kill + relaunch:
7000
7178
  MY_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
7001
7179
  EXE_SESSION="\${MY_SESSION#\${AGENT_ID}-}"
7002
7180
  tmux send-keys -t "$EXE_SESSION" "/exe-intercom context-full: \${AGENT_ID} hit capacity. Checkpoint saved. Resume task <task-id>." Enter
@@ -7004,8 +7182,8 @@ DO NOT keep working degraded. Instead:
7004
7182
  3. Stop working immediately. Do not attempt to continue with degraded context.
7005
7183
 
7006
7184
  COMMUNICATION CHAIN \u2014 who you talk to:
7007
- - You report to the COO. Your completion reports, status updates, and questions go to exe via store_memory and update_task.
7008
- - 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.
7185
+ - You report to the COO. Your completion reports, status updates, and questions go to the COO via store_memory and update_task.
7186
+ - Do NOT address the human user directly for decisions, permissions, or status updates. That's the COO's job. The user talks to the COO; the COO talks to you.
7009
7187
  - Exception: if the user sends you a direct message in your tmux window, respond to them. But default to reporting through exe.
7010
7188
 
7011
7189
  SKILL CAPTURE (encouraged, not mandatory):
@@ -7054,18 +7232,15 @@ ${BASE_OPERATING_PROCEDURES}`;
7054
7232
  }
7055
7233
 
7056
7234
  // src/lib/status-brief.ts
7057
- var EMPLOYEE_EMOJIS = {
7058
- exe: "\u{1F3AF}",
7059
- // 🎯
7060
- yoshi: "\u26A1",
7061
- //
7062
- mari: "\u{1F3A8}",
7063
- // 🎨
7064
- tom: "\u{1F529}",
7065
- // 🔩
7066
- sasha: "\u{1F4F8}"
7067
- // 📸
7068
- };
7235
+ function roleIcon(role) {
7236
+ const lower = role.toLowerCase();
7237
+ if (lower === "coo") return "\u{1F3AF}";
7238
+ if (lower === "cto" || lower.includes("architect")) return "\u26A1";
7239
+ if (lower === "cmo" || lower.includes("marketing")) return "\u{1F3A8}";
7240
+ if (lower.includes("engineer")) return "\u{1F529}";
7241
+ if (lower.includes("content")) return "\u{1F4F8}";
7242
+ return "\u{1F464}";
7243
+ }
7069
7244
  function displayWidth(str) {
7070
7245
  let w = 0;
7071
7246
  for (const ch of str) {
@@ -7155,7 +7330,7 @@ function buildFirstBootBrief(employees, dateStr, sessionTag) {
7155
7330
  bodyLines.push(" \u{1F44B} First time? Here's your team:");
7156
7331
  bodyLines.push("");
7157
7332
  for (const emp of employees) {
7158
- const emoji = EMPLOYEE_EMOJIS[emp.name] ?? "\u{1F464}";
7333
+ const emoji = roleIcon(emp.role);
7159
7334
  const role = emp.role ? ` (${emp.role})` : "";
7160
7335
  bodyLines.push(` ${emoji} ${emp.name}${role}`);
7161
7336
  }
@@ -7288,7 +7463,7 @@ function buildTeam(employees, data) {
7288
7463
  for (const m of data.memoryStats) memMap.set(m.agentId, m.totalMemories);
7289
7464
  }
7290
7465
  for (const emp of employees) {
7291
- const emoji = EMPLOYEE_EMOJIS[emp.name] ?? "\u{1F464}";
7466
+ const emoji = roleIcon(emp.role);
7292
7467
  const memCount = memMap.get(emp.name) ?? 0;
7293
7468
  const memStr = memCount > 0 ? `${memCount.toLocaleString()} memories` : "0 memories";
7294
7469
  const role = emp.role ? ` (${emp.role})` : "";
@@ -7382,6 +7557,7 @@ import path16 from "path";
7382
7557
  init_session_key();
7383
7558
 
7384
7559
  // src/adapters/claude/active-agent.ts
7560
+ init_employees();
7385
7561
  var CACHE_DIR = path16.join(EXE_AI_DIR, "session-cache");
7386
7562
  var STALE_MS = 24 * 60 * 60 * 1e3;
7387
7563
  function getMarkerPath() {
@@ -7419,9 +7595,11 @@ async function boot(options) {
7419
7595
  employees = [DEFAULT_EXE];
7420
7596
  await saveEmployees(employees);
7421
7597
  }
7598
+ const coordinatorEmployee = getCoordinatorEmployee(employees) ?? DEFAULT_EXE;
7599
+ const coordinatorName = coordinatorEmployee.name;
7422
7600
  await initStore();
7423
7601
  cleanupSessionMarkers();
7424
- writeActiveAgent("exe", "COO");
7602
+ writeActiveAgent(coordinatorName, coordinatorEmployee.role);
7425
7603
  let licensePlan;
7426
7604
  let employeeLimit;
7427
7605
  try {
@@ -7447,7 +7625,7 @@ async function boot(options) {
7447
7625
  if (exeWindow) {
7448
7626
  registerSession({
7449
7627
  windowName: exeWindow,
7450
- agentId: "exe",
7628
+ agentId: coordinatorName,
7451
7629
  projectDir: process.cwd(),
7452
7630
  parentExe: null,
7453
7631
  pid: process.ppid,
@@ -7514,7 +7692,7 @@ async function boot(options) {
7514
7692
  const priMatch = content.match(PRIORITY_RE2);
7515
7693
  const priority = priMatch?.[1]?.toLowerCase() ?? "p1";
7516
7694
  const assignedByMatch = content.match(/^\*\*Assigned by:\*\*\s*(\w+)/m);
7517
- const assignedBy = assignedByMatch?.[1] ?? "exe";
7695
+ const assignedBy = assignedByMatch?.[1] ?? coordinatorName;
7518
7696
  const projMatch = content.match(/^\*\*Project:\*\*\s*(.+)/m);
7519
7697
  const projectName = projMatch?.[1]?.trim() ?? getProjectName2(process.cwd());
7520
7698
  const existing = await client.execute({
@@ -7549,11 +7727,11 @@ async function boot(options) {
7549
7727
  try {
7550
7728
  await client.execute({
7551
7729
  sql: `UPDATE tasks SET status = 'done', updated_at = ?
7552
- WHERE assigned_to = 'exe'
7730
+ WHERE (assigned_to = ? OR assigned_to = 'exe')
7553
7731
  AND status = 'in_progress'
7554
7732
  AND task_file LIKE '%review-%'
7555
7733
  AND updated_at < datetime('now', '-1 hour')`,
7556
- args: [(/* @__PURE__ */ new Date()).toISOString()]
7734
+ args: [(/* @__PURE__ */ new Date()).toISOString(), coordinatorName]
7557
7735
  });
7558
7736
  } catch {
7559
7737
  }
@@ -7588,13 +7766,15 @@ async function boot(options) {
7588
7766
  } catch {
7589
7767
  }
7590
7768
  try {
7591
- const exeExeDir = path20.join(process.cwd(), "exe", "exe");
7592
- if (existsSync16(exeExeDir)) {
7593
- for (const f of readdirSync8(exeExeDir)) {
7594
- if (f.startsWith("review-") && f.endsWith(".md")) {
7595
- try {
7596
- unlinkSync9(path20.join(exeExeDir, f));
7597
- } catch {
7769
+ for (const reviewDirName of /* @__PURE__ */ new Set(["exe", coordinatorName])) {
7770
+ const reviewDir = path20.join(process.cwd(), "exe", reviewDirName);
7771
+ if (existsSync16(reviewDir)) {
7772
+ for (const f of readdirSync8(reviewDir)) {
7773
+ if (f.startsWith("review-") && f.endsWith(".md")) {
7774
+ try {
7775
+ unlinkSync9(path20.join(reviewDir, f));
7776
+ } catch {
7777
+ }
7598
7778
  }
7599
7779
  }
7600
7780
  }
@@ -7685,10 +7865,10 @@ async function boot(options) {
7685
7865
  REPLACE(SUBSTR(t.title, INSTR(t.title, 'review-') + 7), SUBSTR(t.title, INSTR(t.title, '-')), '')
7686
7866
  ) as original_assignee
7687
7867
  FROM tasks t
7688
- WHERE t.assigned_to = 'exe' AND t.status IN ('open', 'in_progress')
7868
+ WHERE (t.assigned_to = ? OR t.assigned_to = 'exe') AND t.status IN ('open', 'in_progress')
7689
7869
  AND t.task_file LIKE '%review-%'${revScope.sql}
7690
7870
  ORDER BY t.priority ASC, t.created_at ASC`,
7691
- args: [...revScope.args]
7871
+ args: [coordinatorName, ...revScope.args]
7692
7872
  });
7693
7873
  briefData.pendingReviews = result.rows.map((row) => ({
7694
7874
  title: String(row.title),
@@ -7701,10 +7881,10 @@ async function boot(options) {
7701
7881
  const result = await client.execute({
7702
7882
  sql: `SELECT title, priority, created_at
7703
7883
  FROM tasks
7704
- WHERE assigned_to = 'exe' AND status IN ('open', 'in_progress')
7884
+ WHERE (assigned_to = ? OR assigned_to = 'exe') AND status IN ('open', 'in_progress')
7705
7885
  AND task_file LIKE '%review-%'${revScope.sql}
7706
7886
  ORDER BY priority ASC, created_at ASC`,
7707
- args: [...revScope.args]
7887
+ args: [coordinatorName, ...revScope.args]
7708
7888
  });
7709
7889
  briefData.pendingReviews = result.rows.map((row) => {
7710
7890
  const title = String(row.title);
@@ -7974,17 +8154,17 @@ async function boot(options) {
7974
8154
  }));
7975
8155
  const assignedResult = await client.execute({
7976
8156
  sql: `SELECT COUNT(*) as cnt FROM tasks
7977
- WHERE project_name = ? AND assigned_by = 'exe'
8157
+ WHERE project_name = ? AND (assigned_by = ? OR assigned_by = 'exe')
7978
8158
  AND created_at > datetime('now', '-7 days')${pScope.sql}`,
7979
- args: [p.projectName, ...pScope.args]
8159
+ args: [p.projectName, coordinatorName, ...pScope.args]
7980
8160
  });
7981
8161
  const tasksAssigned = Number(assignedResult.rows[0]?.cnt) || 0;
7982
8162
  if (tasksAssigned > 0) {
7983
- const exeEntry = activity.find((a) => a.name === "exe");
8163
+ const exeEntry = activity.find((a) => a.name === coordinatorName || a.name === "exe");
7984
8164
  if (exeEntry) {
7985
8165
  exeEntry.tasksAssigned = tasksAssigned;
7986
8166
  } else {
7987
- activity.push({ name: "exe", tasksDone: 0, reviewsCleared: 0, tasksAssigned });
8167
+ activity.push({ name: coordinatorName, tasksDone: 0, reviewsCleared: 0, tasksAssigned });
7988
8168
  }
7989
8169
  }
7990
8170
  p.teamActivity = activity;
@@ -8004,7 +8184,7 @@ async function boot(options) {
8004
8184
  const projName = s.projectDir.split("/").pop() ?? "";
8005
8185
  const proj = projects.find((p) => p.projectName === projName);
8006
8186
  if (!proj || proj.sessionName) continue;
8007
- if (s.agentId === "exe") {
8187
+ if (s.agentId === coordinatorName || s.agentId === "exe") {
8008
8188
  proj.sessionName = s.windowName;
8009
8189
  } else if (s.parentExe) {
8010
8190
  proj.sessionName = s.parentExe;
@@ -8293,10 +8473,9 @@ async function boot(options) {
8293
8473
  console.log(brief);
8294
8474
  return;
8295
8475
  }
8296
- const exeEmployee = employees.find((e) => e.name === "exe") ?? DEFAULT_EXE;
8297
- const sessionDir = path20.join(EXE_AI_DIR, "sessions", "exe");
8476
+ const sessionDir = path20.join(EXE_AI_DIR, "sessions", coordinatorName);
8298
8477
  await mkdir5(sessionDir, { recursive: true });
8299
- const claudeMdContent = `${getSessionPrompt(exeEmployee.systemPrompt)}
8478
+ const claudeMdContent = `${getSessionPrompt(coordinatorEmployee.systemPrompt)}
8300
8479
 
8301
8480
  ---
8302
8481