@askexenow/exe-os 0.9.97 → 0.9.99

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 (81) hide show
  1. package/dist/bin/agentic-ontology-backfill.js +7 -29
  2. package/dist/bin/agentic-reflection-backfill.js +7 -29
  3. package/dist/bin/agentic-semantic-label.js +7 -29
  4. package/dist/bin/backfill-conversations.js +7 -29
  5. package/dist/bin/backfill-responses.js +7 -29
  6. package/dist/bin/backfill-vectors.js +7 -29
  7. package/dist/bin/bulk-sync-postgres.js +7 -29
  8. package/dist/bin/cleanup-stale-review-tasks.js +7 -29
  9. package/dist/bin/cli.js +11 -33
  10. package/dist/bin/exe-agent.js +7 -0
  11. package/dist/bin/exe-assign.js +7 -29
  12. package/dist/bin/exe-boot.js +7 -29
  13. package/dist/bin/exe-call.js +7 -0
  14. package/dist/bin/exe-cloud.js +7 -29
  15. package/dist/bin/exe-dispatch.js +7 -29
  16. package/dist/bin/exe-doctor.js +7 -29
  17. package/dist/bin/exe-export-behaviors.js +7 -29
  18. package/dist/bin/exe-forget.js +7 -29
  19. package/dist/bin/exe-gateway.js +7 -29
  20. package/dist/bin/exe-heartbeat.js +7 -29
  21. package/dist/bin/exe-kill.js +7 -29
  22. package/dist/bin/exe-launch-agent.js +7 -29
  23. package/dist/bin/exe-new-employee.js +7 -0
  24. package/dist/bin/exe-pending-messages.js +7 -29
  25. package/dist/bin/exe-pending-notifications.js +7 -29
  26. package/dist/bin/exe-pending-reviews.js +7 -29
  27. package/dist/bin/exe-rename.js +7 -29
  28. package/dist/bin/exe-review.js +7 -29
  29. package/dist/bin/exe-search.js +7 -29
  30. package/dist/bin/exe-session-cleanup.js +7 -29
  31. package/dist/bin/exe-start-codex.js +7 -29
  32. package/dist/bin/exe-start-opencode.js +7 -29
  33. package/dist/bin/exe-status.js +7 -29
  34. package/dist/bin/exe-team.js +7 -29
  35. package/dist/bin/git-sweep.js +7 -29
  36. package/dist/bin/graph-backfill.js +7 -29
  37. package/dist/bin/graph-export.js +7 -29
  38. package/dist/bin/intercom-check.js +7 -29
  39. package/dist/bin/scan-tasks.js +7 -29
  40. package/dist/bin/setup.js +11 -33
  41. package/dist/bin/shard-migrate.js +7 -29
  42. package/dist/gateway/index.js +7 -29
  43. package/dist/hooks/bug-report-worker.js +7 -29
  44. package/dist/hooks/codex-stop-task-finalizer.js +7 -29
  45. package/dist/hooks/commit-complete.js +7 -29
  46. package/dist/hooks/error-recall.js +7 -29
  47. package/dist/hooks/ingest.js +7 -29
  48. package/dist/hooks/instructions-loaded.js +7 -29
  49. package/dist/hooks/notification.js +7 -29
  50. package/dist/hooks/post-compact.js +7 -29
  51. package/dist/hooks/post-tool-combined.js +7 -29
  52. package/dist/hooks/pre-compact.js +7 -29
  53. package/dist/hooks/pre-tool-use.js +7 -29
  54. package/dist/hooks/prompt-submit.js +7 -29
  55. package/dist/hooks/session-end.js +7 -29
  56. package/dist/hooks/session-start.js +7 -29
  57. package/dist/hooks/stop.js +7 -29
  58. package/dist/hooks/subagent-stop.js +7 -29
  59. package/dist/hooks/summary-worker.js +7 -29
  60. package/dist/index.js +7 -29
  61. package/dist/lib/cloud-sync.js +0 -29
  62. package/dist/lib/database.js +0 -35
  63. package/dist/lib/db-daemon-client.js +0 -36
  64. package/dist/lib/db.js +0 -35
  65. package/dist/lib/device-registry.js +0 -35
  66. package/dist/lib/embedder.js +0 -35
  67. package/dist/lib/employee-templates.js +7 -0
  68. package/dist/lib/exe-daemon-client.js +0 -36
  69. package/dist/lib/exe-daemon.js +7 -29
  70. package/dist/lib/hybrid-search.js +7 -29
  71. package/dist/lib/schedules.js +7 -29
  72. package/dist/lib/skill-learning.js +0 -35
  73. package/dist/lib/store.js +7 -29
  74. package/dist/lib/tasks.js +0 -29
  75. package/dist/lib/tmux-routing.js +0 -29
  76. package/dist/mcp/server.js +7 -29
  77. package/dist/mcp/tools/create-task.js +0 -29
  78. package/dist/mcp/tools/update-task.js +0 -29
  79. package/dist/runtime/index.js +7 -29
  80. package/dist/tui/App.js +7 -29
  81. package/package.json +1 -1
@@ -1055,40 +1055,11 @@ function findPackageRoot() {
1055
1055
  }
1056
1056
  return null;
1057
1057
  }
1058
- function getAvailableMemoryGB() {
1059
- if (process.platform === "darwin") {
1060
- try {
1061
- const { execSync: execSync4 } = __require("child_process");
1062
- const vmstat = execSync4("vm_stat", { encoding: "utf8" });
1063
- const pageSize = 16384;
1064
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1065
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1066
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1067
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1068
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1069
- const freePages = free ? parseInt(free[1], 10) : 0;
1070
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1071
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1072
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1073
- } catch {
1074
- return os4.freemem() / (1024 * 1024 * 1024);
1075
- }
1076
- }
1077
- return os4.freemem() / (1024 * 1024 * 1024);
1078
- }
1079
1058
  function spawnDaemon() {
1080
- const freeGB = getAvailableMemoryGB();
1081
1059
  const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1082
1060
  if (totalGB <= 8) {
1083
1061
  process.stderr.write(
1084
1062
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1085
- `
1086
- );
1087
- return;
1088
- }
1089
- if (totalGB <= 16 && freeGB < 2) {
1090
- process.stderr.write(
1091
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1092
1063
  `
1093
1064
  );
1094
1065
  return;
@@ -3545,6 +3516,13 @@ var init_platform_procedures = __esm({
3545
3516
  priority: "p0",
3546
3517
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
3547
3518
  },
3519
+ // --- Encryption key + cloud sync ---
3520
+ {
3521
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
3522
+ domain: "security",
3523
+ priority: "p0",
3524
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
3525
+ },
3548
3526
  // --- MCP is the ONLY data interface ---
3549
3527
  {
3550
3528
  title: "MCP disconnect \u2014 ask the user, never work around it",
@@ -1066,40 +1066,11 @@ function findPackageRoot() {
1066
1066
  }
1067
1067
  return null;
1068
1068
  }
1069
- function getAvailableMemoryGB() {
1070
- if (process.platform === "darwin") {
1071
- try {
1072
- const { execSync: execSync4 } = __require("child_process");
1073
- const vmstat = execSync4("vm_stat", { encoding: "utf8" });
1074
- const pageSize = 16384;
1075
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1076
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1077
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1078
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1079
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1080
- const freePages = free ? parseInt(free[1], 10) : 0;
1081
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1082
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1083
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1084
- } catch {
1085
- return os4.freemem() / (1024 * 1024 * 1024);
1086
- }
1087
- }
1088
- return os4.freemem() / (1024 * 1024 * 1024);
1089
- }
1090
1069
  function spawnDaemon() {
1091
- const freeGB = getAvailableMemoryGB();
1092
1070
  const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1093
1071
  if (totalGB <= 8) {
1094
1072
  process.stderr.write(
1095
1073
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1096
- `
1097
- );
1098
- return;
1099
- }
1100
- if (totalGB <= 16 && freeGB < 2) {
1101
- process.stderr.write(
1102
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1103
1074
  `
1104
1075
  );
1105
1076
  return;
@@ -4317,6 +4288,13 @@ var init_platform_procedures = __esm({
4317
4288
  priority: "p0",
4318
4289
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
4319
4290
  },
4291
+ // --- Encryption key + cloud sync ---
4292
+ {
4293
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
4294
+ domain: "security",
4295
+ priority: "p0",
4296
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
4297
+ },
4320
4298
  // --- MCP is the ONLY data interface ---
4321
4299
  {
4322
4300
  title: "MCP disconnect \u2014 ask the user, never work around it",
@@ -1175,40 +1175,11 @@ function findPackageRoot() {
1175
1175
  }
1176
1176
  return null;
1177
1177
  }
1178
- function getAvailableMemoryGB() {
1179
- if (process.platform === "darwin") {
1180
- try {
1181
- const { execSync: execSync9 } = __require("child_process");
1182
- const vmstat = execSync9("vm_stat", { encoding: "utf8" });
1183
- const pageSize = 16384;
1184
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1185
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1186
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1187
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1188
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1189
- const freePages = free ? parseInt(free[1], 10) : 0;
1190
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1191
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1192
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1193
- } catch {
1194
- return os4.freemem() / (1024 * 1024 * 1024);
1195
- }
1196
- }
1197
- return os4.freemem() / (1024 * 1024 * 1024);
1198
- }
1199
1178
  function spawnDaemon() {
1200
- const freeGB = getAvailableMemoryGB();
1201
1179
  const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1202
1180
  if (totalGB <= 8) {
1203
1181
  process.stderr.write(
1204
1182
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1205
- `
1206
- );
1207
- return;
1208
- }
1209
- if (totalGB <= 16 && freeGB < 2) {
1210
- process.stderr.write(
1211
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1212
1183
  `
1213
1184
  );
1214
1185
  return;
@@ -4426,6 +4397,13 @@ var init_platform_procedures = __esm({
4426
4397
  priority: "p0",
4427
4398
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
4428
4399
  },
4400
+ // --- Encryption key + cloud sync ---
4401
+ {
4402
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
4403
+ domain: "security",
4404
+ priority: "p0",
4405
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
4406
+ },
4429
4407
  // --- MCP is the ONLY data interface ---
4430
4408
  {
4431
4409
  title: "MCP disconnect \u2014 ask the user, never work around it",
@@ -1667,40 +1667,11 @@ function findPackageRoot() {
1667
1667
  }
1668
1668
  return null;
1669
1669
  }
1670
- function getAvailableMemoryGB() {
1671
- if (process.platform === "darwin") {
1672
- try {
1673
- const { execSync: execSync9 } = __require("child_process");
1674
- const vmstat = execSync9("vm_stat", { encoding: "utf8" });
1675
- const pageSize = 16384;
1676
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1677
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1678
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1679
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1680
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1681
- const freePages = free ? parseInt(free[1], 10) : 0;
1682
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1683
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1684
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1685
- } catch {
1686
- return os6.freemem() / (1024 * 1024 * 1024);
1687
- }
1688
- }
1689
- return os6.freemem() / (1024 * 1024 * 1024);
1690
- }
1691
1670
  function spawnDaemon() {
1692
- const freeGB = getAvailableMemoryGB();
1693
1671
  const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
1694
1672
  if (totalGB <= 8) {
1695
1673
  process.stderr.write(
1696
1674
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1697
- `
1698
- );
1699
- return;
1700
- }
1701
- if (totalGB <= 16 && freeGB < 2) {
1702
- process.stderr.write(
1703
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1704
1675
  `
1705
1676
  );
1706
1677
  return;
@@ -8156,6 +8127,13 @@ var init_platform_procedures = __esm({
8156
8127
  priority: "p0",
8157
8128
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
8158
8129
  },
8130
+ // --- Encryption key + cloud sync ---
8131
+ {
8132
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
8133
+ domain: "security",
8134
+ priority: "p0",
8135
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
8136
+ },
8159
8137
  // --- MCP is the ONLY data interface ---
8160
8138
  {
8161
8139
  title: "MCP disconnect \u2014 ask the user, never work around it",
package/dist/bin/setup.js CHANGED
@@ -920,40 +920,11 @@ function findPackageRoot() {
920
920
  }
921
921
  return null;
922
922
  }
923
- function getAvailableMemoryGB() {
924
- if (process.platform === "darwin") {
925
- try {
926
- const { execSync: execSync5 } = __require("child_process");
927
- const vmstat = execSync5("vm_stat", { encoding: "utf8" });
928
- const pageSize = 16384;
929
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
930
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
931
- const free = vmstat.match(/Pages free:\s+(\d+)/);
932
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
933
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
934
- const freePages = free ? parseInt(free[1], 10) : 0;
935
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
936
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
937
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
938
- } catch {
939
- return os3.freemem() / (1024 * 1024 * 1024);
940
- }
941
- }
942
- return os3.freemem() / (1024 * 1024 * 1024);
943
- }
944
923
  function spawnDaemon() {
945
- const freeGB = getAvailableMemoryGB();
946
924
  const totalGB = os3.totalmem() / (1024 * 1024 * 1024);
947
925
  if (totalGB <= 8) {
948
926
  process.stderr.write(
949
927
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
950
- `
951
- );
952
- return;
953
- }
954
- if (totalGB <= 16 && freeGB < 2) {
955
- process.stderr.write(
956
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
957
928
  `
958
929
  );
959
930
  return;
@@ -6727,6 +6698,13 @@ var init_platform_procedures = __esm({
6727
6698
  priority: "p0",
6728
6699
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
6729
6700
  },
6701
+ // --- Encryption key + cloud sync ---
6702
+ {
6703
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
6704
+ domain: "security",
6705
+ priority: "p0",
6706
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
6707
+ },
6730
6708
  // --- MCP is the ONLY data interface ---
6731
6709
  {
6732
6710
  title: "MCP disconnect \u2014 ask the user, never work around it",
@@ -8452,7 +8430,7 @@ function ask(rl, prompt) {
8452
8430
  doAsk();
8453
8431
  });
8454
8432
  }
8455
- function getAvailableMemoryGB2() {
8433
+ function getAvailableMemoryGB() {
8456
8434
  if (process.platform === "darwin") {
8457
8435
  try {
8458
8436
  const { execSync: execSync5 } = __require("child_process");
@@ -8476,11 +8454,11 @@ function getTotalMemoryGB() {
8476
8454
  return os8.totalmem() / (1024 * 1024 * 1024);
8477
8455
  }
8478
8456
  function isLowMemory() {
8479
- return getAvailableMemoryGB2() < 2;
8457
+ return getAvailableMemoryGB() < 2;
8480
8458
  }
8481
8459
  async function validateModel(log) {
8482
8460
  const totalGB = getTotalMemoryGB();
8483
- const freeGB = getAvailableMemoryGB2();
8461
+ const freeGB = getAvailableMemoryGB();
8484
8462
  if (totalGB <= 8 || isLowMemory()) {
8485
8463
  log(`System memory: ${totalGB.toFixed(0)}GB total, ${freeGB.toFixed(1)}GB free`);
8486
8464
  log("Skipping in-memory model validation (low memory \u2014 will validate on first use).");
@@ -8725,7 +8703,7 @@ async function runSetupWizard(opts = {}) {
8725
8703
  skipModel2 = true;
8726
8704
  }
8727
8705
  if (!skipModel2) {
8728
- const freeGB = getAvailableMemoryGB2();
8706
+ const freeGB = getAvailableMemoryGB();
8729
8707
  if (freeGB < 2) {
8730
8708
  log(`\u26A0 Low memory detected: ${freeGB.toFixed(1)}GB free of ${totalGB.toFixed(0)}GB total`);
8731
8709
  log(" Close other applications (browser, Slack, etc.) before continuing.");
@@ -1055,40 +1055,11 @@ function findPackageRoot() {
1055
1055
  }
1056
1056
  return null;
1057
1057
  }
1058
- function getAvailableMemoryGB() {
1059
- if (process.platform === "darwin") {
1060
- try {
1061
- const { execSync: execSync4 } = __require("child_process");
1062
- const vmstat = execSync4("vm_stat", { encoding: "utf8" });
1063
- const pageSize = 16384;
1064
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1065
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1066
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1067
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1068
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1069
- const freePages = free ? parseInt(free[1], 10) : 0;
1070
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1071
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1072
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1073
- } catch {
1074
- return os4.freemem() / (1024 * 1024 * 1024);
1075
- }
1076
- }
1077
- return os4.freemem() / (1024 * 1024 * 1024);
1078
- }
1079
1058
  function spawnDaemon() {
1080
- const freeGB = getAvailableMemoryGB();
1081
1059
  const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1082
1060
  if (totalGB <= 8) {
1083
1061
  process.stderr.write(
1084
1062
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1085
- `
1086
- );
1087
- return;
1088
- }
1089
- if (totalGB <= 16 && freeGB < 2) {
1090
- process.stderr.write(
1091
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1092
1063
  `
1093
1064
  );
1094
1065
  return;
@@ -3545,6 +3516,13 @@ var init_platform_procedures = __esm({
3545
3516
  priority: "p0",
3546
3517
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
3547
3518
  },
3519
+ // --- Encryption key + cloud sync ---
3520
+ {
3521
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
3522
+ domain: "security",
3523
+ priority: "p0",
3524
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
3525
+ },
3548
3526
  // --- MCP is the ONLY data interface ---
3549
3527
  {
3550
3528
  title: "MCP disconnect \u2014 ask the user, never work around it",
@@ -1776,40 +1776,11 @@ function findPackageRoot() {
1776
1776
  }
1777
1777
  return null;
1778
1778
  }
1779
- function getAvailableMemoryGB() {
1780
- if (process.platform === "darwin") {
1781
- try {
1782
- const { execSync: execSync9 } = __require("child_process");
1783
- const vmstat = execSync9("vm_stat", { encoding: "utf8" });
1784
- const pageSize = 16384;
1785
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1786
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1787
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1788
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1789
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1790
- const freePages = free ? parseInt(free[1], 10) : 0;
1791
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1792
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1793
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1794
- } catch {
1795
- return os4.freemem() / (1024 * 1024 * 1024);
1796
- }
1797
- }
1798
- return os4.freemem() / (1024 * 1024 * 1024);
1799
- }
1800
1779
  function spawnDaemon() {
1801
- const freeGB = getAvailableMemoryGB();
1802
1780
  const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1803
1781
  if (totalGB <= 8) {
1804
1782
  process.stderr.write(
1805
1783
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1806
- `
1807
- );
1808
- return;
1809
- }
1810
- if (totalGB <= 16 && freeGB < 2) {
1811
- process.stderr.write(
1812
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1813
1784
  `
1814
1785
  );
1815
1786
  return;
@@ -4985,6 +4956,13 @@ var init_platform_procedures = __esm({
4985
4956
  priority: "p0",
4986
4957
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
4987
4958
  },
4959
+ // --- Encryption key + cloud sync ---
4960
+ {
4961
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
4962
+ domain: "security",
4963
+ priority: "p0",
4964
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
4965
+ },
4988
4966
  // --- MCP is the ONLY data interface ---
4989
4967
  {
4990
4968
  title: "MCP disconnect \u2014 ask the user, never work around it",
@@ -1476,40 +1476,11 @@ function findPackageRoot() {
1476
1476
  }
1477
1477
  return null;
1478
1478
  }
1479
- function getAvailableMemoryGB() {
1480
- if (process.platform === "darwin") {
1481
- try {
1482
- const { execSync: execSync9 } = __require("child_process");
1483
- const vmstat = execSync9("vm_stat", { encoding: "utf8" });
1484
- const pageSize = 16384;
1485
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1486
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1487
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1488
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1489
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1490
- const freePages = free ? parseInt(free[1], 10) : 0;
1491
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1492
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1493
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1494
- } catch {
1495
- return os4.freemem() / (1024 * 1024 * 1024);
1496
- }
1497
- }
1498
- return os4.freemem() / (1024 * 1024 * 1024);
1499
- }
1500
1479
  function spawnDaemon() {
1501
- const freeGB = getAvailableMemoryGB();
1502
1480
  const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1503
1481
  if (totalGB <= 8) {
1504
1482
  process.stderr.write(
1505
1483
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1506
- `
1507
- );
1508
- return;
1509
- }
1510
- if (totalGB <= 16 && freeGB < 2) {
1511
- process.stderr.write(
1512
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1513
1484
  `
1514
1485
  );
1515
1486
  return;
@@ -4727,6 +4698,13 @@ var init_platform_procedures = __esm({
4727
4698
  priority: "p0",
4728
4699
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
4729
4700
  },
4701
+ // --- Encryption key + cloud sync ---
4702
+ {
4703
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
4704
+ domain: "security",
4705
+ priority: "p0",
4706
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
4707
+ },
4730
4708
  // --- MCP is the ONLY data interface ---
4731
4709
  {
4732
4710
  title: "MCP disconnect \u2014 ask the user, never work around it",
@@ -1159,40 +1159,11 @@ function findPackageRoot() {
1159
1159
  }
1160
1160
  return null;
1161
1161
  }
1162
- function getAvailableMemoryGB() {
1163
- if (process.platform === "darwin") {
1164
- try {
1165
- const { execSync: execSync7 } = __require("child_process");
1166
- const vmstat = execSync7("vm_stat", { encoding: "utf8" });
1167
- const pageSize = 16384;
1168
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1169
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1170
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1171
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1172
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1173
- const freePages = free ? parseInt(free[1], 10) : 0;
1174
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1175
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1176
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1177
- } catch {
1178
- return os4.freemem() / (1024 * 1024 * 1024);
1179
- }
1180
- }
1181
- return os4.freemem() / (1024 * 1024 * 1024);
1182
- }
1183
1162
  function spawnDaemon() {
1184
- const freeGB = getAvailableMemoryGB();
1185
1163
  const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1186
1164
  if (totalGB <= 8) {
1187
1165
  process.stderr.write(
1188
1166
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1189
- `
1190
- );
1191
- return;
1192
- }
1193
- if (totalGB <= 16 && freeGB < 2) {
1194
- process.stderr.write(
1195
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1196
1167
  `
1197
1168
  );
1198
1169
  return;
@@ -4410,6 +4381,13 @@ var init_platform_procedures = __esm({
4410
4381
  priority: "p0",
4411
4382
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
4412
4383
  },
4384
+ // --- Encryption key + cloud sync ---
4385
+ {
4386
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
4387
+ domain: "security",
4388
+ priority: "p0",
4389
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
4390
+ },
4413
4391
  // --- MCP is the ONLY data interface ---
4414
4392
  {
4415
4393
  title: "MCP disconnect \u2014 ask the user, never work around it",
@@ -1654,40 +1654,11 @@ function findPackageRoot() {
1654
1654
  }
1655
1655
  return null;
1656
1656
  }
1657
- function getAvailableMemoryGB() {
1658
- if (process.platform === "darwin") {
1659
- try {
1660
- const { execSync: execSync11 } = __require("child_process");
1661
- const vmstat = execSync11("vm_stat", { encoding: "utf8" });
1662
- const pageSize = 16384;
1663
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1664
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1665
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1666
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1667
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1668
- const freePages = free ? parseInt(free[1], 10) : 0;
1669
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1670
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1671
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1672
- } catch {
1673
- return os6.freemem() / (1024 * 1024 * 1024);
1674
- }
1675
- }
1676
- return os6.freemem() / (1024 * 1024 * 1024);
1677
- }
1678
1657
  function spawnDaemon() {
1679
- const freeGB = getAvailableMemoryGB();
1680
1658
  const totalGB = os6.totalmem() / (1024 * 1024 * 1024);
1681
1659
  if (totalGB <= 8) {
1682
1660
  process.stderr.write(
1683
1661
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1684
- `
1685
- );
1686
- return;
1687
- }
1688
- if (totalGB <= 16 && freeGB < 2) {
1689
- process.stderr.write(
1690
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1691
1662
  `
1692
1663
  );
1693
1664
  return;
@@ -8150,6 +8121,13 @@ var init_platform_procedures = __esm({
8150
8121
  priority: "p0",
8151
8122
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
8152
8123
  },
8124
+ // --- Encryption key + cloud sync ---
8125
+ {
8126
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
8127
+ domain: "security",
8128
+ priority: "p0",
8129
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
8130
+ },
8153
8131
  // --- MCP is the ONLY data interface ---
8154
8132
  {
8155
8133
  title: "MCP disconnect \u2014 ask the user, never work around it",
@@ -1122,40 +1122,11 @@ function findPackageRoot() {
1122
1122
  }
1123
1123
  return null;
1124
1124
  }
1125
- function getAvailableMemoryGB() {
1126
- if (process.platform === "darwin") {
1127
- try {
1128
- const { execSync: execSync8 } = __require("child_process");
1129
- const vmstat = execSync8("vm_stat", { encoding: "utf8" });
1130
- const pageSize = 16384;
1131
- const pageSizeMatch = vmstat.match(/page size of (\d+) bytes/);
1132
- const actualPageSize = pageSizeMatch ? parseInt(pageSizeMatch[1], 10) : pageSize;
1133
- const free = vmstat.match(/Pages free:\s+(\d+)/);
1134
- const inactive = vmstat.match(/Pages inactive:\s+(\d+)/);
1135
- const speculative = vmstat.match(/Pages speculative:\s+(\d+)/);
1136
- const freePages = free ? parseInt(free[1], 10) : 0;
1137
- const inactivePages = inactive ? parseInt(inactive[1], 10) : 0;
1138
- const speculativePages = speculative ? parseInt(speculative[1], 10) : 0;
1139
- return (freePages + inactivePages + speculativePages) * actualPageSize / (1024 * 1024 * 1024);
1140
- } catch {
1141
- return os4.freemem() / (1024 * 1024 * 1024);
1142
- }
1143
- }
1144
- return os4.freemem() / (1024 * 1024 * 1024);
1145
- }
1146
1125
  function spawnDaemon() {
1147
- const freeGB = getAvailableMemoryGB();
1148
1126
  const totalGB = os4.totalmem() / (1024 * 1024 * 1024);
1149
1127
  if (totalGB <= 8) {
1150
1128
  process.stderr.write(
1151
1129
  `[exed-client] SKIP: ${totalGB.toFixed(0)}GB system \u2014 embedding daemon disabled. Using keyword search only. Minimum 16GB recommended for vector search.
1152
- `
1153
- );
1154
- return;
1155
- }
1156
- if (totalGB <= 16 && freeGB < 2) {
1157
- process.stderr.write(
1158
- `[exed-client] SKIP: low memory (${freeGB.toFixed(1)}GB available / ${totalGB.toFixed(0)}GB total). Embedding daemon not started \u2014 using keyword search only.
1159
1130
  `
1160
1131
  );
1161
1132
  return;
@@ -4318,6 +4289,13 @@ var init_platform_procedures = __esm({
4318
4289
  priority: "p0",
4319
4290
  content: "Tasks live in the DB. Intercom (tmux send-keys) is fire-and-forget \u2014 it may fail, get garbled, or arrive mid-work. Never rely on intercom for task delivery. The UserPromptSubmit hook checks the DB for new tasks on every prompt. Your operating procedures step 7 says check for next work. The daemon nudges idle agents as a speedup. If you have no tasks, you found them all."
4320
4291
  },
4292
+ // --- Encryption key + cloud sync ---
4293
+ {
4294
+ title: "Encryption key lives in Keychain, not on disk \u2014 never expose the recovery phrase",
4295
+ domain: "security",
4296
+ priority: "p0",
4297
+ content: "The master encryption key is stored in macOS Keychain (Secure Enclave) or Linux secret-tool \u2014 NOT as a file. There is no ~/.exe-os/master.key on modern installs. If an older install had one, it was auto-migrated to Keychain and the file deleted. Device linking uses a 24-word BIP39 recovery phrase: Device 1 runs `exe-os cloud link --show-full` in their local Terminal to reveal it, Device 2 runs `exe-os cloud` and pastes the phrase to import the key into its own Keychain, then cloud sync pulls encrypted memories. NEVER display, log, or return the recovery phrase in agent output. MCP tools are hardened \u2014 they cannot reveal it. If the user needs the phrase, tell them: 'Run exe-os cloud link --show-full in your Terminal.' If searching for master.key returns nothing, that is CORRECT \u2014 the key is in Keychain."
4298
+ },
4321
4299
  // --- MCP is the ONLY data interface ---
4322
4300
  {
4323
4301
  title: "MCP disconnect \u2014 ask the user, never work around it",