@askexenow/exe-os 0.9.93 → 0.9.94

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 (88) hide show
  1. package/deploy/compose/docker-compose.yml +1 -0
  2. package/dist/bin/agentic-ontology-backfill.js +65 -8
  3. package/dist/bin/agentic-reflection-backfill.js +54 -3
  4. package/dist/bin/agentic-semantic-label.js +54 -3
  5. package/dist/bin/backfill-conversations.js +69 -9
  6. package/dist/bin/backfill-responses.js +69 -9
  7. package/dist/bin/backfill-vectors.js +54 -3
  8. package/dist/bin/bulk-sync-postgres.js +66 -8
  9. package/dist/bin/cleanup-stale-review-tasks.js +118 -13
  10. package/dist/bin/cli.js +1558 -466
  11. package/dist/bin/customer-readiness.js +51 -0
  12. package/dist/bin/exe-agent.js +17 -3
  13. package/dist/bin/exe-assign.js +75 -9
  14. package/dist/bin/exe-boot.js +111 -12
  15. package/dist/bin/exe-call.js +17 -3
  16. package/dist/bin/exe-cloud.js +76 -10
  17. package/dist/bin/exe-dispatch.js +133 -18
  18. package/dist/bin/exe-doctor.js +75 -9
  19. package/dist/bin/exe-export-behaviors.js +75 -9
  20. package/dist/bin/exe-forget.js +94 -9
  21. package/dist/bin/exe-gateway.js +132 -18
  22. package/dist/bin/exe-heartbeat.js +118 -13
  23. package/dist/bin/exe-kill.js +75 -9
  24. package/dist/bin/exe-launch-agent.js +75 -9
  25. package/dist/bin/exe-new-employee.js +18 -4
  26. package/dist/bin/exe-pending-messages.js +118 -13
  27. package/dist/bin/exe-pending-notifications.js +118 -13
  28. package/dist/bin/exe-pending-reviews.js +118 -13
  29. package/dist/bin/exe-rename.js +75 -9
  30. package/dist/bin/exe-review.js +75 -9
  31. package/dist/bin/exe-search.js +100 -9
  32. package/dist/bin/exe-session-cleanup.js +133 -18
  33. package/dist/bin/exe-settings.js +1 -0
  34. package/dist/bin/exe-start-codex.js +65 -8
  35. package/dist/bin/exe-start-opencode.js +65 -8
  36. package/dist/bin/exe-status.js +118 -13
  37. package/dist/bin/exe-support.js +1 -0
  38. package/dist/bin/exe-team.js +75 -9
  39. package/dist/bin/git-sweep.js +133 -18
  40. package/dist/bin/graph-backfill.js +65 -8
  41. package/dist/bin/graph-export.js +75 -9
  42. package/dist/bin/intercom-check.js +133 -18
  43. package/dist/bin/scan-tasks.js +133 -18
  44. package/dist/bin/setup.js +55 -4
  45. package/dist/bin/shard-migrate.js +65 -8
  46. package/dist/bin/stack-update.js +5 -6
  47. package/dist/bin/update.js +1 -1
  48. package/dist/gateway/index.js +133 -18
  49. package/dist/hooks/bug-report-worker.js +133 -18
  50. package/dist/hooks/codex-stop-task-finalizer.js +123 -14
  51. package/dist/hooks/commit-complete.js +133 -18
  52. package/dist/hooks/error-recall.js +100 -9
  53. package/dist/hooks/ingest.js +75 -9
  54. package/dist/hooks/instructions-loaded.js +75 -9
  55. package/dist/hooks/notification.js +75 -9
  56. package/dist/hooks/post-compact.js +310 -50
  57. package/dist/hooks/post-tool-combined.js +433 -13
  58. package/dist/hooks/pre-compact.js +133 -18
  59. package/dist/hooks/pre-tool-use.js +118 -13
  60. package/dist/hooks/prompt-submit.js +191 -19
  61. package/dist/hooks/session-end.js +133 -18
  62. package/dist/hooks/session-start.js +143 -13
  63. package/dist/hooks/stop.js +118 -13
  64. package/dist/hooks/subagent-stop.js +118 -13
  65. package/dist/hooks/summary-worker.js +96 -7
  66. package/dist/index.js +133 -18
  67. package/dist/lib/cloud-sync.js +38 -0
  68. package/dist/lib/consolidation.js +3 -1
  69. package/dist/lib/database.js +37 -0
  70. package/dist/lib/db.js +37 -0
  71. package/dist/lib/device-registry.js +37 -0
  72. package/dist/lib/employee-templates.js +17 -3
  73. package/dist/lib/exe-daemon.js +913 -42
  74. package/dist/lib/hybrid-search.js +100 -9
  75. package/dist/lib/license.js +1 -1
  76. package/dist/lib/messaging.js +40 -4
  77. package/dist/lib/schedules.js +54 -3
  78. package/dist/lib/store.js +75 -9
  79. package/dist/lib/tasks.js +58 -9
  80. package/dist/lib/tmux-routing.js +58 -9
  81. package/dist/mcp/server.js +875 -42
  82. package/dist/mcp/tools/create-task.js +67 -12
  83. package/dist/mcp/tools/list-tasks.js +46 -5
  84. package/dist/mcp/tools/send-message.js +40 -4
  85. package/dist/mcp/tools/update-task.js +58 -9
  86. package/dist/runtime/index.js +133 -18
  87. package/dist/tui/App.js +132 -18
  88. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3574,6 +3574,20 @@ async function ensureSchema() {
3574
3574
  });
3575
3575
  } catch {
3576
3576
  }
3577
+ try {
3578
+ await client.execute({
3579
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3580
+ args: []
3581
+ });
3582
+ } catch {
3583
+ }
3584
+ try {
3585
+ await client.execute({
3586
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3587
+ args: []
3588
+ });
3589
+ } catch {
3590
+ }
3577
3591
  await client.executeMultiple(`
3578
3592
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3579
3593
  content_text,
@@ -3825,6 +3839,22 @@ async function ensureSchema() {
3825
3839
  );
3826
3840
  } catch {
3827
3841
  }
3842
+ for (const col of [
3843
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3844
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3845
+ ]) {
3846
+ try {
3847
+ await client.execute(col);
3848
+ } catch {
3849
+ }
3850
+ }
3851
+ try {
3852
+ await client.execute({
3853
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3854
+ args: []
3855
+ });
3856
+ } catch {
3857
+ }
3828
3858
  try {
3829
3859
  await client.execute({
3830
3860
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3867,6 +3897,13 @@ async function ensureSchema() {
3867
3897
  } catch {
3868
3898
  }
3869
3899
  }
3900
+ try {
3901
+ await client.execute({
3902
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3903
+ args: []
3904
+ });
3905
+ } catch {
3906
+ }
3870
3907
  try {
3871
3908
  await client.execute({
3872
3909
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -3927,7 +3964,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3927
3964
  import os8 from "os";
3928
3965
  import path10 from "path";
3929
3966
  import { jwtVerify, importSPKI } from "jose";
3930
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
3967
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
3931
3968
  var init_license = __esm({
3932
3969
  "src/lib/license.ts"() {
3933
3970
  "use strict";
@@ -3935,6 +3972,7 @@ var init_license = __esm({
3935
3972
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3936
3973
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3937
3974
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3975
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3938
3976
  PLAN_LIMITS = {
3939
3977
  free: { devices: 1, employees: 1, memories: 5e3 },
3940
3978
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -4591,8 +4629,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
4591
4629
  const complexity = input.complexity ?? "standard";
4592
4630
  const sessionScope = earlySessionScope;
4593
4631
  await client.execute({
4594
- sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, created_at, updated_at)
4595
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4632
+ sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, session_scope, spawn_runtime, spawn_model, created_at, updated_at)
4633
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4596
4634
  args: [
4597
4635
  id,
4598
4636
  input.title,
@@ -4612,6 +4650,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
4612
4650
  0,
4613
4651
  null,
4614
4652
  sessionScope,
4653
+ input.spawnRuntime ?? null,
4654
+ input.spawnModel ?? null,
4615
4655
  now,
4616
4656
  now
4617
4657
  ]
@@ -4668,7 +4708,9 @@ ${input.context}
4668
4708
  budgetTokens: input.budgetTokens ?? null,
4669
4709
  budgetFallbackModel: input.budgetFallbackModel ?? null,
4670
4710
  tokensUsed: 0,
4671
- tokensWarnedAt: null
4711
+ tokensWarnedAt: null,
4712
+ spawnRuntime: input.spawnRuntime ?? null,
4713
+ spawnModel: input.spawnModel ?? null
4672
4714
  };
4673
4715
  }
4674
4716
  async function listTasks(input) {
@@ -4718,7 +4760,9 @@ async function listTasks(input) {
4718
4760
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
4719
4761
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
4720
4762
  tokensUsed: Number(r.tokens_used ?? 0),
4721
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
4763
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
4764
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
4765
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
4722
4766
  }));
4723
4767
  }
4724
4768
  function isTmuxSessionAlive(identifier) {
@@ -6086,6 +6130,8 @@ async function updateTask(input) {
6086
6130
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
6087
6131
  tokensUsed: Number(row.tokens_used ?? 0),
6088
6132
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
6133
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
6134
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
6089
6135
  nextTask
6090
6136
  };
6091
6137
  }
@@ -6581,6 +6627,7 @@ function resolveExeSession() {
6581
6627
  const mySession = getMySession();
6582
6628
  if (!mySession) return null;
6583
6629
  const fromSessionName = extractRootExe(mySession);
6630
+ let candidate = null;
6584
6631
  try {
6585
6632
  const key = getSessionKey();
6586
6633
  const parentExe = getParentExe(key);
@@ -6591,13 +6638,47 @@ function resolveExeSession() {
6591
6638
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6592
6639
  `
6593
6640
  );
6594
- return fromSessionName;
6641
+ candidate = fromSessionName;
6642
+ } else {
6643
+ candidate = fromCache;
6595
6644
  }
6596
- return fromCache;
6597
6645
  }
6598
6646
  } catch {
6599
6647
  }
6600
- return fromSessionName ?? mySession;
6648
+ if (!candidate) {
6649
+ candidate = fromSessionName ?? mySession;
6650
+ }
6651
+ if (candidate && isRootSession(candidate)) {
6652
+ try {
6653
+ const transport = getTransport();
6654
+ const liveSessions = transport.listSessions();
6655
+ if (!liveSessions.includes(candidate)) {
6656
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
6657
+ if (liveRoots.length === 1) {
6658
+ process.stderr.write(
6659
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
6660
+ `
6661
+ );
6662
+ return liveRoots[0];
6663
+ } else if (liveRoots.length > 1) {
6664
+ const base = candidate.replace(/\d+$/, "");
6665
+ const match = liveRoots.find((s) => s.startsWith(base));
6666
+ const chosen = match ?? liveRoots[0];
6667
+ process.stderr.write(
6668
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
6669
+ `
6670
+ );
6671
+ return chosen;
6672
+ }
6673
+ process.stderr.write(
6674
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
6675
+ `
6676
+ );
6677
+ }
6678
+ } catch {
6679
+ }
6680
+ }
6681
+ return candidate;
6601
6682
  }
6602
6683
  function isEmployeeAlive(sessionName) {
6603
6684
  return getTransport().isAlive(sessionName);
@@ -6999,7 +7080,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6999
7080
  }
7000
7081
  const spawnCwd = opts?.cwd ?? projectDir;
7001
7082
  const useExeAgent = !!(opts?.model && opts?.provider);
7002
- const agentRtConfig = getAgentRuntime(employeeName);
7083
+ const baseRtConfig = getAgentRuntime(employeeName);
7084
+ const agentRtConfig = {
7085
+ ...baseRtConfig,
7086
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
7087
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
7088
+ };
7003
7089
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
7004
7090
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
7005
7091
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -8259,6 +8345,20 @@ var init_platform_procedures = __esm({
8259
8345
  priority: "p1",
8260
8346
  content: "Once per session (COO boot only, never repeat), call list_my_feature_requests to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no requests exist or none are shipped, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
8261
8347
  },
8348
+ // --- Tool guidance ---
8349
+ {
8350
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
8351
+ domain: "tools",
8352
+ priority: "p2",
8353
+ content: "The company_actions tool executes business actions through gateway connectors (e.g. send WhatsApp, trigger workflows, update CRM). It routes through the exe-gateway on the VPS. Actions are defined by the customer's gateway configuration \u2014 each connector (WhatsApp, Shopify, email, etc.) exposes specific actions. Use query_company_brain to find available data first, then company_actions to act on it. Requires gateway auth token. Read-only founders should NOT use this \u2014 it mutates external state."
8354
+ },
8355
+ // --- Release awareness ---
8356
+ {
8357
+ title: "What's New check \u2014 surface new features after update",
8358
+ domain: "support",
8359
+ priority: "p1",
8360
+ content: "Once per session (COO boot only, never repeat), check if the installed exe-os version is newer than the last session. If it is, read the bundled release-notes.json (at the package root) and surface a brief summary to the founder: 'Updated to exe-os vX.Y.Z \u2014 N new features, M fixes.' List the top 3 features by name. This helps the founder know what they got from the update. If release-notes.json doesn't exist or the version hasn't changed, skip silently. Never repeat this check in the same session."
8361
+ },
8262
8362
  // --- Platform vs Customer ownership ---
8263
8363
  {
8264
8364
  title: "What the platform provides vs what you customize",
@@ -8347,13 +8447,13 @@ var init_platform_procedures = __esm({
8347
8447
  title: "MCP tools \u2014 memory, decision, and search",
8348
8448
  domain: "tool-use",
8349
8449
  priority: "p1",
8350
- content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
8450
+ content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). Supports as_of param for bi-temporal queries (what did I know at time X?), kind param to filter by memory type (decision, procedure, observation, raw, conversation, behavior). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. Supports kind param and procedure_for domain tag for procedure-type memories. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="supersede") / supersede: replace an old memory with a new version (old_id + new text). decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
8351
8451
  },
8352
8452
  {
8353
8453
  title: "MCP tools \u2014 task orchestration",
8354
8454
  domain: "tool-use",
8355
8455
  priority: "p1",
8356
- content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.'
8456
+ content: `task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Supports spawn_runtime and spawn_model params to override the agent's default runtime/model for a specific task. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.`
8357
8457
  },
8358
8458
  {
8359
8459
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -8383,7 +8483,7 @@ var init_platform_procedures = __esm({
8383
8483
  title: "MCP tools \u2014 admin, config, and operations",
8384
8484
  domain: "tool-use",
8385
8485
  priority: "p1",
8386
- content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. mcp_ping(): daemon health + license status + tool usage stats.'
8486
+ content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. Returns top-K tools ranked by relevance. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role identity. Returns drift score + recommendations. mcp_ping(): daemon health + license status + tool usage stats.'
8387
8487
  }
8388
8488
  ];
8389
8489
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -9067,6 +9167,8 @@ async function writeMemory(record) {
9067
9167
  source_type: record.source_type ?? null,
9068
9168
  tier: record.tier ?? classifyTier(record),
9069
9169
  supersedes_id: record.supersedes_id ?? null,
9170
+ valid_from: record.valid_from ?? record.timestamp,
9171
+ invalid_at: record.invalid_at ?? null,
9070
9172
  draft: record.draft ? 1 : 0,
9071
9173
  memory_type: memoryType,
9072
9174
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -9085,7 +9187,8 @@ async function writeMemory(record) {
9085
9187
  token_cost: record.token_cost ?? null,
9086
9188
  audience: record.audience ?? null,
9087
9189
  language_type: record.language_type ?? inferLanguageType(record),
9088
- parent_memory_id: record.parent_memory_id ?? null
9190
+ parent_memory_id: record.parent_memory_id ?? null,
9191
+ procedure_for: record.procedure_for ?? null
9089
9192
  };
9090
9193
  _pendingRecords.push(dbRow);
9091
9194
  orgBus.emit({
@@ -9140,6 +9243,8 @@ async function flushBatch() {
9140
9243
  const sourceType = row.source_type ?? null;
9141
9244
  const tier = row.tier ?? 3;
9142
9245
  const supersedesId = row.supersedes_id ?? null;
9246
+ const validFrom = row.valid_from ?? row.timestamp;
9247
+ const invalidAt = row.invalid_at ?? null;
9143
9248
  const draft = row.draft ? 1 : 0;
9144
9249
  const memoryType = row.memory_type ?? "raw";
9145
9250
  const trajectory = row.trajectory ?? null;
@@ -9159,15 +9264,16 @@ async function flushBatch() {
9159
9264
  const audience = row.audience ?? null;
9160
9265
  const languageType = row.language_type ?? null;
9161
9266
  const parentMemoryId = row.parent_memory_id ?? null;
9267
+ const procedureFor = row.procedure_for ?? null;
9162
9268
  const cols = `id, agent_id, agent_role, session_id, timestamp,
9163
9269
  tool_name, project_name,
9164
9270
  has_error, raw_text, vector, version, task_id, importance, status,
9165
9271
  confidence, last_accessed,
9166
9272
  workspace_id, document_id, user_id, char_offset, page_number,
9167
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
9273
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
9168
9274
  intent, outcome, domain, referenced_entities, retrieval_count,
9169
9275
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
9170
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
9276
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
9171
9277
  const metaArgs = [
9172
9278
  intent,
9173
9279
  outcome,
@@ -9183,7 +9289,8 @@ async function flushBatch() {
9183
9289
  tokenCost,
9184
9290
  audience,
9185
9291
  languageType,
9186
- parentMemoryId
9292
+ parentMemoryId,
9293
+ procedureFor
9187
9294
  ];
9188
9295
  const baseArgs = [
9189
9296
  row.id,
@@ -9212,6 +9319,8 @@ async function flushBatch() {
9212
9319
  sourceType,
9213
9320
  tier,
9214
9321
  supersedesId,
9322
+ validFrom,
9323
+ invalidAt,
9215
9324
  draft,
9216
9325
  memoryType,
9217
9326
  trajectory,
@@ -9219,8 +9328,8 @@ async function flushBatch() {
9219
9328
  ];
9220
9329
  return {
9221
9330
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
9222
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
9223
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
9331
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
9332
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
9224
9333
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
9225
9334
  };
9226
9335
  };
@@ -9336,6 +9445,12 @@ async function searchMemories(queryVector, agentId, options) {
9336
9445
  AND vector IS NOT NULL${statusFilter}${draftFilter}
9337
9446
  AND COALESCE(confidence, 0.7) >= 0.3`;
9338
9447
  const args = [agentId];
9448
+ if (options?.asOf) {
9449
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
9450
+ args.push(options.asOf, options.asOf);
9451
+ } else {
9452
+ sql += ` AND invalid_at IS NULL`;
9453
+ }
9339
9454
  const scope = buildWikiScopeFilter(options, "");
9340
9455
  sql += scope.clause;
9341
9456
  args.push(...scope.args);
@@ -2488,6 +2488,20 @@ async function ensureSchema() {
2488
2488
  });
2489
2489
  } catch {
2490
2490
  }
2491
+ try {
2492
+ await client.execute({
2493
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
2494
+ args: []
2495
+ });
2496
+ } catch {
2497
+ }
2498
+ try {
2499
+ await client.execute({
2500
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
2501
+ args: []
2502
+ });
2503
+ } catch {
2504
+ }
2491
2505
  await client.executeMultiple(`
2492
2506
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
2493
2507
  content_text,
@@ -2739,6 +2753,22 @@ async function ensureSchema() {
2739
2753
  );
2740
2754
  } catch {
2741
2755
  }
2756
+ for (const col of [
2757
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
2758
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
2759
+ ]) {
2760
+ try {
2761
+ await client.execute(col);
2762
+ } catch {
2763
+ }
2764
+ }
2765
+ try {
2766
+ await client.execute({
2767
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
2768
+ args: []
2769
+ });
2770
+ } catch {
2771
+ }
2742
2772
  try {
2743
2773
  await client.execute({
2744
2774
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -2781,6 +2811,13 @@ async function ensureSchema() {
2781
2811
  } catch {
2782
2812
  }
2783
2813
  }
2814
+ try {
2815
+ await client.execute({
2816
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
2817
+ args: []
2818
+ });
2819
+ } catch {
2820
+ }
2784
2821
  try {
2785
2822
  await client.execute({
2786
2823
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -3734,6 +3771,7 @@ import { jwtVerify, importSPKI } from "jose";
3734
3771
  var LICENSE_PATH = path6.join(EXE_AI_DIR, "license.key");
3735
3772
  var CACHE_PATH = path6.join(EXE_AI_DIR, "license-cache.json");
3736
3773
  var DEVICE_ID_PATH = path6.join(EXE_AI_DIR, "device-id");
3774
+ var API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3737
3775
  function loadDeviceId() {
3738
3776
  const deviceJsonPath = path6.join(EXE_AI_DIR, "device.json");
3739
3777
  try {
@@ -635,6 +635,7 @@ async function selectUnconsolidated(client, limit = 200) {
635
635
  sql: `SELECT id, agent_id, project_name, tool_name, raw_text, timestamp
636
636
  FROM memories
637
637
  WHERE consolidated = 0
638
+ AND COALESCE(memory_type, 'raw') != 'procedure'
638
639
  ORDER BY timestamp DESC
639
640
  LIMIT ?`,
640
641
  args: [limit]
@@ -1009,7 +1010,8 @@ async function isUserIdle(client, idleMinutes = 30) {
1009
1010
  async function countUnconsolidated(client) {
1010
1011
  const result = await client.execute({
1011
1012
  sql: `SELECT COUNT(*) as cnt FROM memories
1012
- WHERE consolidated = 0`,
1013
+ WHERE consolidated = 0
1014
+ AND COALESCE(memory_type, 'raw') != 'procedure'`,
1013
1015
  args: []
1014
1016
  });
1015
1017
  return Number(result.rows[0]?.cnt ?? 0);
@@ -2404,6 +2404,20 @@ async function ensureSchema() {
2404
2404
  });
2405
2405
  } catch {
2406
2406
  }
2407
+ try {
2408
+ await client.execute({
2409
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
2410
+ args: []
2411
+ });
2412
+ } catch {
2413
+ }
2414
+ try {
2415
+ await client.execute({
2416
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
2417
+ args: []
2418
+ });
2419
+ } catch {
2420
+ }
2407
2421
  await client.executeMultiple(`
2408
2422
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
2409
2423
  content_text,
@@ -2655,6 +2669,22 @@ async function ensureSchema() {
2655
2669
  );
2656
2670
  } catch {
2657
2671
  }
2672
+ for (const col of [
2673
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
2674
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
2675
+ ]) {
2676
+ try {
2677
+ await client.execute(col);
2678
+ } catch {
2679
+ }
2680
+ }
2681
+ try {
2682
+ await client.execute({
2683
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
2684
+ args: []
2685
+ });
2686
+ } catch {
2687
+ }
2658
2688
  try {
2659
2689
  await client.execute({
2660
2690
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -2697,6 +2727,13 @@ async function ensureSchema() {
2697
2727
  } catch {
2698
2728
  }
2699
2729
  }
2730
+ try {
2731
+ await client.execute({
2732
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
2733
+ args: []
2734
+ });
2735
+ } catch {
2736
+ }
2700
2737
  try {
2701
2738
  await client.execute({
2702
2739
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
package/dist/lib/db.js CHANGED
@@ -2404,6 +2404,20 @@ async function ensureSchema() {
2404
2404
  });
2405
2405
  } catch {
2406
2406
  }
2407
+ try {
2408
+ await client.execute({
2409
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
2410
+ args: []
2411
+ });
2412
+ } catch {
2413
+ }
2414
+ try {
2415
+ await client.execute({
2416
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
2417
+ args: []
2418
+ });
2419
+ } catch {
2420
+ }
2407
2421
  await client.executeMultiple(`
2408
2422
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
2409
2423
  content_text,
@@ -2655,6 +2669,22 @@ async function ensureSchema() {
2655
2669
  );
2656
2670
  } catch {
2657
2671
  }
2672
+ for (const col of [
2673
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
2674
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
2675
+ ]) {
2676
+ try {
2677
+ await client.execute(col);
2678
+ } catch {
2679
+ }
2680
+ }
2681
+ try {
2682
+ await client.execute({
2683
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
2684
+ args: []
2685
+ });
2686
+ } catch {
2687
+ }
2658
2688
  try {
2659
2689
  await client.execute({
2660
2690
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -2697,6 +2727,13 @@ async function ensureSchema() {
2697
2727
  } catch {
2698
2728
  }
2699
2729
  }
2730
+ try {
2731
+ await client.execute({
2732
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
2733
+ args: []
2734
+ });
2735
+ } catch {
2736
+ }
2700
2737
  try {
2701
2738
  await client.execute({
2702
2739
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -2433,6 +2433,20 @@ async function ensureSchema() {
2433
2433
  });
2434
2434
  } catch {
2435
2435
  }
2436
+ try {
2437
+ await client.execute({
2438
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
2439
+ args: []
2440
+ });
2441
+ } catch {
2442
+ }
2443
+ try {
2444
+ await client.execute({
2445
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
2446
+ args: []
2447
+ });
2448
+ } catch {
2449
+ }
2436
2450
  await client.executeMultiple(`
2437
2451
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
2438
2452
  content_text,
@@ -2684,6 +2698,22 @@ async function ensureSchema() {
2684
2698
  );
2685
2699
  } catch {
2686
2700
  }
2701
+ for (const col of [
2702
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
2703
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
2704
+ ]) {
2705
+ try {
2706
+ await client.execute(col);
2707
+ } catch {
2708
+ }
2709
+ }
2710
+ try {
2711
+ await client.execute({
2712
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
2713
+ args: []
2714
+ });
2715
+ } catch {
2716
+ }
2687
2717
  try {
2688
2718
  await client.execute({
2689
2719
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -2726,6 +2756,13 @@ async function ensureSchema() {
2726
2756
  } catch {
2727
2757
  }
2728
2758
  }
2759
+ try {
2760
+ await client.execute({
2761
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
2762
+ args: []
2763
+ });
2764
+ } catch {
2765
+ }
2729
2766
  try {
2730
2767
  await client.execute({
2731
2768
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -253,6 +253,20 @@ var PLATFORM_PROCEDURES = [
253
253
  priority: "p1",
254
254
  content: "Once per session (COO boot only, never repeat), call list_my_feature_requests to check if any previously filed feature requests have been shipped by AskExe. If any request has status 'shipped' with a shipped_version, surface it to the founder immediately: '\u{1F680} N feature(s) shipped \u2014 run exe-os update to get version X.Y.Z'. This is a one-time check at boot, not a recurring poll. If no requests exist or none are shipped, skip silently. If the MCP tool is unavailable or the network call fails, skip silently \u2014 this is informational, not blocking."
255
255
  },
256
+ // --- Tool guidance ---
257
+ {
258
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
259
+ domain: "tools",
260
+ priority: "p2",
261
+ content: "The company_actions tool executes business actions through gateway connectors (e.g. send WhatsApp, trigger workflows, update CRM). It routes through the exe-gateway on the VPS. Actions are defined by the customer's gateway configuration \u2014 each connector (WhatsApp, Shopify, email, etc.) exposes specific actions. Use query_company_brain to find available data first, then company_actions to act on it. Requires gateway auth token. Read-only founders should NOT use this \u2014 it mutates external state."
262
+ },
263
+ // --- Release awareness ---
264
+ {
265
+ title: "What's New check \u2014 surface new features after update",
266
+ domain: "support",
267
+ priority: "p1",
268
+ content: "Once per session (COO boot only, never repeat), check if the installed exe-os version is newer than the last session. If it is, read the bundled release-notes.json (at the package root) and surface a brief summary to the founder: 'Updated to exe-os vX.Y.Z \u2014 N new features, M fixes.' List the top 3 features by name. This helps the founder know what they got from the update. If release-notes.json doesn't exist or the version hasn't changed, skip silently. Never repeat this check in the same session."
269
+ },
256
270
  // --- Platform vs Customer ownership ---
257
271
  {
258
272
  title: "What the platform provides vs what you customize",
@@ -341,13 +355,13 @@ var PLATFORM_PROCEDURES = [
341
355
  title: "MCP tools \u2014 memory, decision, and search",
342
356
  domain: "tool-use",
343
357
  priority: "p1",
344
- content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
358
+ content: `memory(action="recall") / recall_my_memory: search your own memories (semantic + FTS). Supports as_of param for bi-temporal queries (what did I know at time X?), kind param to filter by memory type (decision, procedure, observation, raw, conversation, behavior). memory(action="ask_team") / ask_team_memory: search a colleague's memories by agent name. memory(action="store") / store_memory: persist a memory. Supports kind param and procedure_for domain tag for procedure-type memories. memory(action="commit") / commit_memory: high-importance memory that survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal memory window. Requires session_id + target_timestamp. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="supersede") / supersede: replace an old memory with a new version (old_id + new text). decision(action="store") / store_decision: record an architectural decision (domain, decision, rationale). decision(action="get") / get_decision: retrieve a past decision by domain or query.`
345
359
  },
346
360
  {
347
361
  title: "MCP tools \u2014 task orchestration",
348
362
  domain: "tool-use",
349
363
  priority: "p1",
350
- content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.'
364
+ content: `task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Supports spawn_runtime and spawn_model params to override the agent's default runtime/model for a specific task. task(action="list") / list_tasks: query tasks by status, assignee, project. task(action="get") / get_task: fetch full task details by task_id. task(action="update") / update_task: change status (in_progress, done, blocked, cancelled) + result summary. task(action="close") / close_task: finalize a reviewed task (COO only). task(action="checkpoint") / checkpoint_task: save progress state for crash recovery. task(action="resume") / resume_employee: re-spawn an employee session for an existing task.`
351
365
  },
352
366
  {
353
367
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -377,7 +391,7 @@ var PLATFORM_PROCEDURES = [
377
391
  title: "MCP tools \u2014 admin, config, and operations",
378
392
  domain: "tool-use",
379
393
  priority: "p1",
380
- content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. mcp_ping(): daemon health + license status + tool usage stats.'
394
+ content: 'config(action="list_employees"): view roster. config(action="agent_spend"): token usage per agent. config(action="daemon_health"): check exed status. config(action="license_status"): check license. config(action="cloud_sync"): force sync. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="company_procedure", subaction="store|list|deactivate"): manage company procedures. config(action="global_procedure"): list all procedures (platform + company). config(action="create_trigger|list_triggers"): scheduled agent jobs. config(action="export_orchestration|import_orchestration"): portable org state. diagnostics(action="healthcheck|doctor|status_brief|check_update|cloud_status"): system diagnostics. diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. Returns top-K tools ranked by relevance. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role identity. Returns drift score + recommendations. mcp_ping(): daemon health + license status + tool usage stats.'
381
395
  }
382
396
  ];
383
397
  var PLATFORM_PROCEDURE_TITLES = new Set(