@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
@@ -68,7 +68,7 @@ function wrapWithRetry(client) {
68
68
  return (sql) => retryOnBusy(() => target.execute(sql), "execute");
69
69
  }
70
70
  if (prop === "batch") {
71
- return (stmts) => retryOnBusy(() => target.batch(stmts), "batch");
71
+ return (stmts, mode) => retryOnBusy(() => target.batch(stmts, mode), "batch");
72
72
  }
73
73
  return Reflect.get(target, prop, receiver);
74
74
  }
@@ -84,6 +84,301 @@ var init_db_retry = __esm({
84
84
  }
85
85
  });
86
86
 
87
+ // src/lib/config.ts
88
+ var config_exports = {};
89
+ __export(config_exports, {
90
+ CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
91
+ CONFIG_PATH: () => CONFIG_PATH,
92
+ CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
93
+ DB_PATH: () => DB_PATH,
94
+ EXE_AI_DIR: () => EXE_AI_DIR,
95
+ LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
96
+ MODELS_DIR: () => MODELS_DIR,
97
+ loadConfig: () => loadConfig,
98
+ loadConfigFrom: () => loadConfigFrom,
99
+ loadConfigSync: () => loadConfigSync,
100
+ migrateConfig: () => migrateConfig,
101
+ saveConfig: () => saveConfig
102
+ });
103
+ import { readFile, writeFile, mkdir, chmod } from "fs/promises";
104
+ import { readFileSync, existsSync, renameSync } from "fs";
105
+ import path from "path";
106
+ import os from "os";
107
+ function resolveDataDir() {
108
+ if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
109
+ if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
110
+ const newDir = path.join(os.homedir(), ".exe-os");
111
+ const legacyDir = path.join(os.homedir(), ".exe-mem");
112
+ if (!existsSync(newDir) && existsSync(legacyDir)) {
113
+ try {
114
+ renameSync(legacyDir, newDir);
115
+ process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
116
+ `);
117
+ } catch {
118
+ return legacyDir;
119
+ }
120
+ }
121
+ return newDir;
122
+ }
123
+ function migrateLegacyConfig(raw) {
124
+ if ("r2" in raw) {
125
+ process.stderr.write(
126
+ "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
127
+ );
128
+ delete raw.r2;
129
+ }
130
+ if ("syncIntervalMs" in raw) {
131
+ delete raw.syncIntervalMs;
132
+ }
133
+ return raw;
134
+ }
135
+ function migrateConfig(raw) {
136
+ const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
137
+ let currentVersion = fromVersion;
138
+ let migrated = false;
139
+ if (currentVersion > CURRENT_CONFIG_VERSION) {
140
+ return { config: raw, migrated: false, fromVersion };
141
+ }
142
+ for (const migration of CONFIG_MIGRATIONS) {
143
+ if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
144
+ raw = migration.migrate(raw);
145
+ currentVersion = migration.to;
146
+ migrated = true;
147
+ }
148
+ }
149
+ return { config: raw, migrated, fromVersion };
150
+ }
151
+ function normalizeScalingRoadmap(raw) {
152
+ const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
153
+ const userRoadmap = raw.scalingRoadmap ?? {};
154
+ const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
155
+ if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
156
+ userAuto.enabled = raw.rerankerEnabled;
157
+ }
158
+ raw.scalingRoadmap = {
159
+ ...userRoadmap,
160
+ rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
161
+ };
162
+ }
163
+ function normalizeSessionLifecycle(raw) {
164
+ const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
165
+ const userSL = raw.sessionLifecycle ?? {};
166
+ raw.sessionLifecycle = { ...defaultSL, ...userSL };
167
+ }
168
+ function normalizeAutoUpdate(raw) {
169
+ const defaultAU = DEFAULT_CONFIG.autoUpdate;
170
+ const userAU = raw.autoUpdate ?? {};
171
+ raw.autoUpdate = { ...defaultAU, ...userAU };
172
+ }
173
+ async function loadConfig() {
174
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
175
+ await mkdir(dir, { recursive: true });
176
+ const configPath = path.join(dir, "config.json");
177
+ if (!existsSync(configPath)) {
178
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
179
+ }
180
+ const raw = await readFile(configPath, "utf-8");
181
+ try {
182
+ let parsed = JSON.parse(raw);
183
+ parsed = migrateLegacyConfig(parsed);
184
+ const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
185
+ if (migrated) {
186
+ process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
187
+ `);
188
+ try {
189
+ await writeFile(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
190
+ } catch {
191
+ }
192
+ }
193
+ normalizeScalingRoadmap(migratedCfg);
194
+ normalizeSessionLifecycle(migratedCfg);
195
+ normalizeAutoUpdate(migratedCfg);
196
+ const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
197
+ if (config.dbPath.startsWith("~")) {
198
+ config.dbPath = config.dbPath.replace(/^~/, os.homedir());
199
+ }
200
+ return config;
201
+ } catch {
202
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
203
+ }
204
+ }
205
+ function loadConfigSync() {
206
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
207
+ const configPath = path.join(dir, "config.json");
208
+ if (!existsSync(configPath)) {
209
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
210
+ }
211
+ try {
212
+ const raw = readFileSync(configPath, "utf-8");
213
+ let parsed = JSON.parse(raw);
214
+ parsed = migrateLegacyConfig(parsed);
215
+ const { config: migratedCfg } = migrateConfig(parsed);
216
+ normalizeScalingRoadmap(migratedCfg);
217
+ normalizeSessionLifecycle(migratedCfg);
218
+ normalizeAutoUpdate(migratedCfg);
219
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
220
+ } catch {
221
+ return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
222
+ }
223
+ }
224
+ async function saveConfig(config) {
225
+ const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
226
+ await mkdir(dir, { recursive: true });
227
+ const configPath = path.join(dir, "config.json");
228
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
229
+ if (config.cloud?.apiKey) {
230
+ await chmod(configPath, 384);
231
+ }
232
+ }
233
+ async function loadConfigFrom(configPath) {
234
+ const raw = await readFile(configPath, "utf-8");
235
+ try {
236
+ let parsed = JSON.parse(raw);
237
+ parsed = migrateLegacyConfig(parsed);
238
+ const { config: migratedCfg } = migrateConfig(parsed);
239
+ normalizeScalingRoadmap(migratedCfg);
240
+ normalizeSessionLifecycle(migratedCfg);
241
+ normalizeAutoUpdate(migratedCfg);
242
+ return { ...DEFAULT_CONFIG, ...migratedCfg };
243
+ } catch {
244
+ return { ...DEFAULT_CONFIG };
245
+ }
246
+ }
247
+ var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
248
+ var init_config = __esm({
249
+ "src/lib/config.ts"() {
250
+ "use strict";
251
+ EXE_AI_DIR = resolveDataDir();
252
+ DB_PATH = path.join(EXE_AI_DIR, "memories.db");
253
+ MODELS_DIR = path.join(EXE_AI_DIR, "models");
254
+ CONFIG_PATH = path.join(EXE_AI_DIR, "config.json");
255
+ LEGACY_LANCE_PATH = path.join(EXE_AI_DIR, "local.lance");
256
+ CURRENT_CONFIG_VERSION = 1;
257
+ DEFAULT_CONFIG = {
258
+ config_version: CURRENT_CONFIG_VERSION,
259
+ dbPath: DB_PATH,
260
+ modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
261
+ embeddingDim: 1024,
262
+ batchSize: 20,
263
+ flushIntervalMs: 1e4,
264
+ autoIngestion: true,
265
+ autoRetrieval: true,
266
+ searchMode: "hybrid",
267
+ hookSearchMode: "hybrid",
268
+ fileGrepEnabled: true,
269
+ splashEffect: true,
270
+ consolidationEnabled: true,
271
+ consolidationIntervalMs: 6 * 60 * 60 * 1e3,
272
+ consolidationModel: "claude-haiku-4-5-20251001",
273
+ consolidationMaxCallsPerRun: 20,
274
+ selfQueryRouter: true,
275
+ selfQueryModel: "claude-haiku-4-5-20251001",
276
+ rerankerEnabled: true,
277
+ scalingRoadmap: {
278
+ rerankerAutoTrigger: {
279
+ enabled: true,
280
+ broadQueryMinCardinality: 5e4,
281
+ fetchTopK: 150,
282
+ returnTopK: 5
283
+ }
284
+ },
285
+ graphRagEnabled: true,
286
+ wikiEnabled: false,
287
+ wikiUrl: "",
288
+ wikiApiKey: "",
289
+ wikiSyncIntervalMs: 30 * 60 * 1e3,
290
+ wikiWorkspaceMapping: {},
291
+ wikiAutoUpdate: true,
292
+ wikiAutoUpdateThreshold: 0.5,
293
+ wikiAutoUpdateCreateNew: true,
294
+ skillLearning: true,
295
+ skillThreshold: 3,
296
+ skillModel: "claude-haiku-4-5-20251001",
297
+ exeHeartbeat: {
298
+ enabled: true,
299
+ intervalSeconds: 60,
300
+ staleInProgressThresholdHours: 2
301
+ },
302
+ sessionLifecycle: {
303
+ idleKillEnabled: true,
304
+ idleKillTicksRequired: 3,
305
+ idleKillIntercomAckWindowMs: 1e4,
306
+ maxAutoInstances: 10
307
+ },
308
+ autoUpdate: {
309
+ checkOnBoot: true,
310
+ autoInstall: false,
311
+ checkIntervalMs: 24 * 60 * 60 * 1e3
312
+ }
313
+ };
314
+ CONFIG_MIGRATIONS = [
315
+ {
316
+ from: 0,
317
+ to: 1,
318
+ migrate: (cfg) => {
319
+ cfg.config_version = 1;
320
+ return cfg;
321
+ }
322
+ }
323
+ ];
324
+ }
325
+ });
326
+
327
+ // src/lib/employees.ts
328
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
329
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
330
+ import { execSync } from "child_process";
331
+ import path2 from "path";
332
+ import os2 from "os";
333
+ function normalizeRole(role) {
334
+ return (role ?? "").trim().toLowerCase();
335
+ }
336
+ function isCoordinatorRole(role) {
337
+ return normalizeRole(role) === normalizeRole(COORDINATOR_ROLE);
338
+ }
339
+ function getCoordinatorEmployee(employees) {
340
+ return employees.find((e) => isCoordinatorRole(e.role));
341
+ }
342
+ function getCoordinatorName(employees = loadEmployeesSync()) {
343
+ return getCoordinatorEmployee(employees)?.name ?? DEFAULT_COORDINATOR_TEMPLATE_NAME;
344
+ }
345
+ function isCoordinatorName(agentName2, employees = loadEmployeesSync()) {
346
+ if (!agentName2) return false;
347
+ return agentName2.toLowerCase() === getCoordinatorName(employees).toLowerCase();
348
+ }
349
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
350
+ if (!existsSync2(employeesPath)) {
351
+ return [];
352
+ }
353
+ const raw = await readFile2(employeesPath, "utf-8");
354
+ try {
355
+ return JSON.parse(raw);
356
+ } catch {
357
+ return [];
358
+ }
359
+ }
360
+ function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
361
+ if (!existsSync2(employeesPath)) return [];
362
+ try {
363
+ return JSON.parse(readFileSync2(employeesPath, "utf-8"));
364
+ } catch {
365
+ return [];
366
+ }
367
+ }
368
+ function getEmployee(employees, name) {
369
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
370
+ }
371
+ var EMPLOYEES_PATH, DEFAULT_COORDINATOR_TEMPLATE_NAME, COORDINATOR_ROLE;
372
+ var init_employees = __esm({
373
+ "src/lib/employees.ts"() {
374
+ "use strict";
375
+ init_config();
376
+ EMPLOYEES_PATH = path2.join(EXE_AI_DIR, "exe-employees.json");
377
+ DEFAULT_COORDINATOR_TEMPLATE_NAME = "exe";
378
+ COORDINATOR_ROLE = "COO";
379
+ }
380
+ });
381
+
87
382
  // src/lib/database.ts
88
383
  import { createClient } from "@libsql/client";
89
384
  async function initDatabase(config) {
@@ -217,22 +512,24 @@ async function ensureSchema() {
217
512
  ON behaviors(agent_id, active);
218
513
  `);
219
514
  try {
515
+ const coordinatorName = getCoordinatorName();
220
516
  const existing = await client.execute({
221
- sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = 'exe'",
222
- args: []
517
+ sql: "SELECT COUNT(*) as cnt FROM behaviors WHERE agent_id = ?",
518
+ args: [coordinatorName]
223
519
  });
224
520
  if (Number(existing.rows[0]?.cnt) === 0) {
225
- await client.executeMultiple(`
226
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
227
- VALUES
228
- (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');
229
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
230
- VALUES
231
- (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');
232
- INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
233
- VALUES
234
- (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');
235
- `);
521
+ const seededAt = "2026-03-25T00:00:00Z";
522
+ for (const [domain, content] of [
523
+ ["workflow", `Don't ask "keep going?" \u2014 just keep executing phases/plans autonomously`],
524
+ ["tool-use", "Always use create_task MCP tool, never write .md files directly for task creation"],
525
+ ["workflow", "Auto-start reviewing when idle and reviews are pending \u2014 never ask founder for permission"]
526
+ ]) {
527
+ await client.execute({
528
+ sql: `INSERT INTO behaviors (id, agent_id, project_name, domain, content, active, created_at, updated_at)
529
+ VALUES (hex(randomblob(16)), ?, NULL, ?, ?, 1, ?, ?)`,
530
+ args: [coordinatorName, domain, content, seededAt, seededAt]
531
+ });
532
+ }
236
533
  }
237
534
  } catch {
238
535
  }
@@ -881,306 +1178,92 @@ async function ensureSchema() {
881
1178
  try {
882
1179
  await client.execute({
883
1180
  sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
884
- args: []
885
- });
886
- } catch {
887
- }
888
- try {
889
- await client.execute(
890
- `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
891
- );
892
- } catch {
893
- }
894
- try {
895
- await client.execute({
896
- sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
897
- args: []
898
- });
899
- await client.execute({
900
- sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
901
- args: []
902
- });
903
- } catch {
904
- }
905
- try {
906
- await client.execute({
907
- sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
908
- args: []
909
- });
910
- } catch {
911
- }
912
- try {
913
- await client.execute(
914
- `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
915
- );
916
- } catch {
917
- }
918
- for (const col of [
919
- "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
920
- "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
921
- ]) {
922
- try {
923
- await client.execute(col);
924
- } catch {
925
- }
926
- }
927
- }
928
- var _client, _resilientClient, initTurso;
929
- var init_database = __esm({
930
- "src/lib/database.ts"() {
931
- "use strict";
932
- init_db_retry();
933
- _client = null;
934
- _resilientClient = null;
935
- initTurso = initDatabase;
936
- }
937
- });
938
-
939
- // src/lib/config.ts
940
- var config_exports = {};
941
- __export(config_exports, {
942
- CONFIG_MIGRATIONS: () => CONFIG_MIGRATIONS,
943
- CONFIG_PATH: () => CONFIG_PATH,
944
- COO_AGENT_NAME: () => COO_AGENT_NAME,
945
- CURRENT_CONFIG_VERSION: () => CURRENT_CONFIG_VERSION,
946
- DB_PATH: () => DB_PATH,
947
- EXE_AI_DIR: () => EXE_AI_DIR,
948
- LEGACY_LANCE_PATH: () => LEGACY_LANCE_PATH,
949
- MODELS_DIR: () => MODELS_DIR,
950
- loadConfig: () => loadConfig,
951
- loadConfigFrom: () => loadConfigFrom,
952
- loadConfigSync: () => loadConfigSync,
953
- migrateConfig: () => migrateConfig,
954
- saveConfig: () => saveConfig
955
- });
956
- import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod as chmod2 } from "fs/promises";
957
- import { readFileSync, existsSync as existsSync2, renameSync } from "fs";
958
- import path2 from "path";
959
- import os2 from "os";
960
- function resolveDataDir() {
961
- if (process.env.EXE_OS_DIR) return process.env.EXE_OS_DIR;
962
- if (process.env.EXE_MEM_DIR) return process.env.EXE_MEM_DIR;
963
- const newDir = path2.join(os2.homedir(), ".exe-os");
964
- const legacyDir = path2.join(os2.homedir(), ".exe-mem");
965
- if (!existsSync2(newDir) && existsSync2(legacyDir)) {
966
- try {
967
- renameSync(legacyDir, newDir);
968
- process.stderr.write(`[exe-os] Migrated data directory: ~/.exe-mem \u2192 ~/.exe-os
969
- `);
970
- } catch {
971
- return legacyDir;
972
- }
973
- }
974
- return newDir;
975
- }
976
- function migrateLegacyConfig(raw) {
977
- if ("r2" in raw) {
978
- process.stderr.write(
979
- "[exe-os] Warning: config.json contains deprecated 'r2' field from v1.0. R2 sync has been replaced in v1.1. The 'r2' field will be ignored.\n"
980
- );
981
- delete raw.r2;
1181
+ args: []
1182
+ });
1183
+ } catch {
982
1184
  }
983
- if ("syncIntervalMs" in raw) {
984
- delete raw.syncIntervalMs;
1185
+ try {
1186
+ await client.execute(
1187
+ `CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
1188
+ );
1189
+ } catch {
985
1190
  }
986
- return raw;
987
- }
988
- function migrateConfig(raw) {
989
- const fromVersion = typeof raw.config_version === "number" ? raw.config_version : 0;
990
- let currentVersion = fromVersion;
991
- let migrated = false;
992
- if (currentVersion > CURRENT_CONFIG_VERSION) {
993
- return { config: raw, migrated: false, fromVersion };
1191
+ try {
1192
+ await client.execute({
1193
+ sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
1194
+ args: []
1195
+ });
1196
+ await client.execute({
1197
+ sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
1198
+ args: []
1199
+ });
1200
+ } catch {
994
1201
  }
995
- for (const migration of CONFIG_MIGRATIONS) {
996
- if (currentVersion === migration.from && migration.to <= CURRENT_CONFIG_VERSION) {
997
- raw = migration.migrate(raw);
998
- currentVersion = migration.to;
999
- migrated = true;
1000
- }
1202
+ try {
1203
+ await client.execute({
1204
+ sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
1205
+ args: []
1206
+ });
1207
+ } catch {
1001
1208
  }
1002
- return { config: raw, migrated, fromVersion };
1003
- }
1004
- function normalizeScalingRoadmap(raw) {
1005
- const defaultAuto = DEFAULT_CONFIG.scalingRoadmap.rerankerAutoTrigger;
1006
- const userRoadmap = raw.scalingRoadmap ?? {};
1007
- const userAuto = userRoadmap.rerankerAutoTrigger ?? {};
1008
- if (userAuto.enabled === void 0 && raw.rerankerEnabled !== void 0) {
1009
- userAuto.enabled = raw.rerankerEnabled;
1209
+ try {
1210
+ await client.execute(
1211
+ `CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
1212
+ );
1213
+ } catch {
1010
1214
  }
1011
- raw.scalingRoadmap = {
1012
- ...userRoadmap,
1013
- rerankerAutoTrigger: { ...defaultAuto, ...userAuto }
1014
- };
1015
- }
1016
- function normalizeSessionLifecycle(raw) {
1017
- const defaultSL = DEFAULT_CONFIG.sessionLifecycle;
1018
- const userSL = raw.sessionLifecycle ?? {};
1019
- raw.sessionLifecycle = { ...defaultSL, ...userSL };
1020
- }
1021
- function normalizeAutoUpdate(raw) {
1022
- const defaultAU = DEFAULT_CONFIG.autoUpdate;
1023
- const userAU = raw.autoUpdate ?? {};
1024
- raw.autoUpdate = { ...defaultAU, ...userAU };
1025
- }
1026
- async function loadConfig() {
1027
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
1028
- await mkdir2(dir, { recursive: true });
1029
- const configPath = path2.join(dir, "config.json");
1030
- if (!existsSync2(configPath)) {
1031
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
1215
+ for (const col of [
1216
+ "ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
1217
+ "ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
1218
+ ]) {
1219
+ try {
1220
+ await client.execute(col);
1221
+ } catch {
1222
+ }
1032
1223
  }
1033
- const raw = await readFile2(configPath, "utf-8");
1034
1224
  try {
1035
- let parsed = JSON.parse(raw);
1036
- parsed = migrateLegacyConfig(parsed);
1037
- const { config: migratedCfg, migrated, fromVersion } = migrateConfig(parsed);
1038
- if (migrated) {
1039
- process.stderr.write(`[exe-os] Config migrated from v${fromVersion} to v${migratedCfg.config_version}
1040
- `);
1041
- try {
1042
- await writeFile2(configPath, JSON.stringify(migratedCfg, null, 2) + "\n");
1043
- } catch {
1044
- }
1045
- }
1046
- normalizeScalingRoadmap(migratedCfg);
1047
- normalizeSessionLifecycle(migratedCfg);
1048
- normalizeAutoUpdate(migratedCfg);
1049
- const config = { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
1050
- if (config.dbPath.startsWith("~")) {
1051
- config.dbPath = config.dbPath.replace(/^~/, os2.homedir());
1052
- }
1053
- return config;
1225
+ await client.execute({
1226
+ sql: `ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0`,
1227
+ args: []
1228
+ });
1054
1229
  } catch {
1055
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
1056
1230
  }
1057
- }
1058
- function loadConfigSync() {
1059
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
1060
- const configPath = path2.join(dir, "config.json");
1061
- if (!existsSync2(configPath)) {
1062
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
1231
+ try {
1232
+ await client.execute(
1233
+ `CREATE INDEX IF NOT EXISTS idx_memories_draft ON memories(draft) WHERE draft = 1`
1234
+ );
1235
+ } catch {
1063
1236
  }
1064
1237
  try {
1065
- const raw = readFileSync(configPath, "utf-8");
1066
- let parsed = JSON.parse(raw);
1067
- parsed = migrateLegacyConfig(parsed);
1068
- const { config: migratedCfg } = migrateConfig(parsed);
1069
- normalizeScalingRoadmap(migratedCfg);
1070
- normalizeSessionLifecycle(migratedCfg);
1071
- normalizeAutoUpdate(migratedCfg);
1072
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db"), ...migratedCfg };
1238
+ await client.execute({
1239
+ sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
1240
+ args: []
1241
+ });
1073
1242
  } catch {
1074
- return { ...DEFAULT_CONFIG, dbPath: path2.join(dir, "memories.db") };
1075
1243
  }
1076
- }
1077
- async function saveConfig(config) {
1078
- const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
1079
- await mkdir2(dir, { recursive: true });
1080
- const configPath = path2.join(dir, "config.json");
1081
- await writeFile2(configPath, JSON.stringify(config, null, 2) + "\n");
1082
- if (config.cloud?.apiKey) {
1083
- await chmod2(configPath, 384);
1244
+ try {
1245
+ await client.execute(
1246
+ `CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(memory_type)`
1247
+ );
1248
+ } catch {
1084
1249
  }
1085
- }
1086
- async function loadConfigFrom(configPath) {
1087
- const raw = await readFile2(configPath, "utf-8");
1088
1250
  try {
1089
- let parsed = JSON.parse(raw);
1090
- parsed = migrateLegacyConfig(parsed);
1091
- const { config: migratedCfg } = migrateConfig(parsed);
1092
- normalizeScalingRoadmap(migratedCfg);
1093
- normalizeSessionLifecycle(migratedCfg);
1094
- normalizeAutoUpdate(migratedCfg);
1095
- return { ...DEFAULT_CONFIG, ...migratedCfg };
1251
+ await client.execute({
1252
+ sql: `ALTER TABLE memories ADD COLUMN trajectory TEXT`,
1253
+ args: []
1254
+ });
1096
1255
  } catch {
1097
- return { ...DEFAULT_CONFIG };
1098
1256
  }
1099
1257
  }
1100
- var EXE_AI_DIR, DB_PATH, MODELS_DIR, CONFIG_PATH, COO_AGENT_NAME, LEGACY_LANCE_PATH, CURRENT_CONFIG_VERSION, DEFAULT_CONFIG, CONFIG_MIGRATIONS;
1101
- var init_config = __esm({
1102
- "src/lib/config.ts"() {
1258
+ var _client, _resilientClient, initTurso;
1259
+ var init_database = __esm({
1260
+ "src/lib/database.ts"() {
1103
1261
  "use strict";
1104
- EXE_AI_DIR = resolveDataDir();
1105
- DB_PATH = path2.join(EXE_AI_DIR, "memories.db");
1106
- MODELS_DIR = path2.join(EXE_AI_DIR, "models");
1107
- CONFIG_PATH = path2.join(EXE_AI_DIR, "config.json");
1108
- COO_AGENT_NAME = "exe";
1109
- LEGACY_LANCE_PATH = path2.join(EXE_AI_DIR, "local.lance");
1110
- CURRENT_CONFIG_VERSION = 1;
1111
- DEFAULT_CONFIG = {
1112
- config_version: CURRENT_CONFIG_VERSION,
1113
- dbPath: DB_PATH,
1114
- modelFile: "jina-embeddings-v5-small-q4_k_m.gguf",
1115
- embeddingDim: 1024,
1116
- batchSize: 20,
1117
- flushIntervalMs: 1e4,
1118
- autoIngestion: true,
1119
- autoRetrieval: true,
1120
- searchMode: "hybrid",
1121
- hookSearchMode: "hybrid",
1122
- fileGrepEnabled: true,
1123
- splashEffect: true,
1124
- consolidationEnabled: true,
1125
- consolidationIntervalMs: 6 * 60 * 60 * 1e3,
1126
- consolidationModel: "claude-haiku-4-5-20251001",
1127
- consolidationMaxCallsPerRun: 20,
1128
- selfQueryRouter: true,
1129
- selfQueryModel: "claude-haiku-4-5-20251001",
1130
- rerankerEnabled: true,
1131
- scalingRoadmap: {
1132
- rerankerAutoTrigger: {
1133
- enabled: true,
1134
- broadQueryMinCardinality: 5e4,
1135
- fetchTopK: 150,
1136
- returnTopK: 5
1137
- }
1138
- },
1139
- graphRagEnabled: true,
1140
- wikiEnabled: false,
1141
- wikiUrl: "",
1142
- wikiApiKey: "",
1143
- wikiSyncIntervalMs: 30 * 60 * 1e3,
1144
- wikiWorkspaceMapping: {
1145
- exe: "Executive",
1146
- yoshi: "Engineering",
1147
- mari: "Marketing",
1148
- tom: "Engineering",
1149
- sasha: "Production"
1150
- },
1151
- wikiAutoUpdate: true,
1152
- wikiAutoUpdateThreshold: 0.5,
1153
- wikiAutoUpdateCreateNew: true,
1154
- skillLearning: true,
1155
- skillThreshold: 3,
1156
- skillModel: "claude-haiku-4-5-20251001",
1157
- exeHeartbeat: {
1158
- enabled: true,
1159
- intervalSeconds: 60,
1160
- staleInProgressThresholdHours: 2
1161
- },
1162
- sessionLifecycle: {
1163
- idleKillEnabled: true,
1164
- idleKillTicksRequired: 3,
1165
- idleKillIntercomAckWindowMs: 1e4,
1166
- maxAutoInstances: 10
1167
- },
1168
- autoUpdate: {
1169
- checkOnBoot: true,
1170
- autoInstall: false,
1171
- checkIntervalMs: 24 * 60 * 60 * 1e3
1172
- }
1173
- };
1174
- CONFIG_MIGRATIONS = [
1175
- {
1176
- from: 0,
1177
- to: 1,
1178
- migrate: (cfg) => {
1179
- cfg.config_version = 1;
1180
- return cfg;
1181
- }
1182
- }
1183
- ];
1262
+ init_db_retry();
1263
+ init_employees();
1264
+ _client = null;
1265
+ _resilientClient = null;
1266
+ initTurso = initDatabase;
1184
1267
  }
1185
1268
  });
1186
1269
 
@@ -1252,12 +1335,12 @@ __export(shard_manager_exports, {
1252
1335
  listShards: () => listShards,
1253
1336
  shardExists: () => shardExists
1254
1337
  });
1255
- import path3 from "path";
1256
- import { existsSync as existsSync3, mkdirSync, readdirSync } from "fs";
1338
+ import path4 from "path";
1339
+ import { existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
1257
1340
  import { createClient as createClient2 } from "@libsql/client";
1258
1341
  function initShardManager(encryptionKey) {
1259
1342
  _encryptionKey = encryptionKey;
1260
- if (!existsSync3(SHARDS_DIR)) {
1343
+ if (!existsSync4(SHARDS_DIR)) {
1261
1344
  mkdirSync(SHARDS_DIR, { recursive: true });
1262
1345
  }
1263
1346
  _shardingEnabled = true;
@@ -1278,7 +1361,7 @@ function getShardClient(projectName) {
1278
1361
  }
1279
1362
  const cached = _shards.get(safeName);
1280
1363
  if (cached) return cached;
1281
- const dbPath = path3.join(SHARDS_DIR, `${safeName}.db`);
1364
+ const dbPath = path4.join(SHARDS_DIR, `${safeName}.db`);
1282
1365
  const client = createClient2({
1283
1366
  url: `file:${dbPath}`,
1284
1367
  encryptionKey: _encryptionKey
@@ -1288,10 +1371,10 @@ function getShardClient(projectName) {
1288
1371
  }
1289
1372
  function shardExists(projectName) {
1290
1373
  const safeName = projectName.replace(/[^a-zA-Z0-9_-]/g, "_");
1291
- return existsSync3(path3.join(SHARDS_DIR, `${safeName}.db`));
1374
+ return existsSync4(path4.join(SHARDS_DIR, `${safeName}.db`));
1292
1375
  }
1293
1376
  function listShards() {
1294
- if (!existsSync3(SHARDS_DIR)) return [];
1377
+ if (!existsSync4(SHARDS_DIR)) return [];
1295
1378
  return readdirSync(SHARDS_DIR).filter((f) => f.endsWith(".db")).map((f) => f.replace(".db", ""));
1296
1379
  }
1297
1380
  async function ensureShardSchema(client) {
@@ -1361,7 +1444,11 @@ async function ensureShardSchema(client) {
1361
1444
  "ALTER TABLE memories ADD COLUMN source_path TEXT",
1362
1445
  "ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
1363
1446
  "ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
1364
- "ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
1447
+ "ALTER TABLE memories ADD COLUMN supersedes_id TEXT",
1448
+ // MS-11: draft staging, MS-6a: memory_type, MS-7: trajectory
1449
+ "ALTER TABLE memories ADD COLUMN draft INTEGER DEFAULT 0",
1450
+ "ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'",
1451
+ "ALTER TABLE memories ADD COLUMN trajectory TEXT"
1365
1452
  ]) {
1366
1453
  try {
1367
1454
  await client.execute(col);
@@ -1473,7 +1560,7 @@ var init_shard_manager = __esm({
1473
1560
  "src/lib/shard-manager.ts"() {
1474
1561
  "use strict";
1475
1562
  init_config();
1476
- SHARDS_DIR = path3.join(EXE_AI_DIR, "shards");
1563
+ SHARDS_DIR = path4.join(EXE_AI_DIR, "shards");
1477
1564
  _shards = /* @__PURE__ */ new Map();
1478
1565
  _encryptionKey = null;
1479
1566
  _shardingEnabled = false;
@@ -1491,26 +1578,26 @@ var init_platform_procedures = __esm({
1491
1578
  title: "What is exe-os \u2014 the operating model every agent must understand",
1492
1579
  domain: "architecture",
1493
1580
  priority: "p0",
1494
- 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."
1581
+ 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."
1495
1582
  },
1496
1583
  {
1497
1584
  title: "Mode 1 \u2014 how exe-os runs inside Claude Code",
1498
1585
  domain: "architecture",
1499
1586
  priority: "p0",
1500
- 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."
1587
+ 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."
1501
1588
  },
1502
1589
  {
1503
- title: "Sessions explained \u2014 what exeN means and how projects work",
1590
+ title: "Sessions explained \u2014 coordinator session names and projects",
1504
1591
  domain: "architecture",
1505
1592
  priority: "p0",
1506
- 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."
1593
+ 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."
1507
1594
  },
1508
1595
  // --- Hierarchy and dispatch ---
1509
1596
  {
1510
1597
  title: "Chain of command \u2014 who talks to whom",
1511
1598
  domain: "workflow",
1512
1599
  priority: "p0",
1513
- 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."
1600
+ 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."
1514
1601
  },
1515
1602
  {
1516
1603
  title: "Single dispatch path \u2014 create_task only",
@@ -1520,30 +1607,30 @@ var init_platform_procedures = __esm({
1520
1607
  },
1521
1608
  // --- Session isolation ---
1522
1609
  {
1523
- title: "Session scoping \u2014 stay in your exe boundary",
1610
+ title: "Session scoping \u2014 stay in your coordinator boundary",
1524
1611
  domain: "security",
1525
1612
  priority: "p0",
1526
- 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."
1613
+ 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."
1527
1614
  },
1528
1615
  {
1529
1616
  title: "Session isolation \u2014 never touch another session's work",
1530
1617
  domain: "workflow",
1531
1618
  priority: "p0",
1532
- 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.`
1619
+ 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."
1533
1620
  },
1534
1621
  // --- Engineering: session scoping in code ---
1535
1622
  {
1536
1623
  title: "Three-dimensional scoping \u2014 session, project, role \u2014 enforced in every query",
1537
1624
  domain: "architecture",
1538
1625
  priority: "p0",
1539
- 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."
1626
+ 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."
1540
1627
  },
1541
1628
  // --- Hard constraints ---
1542
1629
  {
1543
1630
  title: "What you CANNOT do in exe-os \u2014 hard constraints",
1544
1631
  domain: "security",
1545
1632
  priority: "p0",
1546
- 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."
1633
+ 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."
1547
1634
  },
1548
1635
  // --- Operations ---
1549
1636
  {
@@ -1662,24 +1749,24 @@ ${p.content}`).join("\n\n");
1662
1749
  });
1663
1750
 
1664
1751
  // src/lib/session-registry.ts
1665
- import path4 from "path";
1666
- import os3 from "os";
1752
+ import path5 from "path";
1753
+ import os4 from "os";
1667
1754
  var REGISTRY_PATH;
1668
1755
  var init_session_registry = __esm({
1669
1756
  "src/lib/session-registry.ts"() {
1670
1757
  "use strict";
1671
- REGISTRY_PATH = path4.join(os3.homedir(), ".exe-os", "session-registry.json");
1758
+ REGISTRY_PATH = path5.join(os4.homedir(), ".exe-os", "session-registry.json");
1672
1759
  }
1673
1760
  });
1674
1761
 
1675
1762
  // src/lib/session-key.ts
1676
- import { execSync } from "child_process";
1763
+ import { execSync as execSync2 } from "child_process";
1677
1764
  function getSessionKey() {
1678
1765
  if (_cached) return _cached;
1679
1766
  let pid = process.ppid;
1680
1767
  for (let i = 0; i < 10; i++) {
1681
1768
  try {
1682
- const info = execSync(`ps -p ${pid} -o ppid=,comm=`, {
1769
+ const info = execSync2(`ps -p ${pid} -o ppid=,comm=`, {
1683
1770
  encoding: "utf8",
1684
1771
  timeout: 2e3
1685
1772
  }).trim();
@@ -1815,7 +1902,7 @@ var init_transport = __esm({
1815
1902
  });
1816
1903
 
1817
1904
  // src/lib/cc-agent-support.ts
1818
- import { execSync as execSync2 } from "child_process";
1905
+ import { execSync as execSync3 } from "child_process";
1819
1906
  var init_cc_agent_support = __esm({
1820
1907
  "src/lib/cc-agent-support.ts"() {
1821
1908
  "use strict";
@@ -1844,17 +1931,17 @@ var init_provider_table = __esm({
1844
1931
  });
1845
1932
 
1846
1933
  // src/lib/intercom-queue.ts
1847
- import { readFileSync as readFileSync2, writeFileSync, renameSync as renameSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
1848
- import path5 from "path";
1849
- import os4 from "os";
1934
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, renameSync as renameSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
1935
+ import path6 from "path";
1936
+ import os5 from "os";
1850
1937
  function ensureDir() {
1851
- const dir = path5.dirname(QUEUE_PATH);
1852
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
1938
+ const dir = path6.dirname(QUEUE_PATH);
1939
+ if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
1853
1940
  }
1854
1941
  function readQueue() {
1855
1942
  try {
1856
- if (!existsSync4(QUEUE_PATH)) return [];
1857
- return JSON.parse(readFileSync2(QUEUE_PATH, "utf8"));
1943
+ if (!existsSync5(QUEUE_PATH)) return [];
1944
+ return JSON.parse(readFileSync3(QUEUE_PATH, "utf8"));
1858
1945
  } catch {
1859
1946
  return [];
1860
1947
  }
@@ -1862,8 +1949,8 @@ function readQueue() {
1862
1949
  function writeQueue(queue) {
1863
1950
  ensureDir();
1864
1951
  const tmp = `${QUEUE_PATH}.tmp`;
1865
- writeFileSync(tmp, JSON.stringify(queue, null, 2));
1866
- renameSync2(tmp, QUEUE_PATH);
1952
+ writeFileSync2(tmp, JSON.stringify(queue, null, 2));
1953
+ renameSync3(tmp, QUEUE_PATH);
1867
1954
  }
1868
1955
  function queueIntercom(targetSession, reason) {
1869
1956
  const queue = readQueue();
@@ -1886,38 +1973,9 @@ var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
1886
1973
  var init_intercom_queue = __esm({
1887
1974
  "src/lib/intercom-queue.ts"() {
1888
1975
  "use strict";
1889
- QUEUE_PATH = path5.join(os4.homedir(), ".exe-os", "intercom-queue.json");
1976
+ QUEUE_PATH = path6.join(os5.homedir(), ".exe-os", "intercom-queue.json");
1890
1977
  TTL_MS = 60 * 60 * 1e3;
1891
- INTERCOM_LOG = path5.join(os4.homedir(), ".exe-os", "intercom.log");
1892
- }
1893
- });
1894
-
1895
- // src/lib/employees.ts
1896
- import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
1897
- import { existsSync as existsSync5, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
1898
- import { execSync as execSync3 } from "child_process";
1899
- import path6 from "path";
1900
- import os5 from "os";
1901
- async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
1902
- if (!existsSync5(employeesPath)) {
1903
- return [];
1904
- }
1905
- const raw = await readFile3(employeesPath, "utf-8");
1906
- try {
1907
- return JSON.parse(raw);
1908
- } catch {
1909
- return [];
1910
- }
1911
- }
1912
- function getEmployee(employees, name) {
1913
- return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
1914
- }
1915
- var EMPLOYEES_PATH;
1916
- var init_employees = __esm({
1917
- "src/lib/employees.ts"() {
1918
- "use strict";
1919
- init_config();
1920
- EMPLOYEES_PATH = path6.join(EXE_AI_DIR, "exe-employees.json");
1978
+ INTERCOM_LOG = path6.join(os5.homedir(), ".exe-os", "intercom.log");
1921
1979
  }
1922
1980
  });
1923
1981
 
@@ -1961,8 +2019,10 @@ function getMySession() {
1961
2019
  return getTransport().getMySession();
1962
2020
  }
1963
2021
  function extractRootExe(name) {
1964
- const match = name.match(/(exe\d+)$/);
1965
- return match?.[1] ?? null;
2022
+ if (!name) return null;
2023
+ if (!name.includes("-")) return name;
2024
+ const parts = name.split("-").filter(Boolean);
2025
+ return parts.length > 0 ? parts[parts.length - 1] : null;
1966
2026
  }
1967
2027
  function getParentExe(sessionKey) {
1968
2028
  try {
@@ -2042,12 +2102,14 @@ function getSessionState(sessionName) {
2042
2102
  }
2043
2103
  }
2044
2104
  function isExeSession(sessionName) {
2045
- return /^exe\d*$/.test(sessionName);
2105
+ const matchesBaseWithInstance = (baseName) => sessionName === baseName || sessionName.startsWith(baseName) && /^\d+$/.test(sessionName.slice(baseName.length));
2106
+ const coordinatorName = getCoordinatorName();
2107
+ return matchesBaseWithInstance(coordinatorName) || matchesBaseWithInstance("exe");
2046
2108
  }
2047
2109
  function sendIntercom(targetSession) {
2048
2110
  const transport = getTransport();
2049
2111
  if (isExeSession(targetSession)) {
2050
- logIntercom(`SKIP_EXE \u2192 ${targetSession} (exe sessions use prompt-submit hook)`);
2112
+ logIntercom(`SKIP_COORDINATOR \u2192 ${targetSession} (coordinator sessions use prompt-submit hook)`);
2051
2113
  return "skipped_exe";
2052
2114
  }
2053
2115
  if (isDebounced(targetSession)) {
@@ -2098,6 +2160,7 @@ var init_tmux_routing = __esm({
2098
2160
  init_provider_table();
2099
2161
  init_intercom_queue();
2100
2162
  init_plan_limits();
2163
+ init_employees();
2101
2164
  SPAWN_LOCK_DIR = path9.join(os6.homedir(), ".exe-os", "spawn-locks");
2102
2165
  SESSION_CACHE = path9.join(os6.homedir(), ".exe-os", "session-cache");
2103
2166
  INTERCOM_DEBOUNCE_MS = 3e4;
@@ -2262,6 +2325,10 @@ function spawnDaemon() {
2262
2325
  stdio: ["ignore", "ignore", stderrFd],
2263
2326
  env: {
2264
2327
  ...process.env,
2328
+ TMUX: void 0,
2329
+ // Daemon is global — must not inherit session scope
2330
+ TMUX_PANE: void 0,
2331
+ // Prevents resolveExeSession() from scoping to one session
2265
2332
  EXE_DAEMON_SOCK: SOCKET_PATH,
2266
2333
  EXE_DAEMON_PID: PID_PATH
2267
2334
  }
@@ -2776,21 +2843,23 @@ function getReviewChecklist(role, agent, taskSlug) {
2776
2843
  }
2777
2844
  async function createReviewForCompletedTask(row, result, _baseDir, now) {
2778
2845
  const taskFile = String(row.task_file);
2779
- if (String(row.assigned_to) === "exe") return;
2846
+ const employees = await loadEmployees();
2847
+ const coordinatorName = getCoordinatorName(employees);
2848
+ if (String(row.assigned_to) === "exe" || isCoordinatorName(String(row.assigned_to), employees)) return;
2780
2849
  if (String(row.title).startsWith("Review:")) return;
2781
2850
  const fileName = taskFile.split("/").pop() ?? "";
2782
2851
  if (fileName.startsWith("review-") && String(row.assigned_by) === "system") return;
2783
2852
  if (fileName.startsWith("review-") && String(row.assigned_to) === "system") return;
2784
2853
  const client = getClient();
2785
2854
  const agent = String(row.assigned_to);
2786
- const reviewer = String(row.reviewer || row.assigned_by) || "exe";
2855
+ const rawReviewer = row.reviewer || row.assigned_by;
2856
+ const reviewer = rawReviewer ? String(rawReviewer) : coordinatorName;
2787
2857
  const currentStatus = String(row.status ?? "");
2788
2858
  if (currentStatus === "done") return;
2789
2859
  const existingResult = String(row.result ?? "");
2790
2860
  if (existingResult.includes("## Review notes")) return;
2791
2861
  let reviewerRole = "unknown";
2792
2862
  try {
2793
- const employees = await loadEmployees();
2794
2863
  const emp = getEmployee(employees, reviewer);
2795
2864
  if (emp) reviewerRole = emp.role;
2796
2865
  } catch {
@@ -2835,7 +2904,7 @@ async function createReviewForCompletedTask(row, result, _baseDir, now) {
2835
2904
  agentRole: String(row.assigned_to),
2836
2905
  event: "task_complete",
2837
2906
  project: String(row.project_name),
2838
- summary: `completed "${taskTitle}" \u2014 review task created`,
2907
+ summary: `completed "${taskTitle}" \u2014 ready for review`,
2839
2908
  taskFile
2840
2909
  });
2841
2910
  const originalPriority = String(row.priority).toLowerCase();
@@ -3149,17 +3218,17 @@ init_memory();
3149
3218
  init_database();
3150
3219
 
3151
3220
  // src/lib/keychain.ts
3152
- import { readFile, writeFile, unlink, mkdir, chmod } from "fs/promises";
3153
- import { existsSync } from "fs";
3154
- import path from "path";
3155
- import os from "os";
3221
+ import { readFile as readFile3, writeFile as writeFile3, unlink, mkdir as mkdir3, chmod as chmod2 } from "fs/promises";
3222
+ import { existsSync as existsSync3 } from "fs";
3223
+ import path3 from "path";
3224
+ import os3 from "os";
3156
3225
  var SERVICE = "exe-mem";
3157
3226
  var ACCOUNT = "master-key";
3158
3227
  function getKeyDir() {
3159
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path.join(os.homedir(), ".exe-os");
3228
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path3.join(os3.homedir(), ".exe-os");
3160
3229
  }
3161
3230
  function getKeyPath() {
3162
- return path.join(getKeyDir(), "master.key");
3231
+ return path3.join(getKeyDir(), "master.key");
3163
3232
  }
3164
3233
  async function tryKeytar() {
3165
3234
  try {
@@ -3180,11 +3249,11 @@ async function getMasterKey() {
3180
3249
  }
3181
3250
  }
3182
3251
  const keyPath = getKeyPath();
3183
- if (!existsSync(keyPath)) {
3252
+ if (!existsSync3(keyPath)) {
3184
3253
  return null;
3185
3254
  }
3186
3255
  try {
3187
- const content = await readFile(keyPath, "utf-8");
3256
+ const content = await readFile3(keyPath, "utf-8");
3188
3257
  return Buffer.from(content.trim(), "base64");
3189
3258
  } catch {
3190
3259
  return null;
@@ -3307,7 +3376,10 @@ async function writeMemory(record) {
3307
3376
  source_path: record.source_path ?? null,
3308
3377
  source_type: record.source_type ?? null,
3309
3378
  tier: record.tier ?? classifyTier(record),
3310
- supersedes_id: record.supersedes_id ?? null
3379
+ supersedes_id: record.supersedes_id ?? null,
3380
+ draft: record.draft ? 1 : 0,
3381
+ memory_type: record.memory_type ?? "raw",
3382
+ trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null
3311
3383
  };
3312
3384
  _pendingRecords.push(dbRow);
3313
3385
  orgBus.emit({
@@ -3362,6 +3434,9 @@ async function flushBatch() {
3362
3434
  const sourceType = row.source_type ?? null;
3363
3435
  const tier = row.tier ?? 3;
3364
3436
  const supersedesId = row.supersedes_id ?? null;
3437
+ const draft = row.draft ? 1 : 0;
3438
+ const memoryType = row.memory_type ?? "raw";
3439
+ const trajectory = row.trajectory ?? null;
3365
3440
  return {
3366
3441
  sql: hasVector ? `INSERT OR IGNORE INTO memories
3367
3442
  (id, agent_id, agent_role, session_id, timestamp,
@@ -3369,15 +3444,15 @@ async function flushBatch() {
3369
3444
  has_error, raw_text, vector, version, task_id, importance, status,
3370
3445
  confidence, last_accessed,
3371
3446
  workspace_id, document_id, user_id, char_offset, page_number,
3372
- source_path, source_type, tier, supersedes_id)
3373
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
3447
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
3448
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
3374
3449
  (id, agent_id, agent_role, session_id, timestamp,
3375
3450
  tool_name, project_name,
3376
3451
  has_error, raw_text, vector, version, task_id, importance, status,
3377
3452
  confidence, last_accessed,
3378
3453
  workspace_id, document_id, user_id, char_offset, page_number,
3379
- source_path, source_type, tier, supersedes_id)
3380
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3454
+ source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory)
3455
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
3381
3456
  args: hasVector ? [
3382
3457
  row.id,
3383
3458
  row.agent_id,
@@ -3403,7 +3478,10 @@ async function flushBatch() {
3403
3478
  sourcePath,
3404
3479
  sourceType,
3405
3480
  tier,
3406
- supersedesId
3481
+ supersedesId,
3482
+ draft,
3483
+ memoryType,
3484
+ trajectory
3407
3485
  ] : [
3408
3486
  row.id,
3409
3487
  row.agent_id,
@@ -3428,7 +3506,10 @@ async function flushBatch() {
3428
3506
  sourcePath,
3429
3507
  sourceType,
3430
3508
  tier,
3431
- supersedesId
3509
+ supersedesId,
3510
+ draft,
3511
+ memoryType,
3512
+ trajectory
3432
3513
  ]
3433
3514
  };
3434
3515
  };