@askexenow/exe-os 0.9.93 → 0.9.95

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 +121 -13
  10. package/dist/bin/cli.js +1561 -466
  11. package/dist/bin/customer-readiness.js +61 -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 +114 -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 +136 -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 +135 -18
  22. package/dist/bin/exe-heartbeat.js +121 -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 +121 -13
  27. package/dist/bin/exe-pending-notifications.js +121 -13
  28. package/dist/bin/exe-pending-reviews.js +121 -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 +136 -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 +121 -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 +136 -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 +136 -18
  43. package/dist/bin/scan-tasks.js +136 -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 +136 -18
  49. package/dist/hooks/bug-report-worker.js +136 -18
  50. package/dist/hooks/codex-stop-task-finalizer.js +126 -14
  51. package/dist/hooks/commit-complete.js +136 -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 +313 -50
  57. package/dist/hooks/post-tool-combined.js +436 -13
  58. package/dist/hooks/pre-compact.js +136 -18
  59. package/dist/hooks/pre-tool-use.js +121 -13
  60. package/dist/hooks/prompt-submit.js +194 -19
  61. package/dist/hooks/session-end.js +136 -18
  62. package/dist/hooks/session-start.js +146 -13
  63. package/dist/hooks/stop.js +121 -13
  64. package/dist/hooks/subagent-stop.js +121 -13
  65. package/dist/hooks/summary-worker.js +99 -7
  66. package/dist/index.js +136 -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 +916 -42
  74. package/dist/lib/hybrid-search.js +100 -9
  75. package/dist/lib/license.js +1 -1
  76. package/dist/lib/messaging.js +43 -4
  77. package/dist/lib/schedules.js +54 -3
  78. package/dist/lib/store.js +75 -9
  79. package/dist/lib/tasks.js +61 -9
  80. package/dist/lib/tmux-routing.js +61 -9
  81. package/dist/mcp/server.js +878 -42
  82. package/dist/mcp/tools/create-task.js +70 -12
  83. package/dist/mcp/tools/list-tasks.js +49 -5
  84. package/dist/mcp/tools/send-message.js +43 -4
  85. package/dist/mcp/tools/update-task.js +61 -9
  86. package/dist/runtime/index.js +136 -18
  87. package/dist/tui/App.js +135 -18
  88. package/package.json +1 -1
@@ -3353,6 +3353,20 @@ async function ensureSchema() {
3353
3353
  });
3354
3354
  } catch {
3355
3355
  }
3356
+ try {
3357
+ await client.execute({
3358
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3359
+ args: []
3360
+ });
3361
+ } catch {
3362
+ }
3363
+ try {
3364
+ await client.execute({
3365
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3366
+ args: []
3367
+ });
3368
+ } catch {
3369
+ }
3356
3370
  await client.executeMultiple(`
3357
3371
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3358
3372
  content_text,
@@ -3604,6 +3618,22 @@ async function ensureSchema() {
3604
3618
  );
3605
3619
  } catch {
3606
3620
  }
3621
+ for (const col of [
3622
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3623
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3624
+ ]) {
3625
+ try {
3626
+ await client.execute(col);
3627
+ } catch {
3628
+ }
3629
+ }
3630
+ try {
3631
+ await client.execute({
3632
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3633
+ args: []
3634
+ });
3635
+ } catch {
3636
+ }
3607
3637
  try {
3608
3638
  await client.execute({
3609
3639
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3646,6 +3676,13 @@ async function ensureSchema() {
3646
3676
  } catch {
3647
3677
  }
3648
3678
  }
3679
+ try {
3680
+ await client.execute({
3681
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3682
+ args: []
3683
+ });
3684
+ } catch {
3685
+ }
3649
3686
  try {
3650
3687
  await client.execute({
3651
3688
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -3706,7 +3743,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3706
3743
  import os7 from "os";
3707
3744
  import path10 from "path";
3708
3745
  import { jwtVerify, importSPKI } from "jose";
3709
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
3746
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
3710
3747
  var init_license = __esm({
3711
3748
  "src/lib/license.ts"() {
3712
3749
  "use strict";
@@ -3714,6 +3751,7 @@ var init_license = __esm({
3714
3751
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3715
3752
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3716
3753
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3754
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3717
3755
  PLAN_LIMITS = {
3718
3756
  free: { devices: 1, employees: 1, memories: 5e3 },
3719
3757
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -4331,8 +4369,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
4331
4369
  const complexity = input2.complexity ?? "standard";
4332
4370
  const sessionScope = earlySessionScope;
4333
4371
  await client.execute({
4334
- 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)
4335
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4372
+ 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)
4373
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4336
4374
  args: [
4337
4375
  id,
4338
4376
  input2.title,
@@ -4352,6 +4390,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
4352
4390
  0,
4353
4391
  null,
4354
4392
  sessionScope,
4393
+ input2.spawnRuntime ?? null,
4394
+ input2.spawnModel ?? null,
4355
4395
  now,
4356
4396
  now
4357
4397
  ]
@@ -4408,7 +4448,9 @@ ${input2.context}
4408
4448
  budgetTokens: input2.budgetTokens ?? null,
4409
4449
  budgetFallbackModel: input2.budgetFallbackModel ?? null,
4410
4450
  tokensUsed: 0,
4411
- tokensWarnedAt: null
4451
+ tokensWarnedAt: null,
4452
+ spawnRuntime: input2.spawnRuntime ?? null,
4453
+ spawnModel: input2.spawnModel ?? null
4412
4454
  };
4413
4455
  }
4414
4456
  async function listTasks(input2) {
@@ -4458,7 +4500,9 @@ async function listTasks(input2) {
4458
4500
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
4459
4501
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
4460
4502
  tokensUsed: Number(r.tokens_used ?? 0),
4461
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
4503
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
4504
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
4505
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
4462
4506
  }));
4463
4507
  }
4464
4508
  function isTmuxSessionAlive(identifier) {
@@ -5755,6 +5799,8 @@ async function updateTask(input2) {
5755
5799
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
5756
5800
  tokensUsed: Number(row.tokens_used ?? 0),
5757
5801
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
5802
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
5803
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
5758
5804
  nextTask
5759
5805
  };
5760
5806
  }
@@ -6247,9 +6293,13 @@ function getDispatchedBy(sessionKey) {
6247
6293
  }
6248
6294
  }
6249
6295
  function resolveExeSession() {
6296
+ if (process.env.EXE_SESSION_NAME) {
6297
+ return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
6298
+ }
6250
6299
  const mySession = getMySession();
6251
6300
  if (!mySession) return null;
6252
6301
  const fromSessionName = extractRootExe(mySession);
6302
+ let candidate = null;
6253
6303
  try {
6254
6304
  const key = getSessionKey();
6255
6305
  const parentExe = getParentExe(key);
@@ -6260,13 +6310,47 @@ function resolveExeSession() {
6260
6310
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6261
6311
  `
6262
6312
  );
6263
- return fromSessionName;
6313
+ candidate = fromSessionName;
6314
+ } else {
6315
+ candidate = fromCache;
6264
6316
  }
6265
- return fromCache;
6266
6317
  }
6267
6318
  } catch {
6268
6319
  }
6269
- return fromSessionName ?? mySession;
6320
+ if (!candidate) {
6321
+ candidate = fromSessionName ?? mySession;
6322
+ }
6323
+ if (candidate && isRootSession(candidate)) {
6324
+ try {
6325
+ const transport = getTransport();
6326
+ const liveSessions = transport.listSessions();
6327
+ if (!liveSessions.includes(candidate)) {
6328
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
6329
+ if (liveRoots.length === 1) {
6330
+ process.stderr.write(
6331
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
6332
+ `
6333
+ );
6334
+ return liveRoots[0];
6335
+ } else if (liveRoots.length > 1) {
6336
+ const base = candidate.replace(/\d+$/, "");
6337
+ const match = liveRoots.find((s) => s.startsWith(base));
6338
+ const chosen = match ?? liveRoots[0];
6339
+ process.stderr.write(
6340
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
6341
+ `
6342
+ );
6343
+ return chosen;
6344
+ }
6345
+ process.stderr.write(
6346
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
6347
+ `
6348
+ );
6349
+ }
6350
+ } catch {
6351
+ }
6352
+ }
6353
+ return candidate;
6270
6354
  }
6271
6355
  function isEmployeeAlive(sessionName) {
6272
6356
  return getTransport().isAlive(sessionName);
@@ -6668,7 +6752,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6668
6752
  }
6669
6753
  const spawnCwd = opts?.cwd ?? projectDir;
6670
6754
  const useExeAgent = !!(opts?.model && opts?.provider);
6671
- const agentRtConfig = getAgentRuntime(employeeName);
6755
+ const baseRtConfig = getAgentRuntime(employeeName);
6756
+ const agentRtConfig = {
6757
+ ...baseRtConfig,
6758
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
6759
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
6760
+ };
6672
6761
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
6673
6762
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
6674
6763
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -7961,6 +8050,20 @@ var init_platform_procedures = __esm({
7961
8050
  priority: "p1",
7962
8051
  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."
7963
8052
  },
8053
+ // --- Tool guidance ---
8054
+ {
8055
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
8056
+ domain: "tools",
8057
+ priority: "p2",
8058
+ 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."
8059
+ },
8060
+ // --- Release awareness ---
8061
+ {
8062
+ title: "What's New check \u2014 surface new features after update",
8063
+ domain: "support",
8064
+ priority: "p1",
8065
+ 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."
8066
+ },
7964
8067
  // --- Platform vs Customer ownership ---
7965
8068
  {
7966
8069
  title: "What the platform provides vs what you customize",
@@ -8049,13 +8152,13 @@ var init_platform_procedures = __esm({
8049
8152
  title: "MCP tools \u2014 memory, decision, and search",
8050
8153
  domain: "tool-use",
8051
8154
  priority: "p1",
8052
- 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.`
8155
+ content: `memory(action="recall") / recall_my_memory: search memories (semantic + FTS). Params: as_of (bi-temporal \u2014 what did I know at time X?), kind (decision|procedure|observation|raw|conversation|behavior), retrieval_mode (all|decisions_only|procedures_only|operational|recent_high_value). memory(action="ask_team") / ask_team_memory: search a colleague's memories. memory(action="store") / store_memory: persist a memory. Params: kind, procedure_for (domain tag for procedures). memory(action="commit") / commit_memory: high-importance, survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal window. Requires session_id + target_timestamp. memory(action="get_by_id"): fetch one memory by UUID with full untruncated text. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="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.`
8053
8156
  },
8054
8157
  {
8055
8158
  title: "MCP tools \u2014 task orchestration",
8056
8159
  domain: "tool-use",
8057
8160
  priority: "p1",
8058
- 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.'
8161
+ content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Params: blocked_by (task ID for dependency), parent_task_id (subtask hierarchy), reviewer, complexity (routine|standard|complex|critical), budget_tokens (max token cap), budget_fallback_model, spawn_runtime (override runtime: claude|codex|opencode), spawn_model (override model). 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.'
8059
8162
  },
8060
8163
  {
8061
8164
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -8085,7 +8188,7 @@ var init_platform_procedures = __esm({
8085
8188
  title: "MCP tools \u2014 admin, config, and operations",
8086
8189
  domain: "tool-use",
8087
8190
  priority: "p1",
8088
- 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.'
8191
+ content: 'config(action="list_employees"): view roster. config(action="set_agent_config"): view or change per-agent runtime + model. Call with no args to show all agents. 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. Supports cloud_action param: status|sync|reupload. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="worker_gate"): check spawn slot availability \u2014 alive/stale/reserved counts vs max. Use before dispatching. config(action="auto_wake_status"): orphaned tasks, blocked tasks, auto-wake retry status. config(action="orchestration_phase"): view/change org phase (phase_1_coo|phase_2_executives|phase_3_parallel_org). 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="pending_work_summary"): pending reviews + messages + notifications in one call. diagnostics(action="rename_employee"): rename an agent across all systems (roster, identity, DB, symlinks). diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role. mcp_ping(): daemon health + license status + tool usage stats.'
8089
8192
  }
8090
8193
  ];
8091
8194
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -8769,6 +8872,8 @@ async function writeMemory(record) {
8769
8872
  source_type: record.source_type ?? null,
8770
8873
  tier: record.tier ?? classifyTier(record),
8771
8874
  supersedes_id: record.supersedes_id ?? null,
8875
+ valid_from: record.valid_from ?? record.timestamp,
8876
+ invalid_at: record.invalid_at ?? null,
8772
8877
  draft: record.draft ? 1 : 0,
8773
8878
  memory_type: memoryType,
8774
8879
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -8787,7 +8892,8 @@ async function writeMemory(record) {
8787
8892
  token_cost: record.token_cost ?? null,
8788
8893
  audience: record.audience ?? null,
8789
8894
  language_type: record.language_type ?? inferLanguageType(record),
8790
- parent_memory_id: record.parent_memory_id ?? null
8895
+ parent_memory_id: record.parent_memory_id ?? null,
8896
+ procedure_for: record.procedure_for ?? null
8791
8897
  };
8792
8898
  _pendingRecords.push(dbRow);
8793
8899
  orgBus.emit({
@@ -8842,6 +8948,8 @@ async function flushBatch() {
8842
8948
  const sourceType = row.source_type ?? null;
8843
8949
  const tier = row.tier ?? 3;
8844
8950
  const supersedesId = row.supersedes_id ?? null;
8951
+ const validFrom = row.valid_from ?? row.timestamp;
8952
+ const invalidAt = row.invalid_at ?? null;
8845
8953
  const draft = row.draft ? 1 : 0;
8846
8954
  const memoryType = row.memory_type ?? "raw";
8847
8955
  const trajectory = row.trajectory ?? null;
@@ -8861,15 +8969,16 @@ async function flushBatch() {
8861
8969
  const audience = row.audience ?? null;
8862
8970
  const languageType = row.language_type ?? null;
8863
8971
  const parentMemoryId = row.parent_memory_id ?? null;
8972
+ const procedureFor = row.procedure_for ?? null;
8864
8973
  const cols = `id, agent_id, agent_role, session_id, timestamp,
8865
8974
  tool_name, project_name,
8866
8975
  has_error, raw_text, vector, version, task_id, importance, status,
8867
8976
  confidence, last_accessed,
8868
8977
  workspace_id, document_id, user_id, char_offset, page_number,
8869
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
8978
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
8870
8979
  intent, outcome, domain, referenced_entities, retrieval_count,
8871
8980
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
8872
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
8981
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
8873
8982
  const metaArgs = [
8874
8983
  intent,
8875
8984
  outcome,
@@ -8885,7 +8994,8 @@ async function flushBatch() {
8885
8994
  tokenCost,
8886
8995
  audience,
8887
8996
  languageType,
8888
- parentMemoryId
8997
+ parentMemoryId,
8998
+ procedureFor
8889
8999
  ];
8890
9000
  const baseArgs = [
8891
9001
  row.id,
@@ -8914,6 +9024,8 @@ async function flushBatch() {
8914
9024
  sourceType,
8915
9025
  tier,
8916
9026
  supersedesId,
9027
+ validFrom,
9028
+ invalidAt,
8917
9029
  draft,
8918
9030
  memoryType,
8919
9031
  trajectory,
@@ -8921,8 +9033,8 @@ async function flushBatch() {
8921
9033
  ];
8922
9034
  return {
8923
9035
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
8924
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
8925
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
9036
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
9037
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
8926
9038
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
8927
9039
  };
8928
9040
  };
@@ -9038,6 +9150,12 @@ async function searchMemories(queryVector, agentId, options) {
9038
9150
  AND vector IS NOT NULL${statusFilter}${draftFilter}
9039
9151
  AND COALESCE(confidence, 0.7) >= 0.3`;
9040
9152
  const args = [agentId];
9153
+ if (options?.asOf) {
9154
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
9155
+ args.push(options.asOf, options.asOf);
9156
+ } else {
9157
+ sql += ` AND invalid_at IS NULL`;
9158
+ }
9041
9159
  const scope = buildWikiScopeFilter(options, "");
9042
9160
  sql += scope.clause;
9043
9161
  args.push(...scope.args);
@@ -3349,6 +3349,20 @@ async function ensureSchema() {
3349
3349
  });
3350
3350
  } catch {
3351
3351
  }
3352
+ try {
3353
+ await client.execute({
3354
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3355
+ args: []
3356
+ });
3357
+ } catch {
3358
+ }
3359
+ try {
3360
+ await client.execute({
3361
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3362
+ args: []
3363
+ });
3364
+ } catch {
3365
+ }
3352
3366
  await client.executeMultiple(`
3353
3367
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3354
3368
  content_text,
@@ -3600,6 +3614,22 @@ async function ensureSchema() {
3600
3614
  );
3601
3615
  } catch {
3602
3616
  }
3617
+ for (const col of [
3618
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3619
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3620
+ ]) {
3621
+ try {
3622
+ await client.execute(col);
3623
+ } catch {
3624
+ }
3625
+ }
3626
+ try {
3627
+ await client.execute({
3628
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3629
+ args: []
3630
+ });
3631
+ } catch {
3632
+ }
3603
3633
  try {
3604
3634
  await client.execute({
3605
3635
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3642,6 +3672,13 @@ async function ensureSchema() {
3642
3672
  } catch {
3643
3673
  }
3644
3674
  }
3675
+ try {
3676
+ await client.execute({
3677
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3678
+ args: []
3679
+ });
3680
+ } catch {
3681
+ }
3645
3682
  try {
3646
3683
  await client.execute({
3647
3684
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -3702,7 +3739,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3702
3739
  import os7 from "os";
3703
3740
  import path10 from "path";
3704
3741
  import { jwtVerify, importSPKI } from "jose";
3705
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH;
3742
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE;
3706
3743
  var init_license = __esm({
3707
3744
  "src/lib/license.ts"() {
3708
3745
  "use strict";
@@ -3710,6 +3747,7 @@ var init_license = __esm({
3710
3747
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3711
3748
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3712
3749
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3750
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3713
3751
  }
3714
3752
  });
3715
3753
 
@@ -3753,6 +3791,9 @@ import { fileURLToPath as fileURLToPath2 } from "url";
3753
3791
  function getMySession() {
3754
3792
  return getTransport().getMySession();
3755
3793
  }
3794
+ function isRootSession(name) {
3795
+ return name.length > 0 && !name.includes("-");
3796
+ }
3756
3797
  function extractRootExe(name) {
3757
3798
  if (!name) return null;
3758
3799
  if (!name.includes("-")) return name;
@@ -3768,9 +3809,13 @@ function getParentExe(sessionKey) {
3768
3809
  }
3769
3810
  }
3770
3811
  function resolveExeSession() {
3812
+ if (process.env.EXE_SESSION_NAME) {
3813
+ return extractRootExe(process.env.EXE_SESSION_NAME) ?? process.env.EXE_SESSION_NAME;
3814
+ }
3771
3815
  const mySession = getMySession();
3772
3816
  if (!mySession) return null;
3773
3817
  const fromSessionName = extractRootExe(mySession);
3818
+ let candidate = null;
3774
3819
  try {
3775
3820
  const key = getSessionKey();
3776
3821
  const parentExe = getParentExe(key);
@@ -3781,13 +3826,47 @@ function resolveExeSession() {
3781
3826
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
3782
3827
  `
3783
3828
  );
3784
- return fromSessionName;
3829
+ candidate = fromSessionName;
3830
+ } else {
3831
+ candidate = fromCache;
3785
3832
  }
3786
- return fromCache;
3787
3833
  }
3788
3834
  } catch {
3789
3835
  }
3790
- return fromSessionName ?? mySession;
3836
+ if (!candidate) {
3837
+ candidate = fromSessionName ?? mySession;
3838
+ }
3839
+ if (candidate && isRootSession(candidate)) {
3840
+ try {
3841
+ const transport = getTransport();
3842
+ const liveSessions = transport.listSessions();
3843
+ if (!liveSessions.includes(candidate)) {
3844
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
3845
+ if (liveRoots.length === 1) {
3846
+ process.stderr.write(
3847
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
3848
+ `
3849
+ );
3850
+ return liveRoots[0];
3851
+ } else if (liveRoots.length > 1) {
3852
+ const base = candidate.replace(/\d+$/, "");
3853
+ const match = liveRoots.find((s) => s.startsWith(base));
3854
+ const chosen = match ?? liveRoots[0];
3855
+ process.stderr.write(
3856
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
3857
+ `
3858
+ );
3859
+ return chosen;
3860
+ }
3861
+ process.stderr.write(
3862
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
3863
+ `
3864
+ );
3865
+ }
3866
+ } catch {
3867
+ }
3868
+ }
3869
+ return candidate;
3791
3870
  }
3792
3871
  var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
3793
3872
  var init_tmux_routing = __esm({
@@ -5107,6 +5186,20 @@ var init_platform_procedures = __esm({
5107
5186
  priority: "p1",
5108
5187
  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."
5109
5188
  },
5189
+ // --- Tool guidance ---
5190
+ {
5191
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
5192
+ domain: "tools",
5193
+ priority: "p2",
5194
+ 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."
5195
+ },
5196
+ // --- Release awareness ---
5197
+ {
5198
+ title: "What's New check \u2014 surface new features after update",
5199
+ domain: "support",
5200
+ priority: "p1",
5201
+ 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."
5202
+ },
5110
5203
  // --- Platform vs Customer ownership ---
5111
5204
  {
5112
5205
  title: "What the platform provides vs what you customize",
@@ -5195,13 +5288,13 @@ var init_platform_procedures = __esm({
5195
5288
  title: "MCP tools \u2014 memory, decision, and search",
5196
5289
  domain: "tool-use",
5197
5290
  priority: "p1",
5198
- 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.`
5291
+ content: `memory(action="recall") / recall_my_memory: search memories (semantic + FTS). Params: as_of (bi-temporal \u2014 what did I know at time X?), kind (decision|procedure|observation|raw|conversation|behavior), retrieval_mode (all|decisions_only|procedures_only|operational|recent_high_value). memory(action="ask_team") / ask_team_memory: search a colleague's memories. memory(action="store") / store_memory: persist a memory. Params: kind, procedure_for (domain tag for procedures). memory(action="commit") / commit_memory: high-importance, survives consolidation. Requires summary. memory(action="search") / search_everything: unified search across memories, tasks, entities, conversations. memory(action="session_context") / get_session_context: temporal window. Requires session_id + target_timestamp. memory(action="get_by_id"): fetch one memory by UUID with full untruncated text. memory(action="consolidate") / consolidate_memories: merge duplicate/related memories. memory(action="cardinality") / get_memory_cardinality: count memories per agent. memory(action="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.`
5199
5292
  },
5200
5293
  {
5201
5294
  title: "MCP tools \u2014 task orchestration",
5202
5295
  domain: "tool-use",
5203
5296
  priority: "p1",
5204
- 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.'
5297
+ content: 'task(action="create") / create_task: dispatch work (title, assigned_to, context). The ONLY dispatch path. Auto-spawns session. Params: blocked_by (task ID for dependency), parent_task_id (subtask hierarchy), reviewer, complexity (routine|standard|complex|critical), budget_tokens (max token cap), budget_fallback_model, spawn_runtime (override runtime: claude|codex|opencode), spawn_model (override model). 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.'
5205
5298
  },
5206
5299
  {
5207
5300
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -5231,7 +5324,7 @@ var init_platform_procedures = __esm({
5231
5324
  title: "MCP tools \u2014 admin, config, and operations",
5232
5325
  domain: "tool-use",
5233
5326
  priority: "p1",
5234
- 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.'
5327
+ content: 'config(action="list_employees"): view roster. config(action="set_agent_config"): view or change per-agent runtime + model. Call with no args to show all agents. 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. Supports cloud_action param: status|sync|reupload. config(action="memory_audit"): health check (dupes, null vectors). config(action="run_consolidation"): trigger memory consolidation. config(action="worker_gate"): check spawn slot availability \u2014 alive/stale/reserved counts vs max. Use before dispatching. config(action="auto_wake_status"): orphaned tasks, blocked tasks, auto-wake retry status. config(action="orchestration_phase"): view/change org phase (phase_1_coo|phase_2_executives|phase_3_parallel_org). 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="pending_work_summary"): pending reviews + messages + notifications in one call. diagnostics(action="rename_employee"): rename an agent across all systems (roster, identity, DB, symlinks). diagnostics(action="tool_search"): semantic tool discovery \u2014 find relevant MCP tools by natural language query. diagnostics(action="drift"): identity drift detection \u2014 score how far an agent has drifted from its role. mcp_ping(): daemon health + license status + tool usage stats.'
5235
5328
  }
5236
5329
  ];
5237
5330
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -5915,6 +6008,8 @@ async function writeMemory(record) {
5915
6008
  source_type: record.source_type ?? null,
5916
6009
  tier: record.tier ?? classifyTier(record),
5917
6010
  supersedes_id: record.supersedes_id ?? null,
6011
+ valid_from: record.valid_from ?? record.timestamp,
6012
+ invalid_at: record.invalid_at ?? null,
5918
6013
  draft: record.draft ? 1 : 0,
5919
6014
  memory_type: memoryType,
5920
6015
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -5933,7 +6028,8 @@ async function writeMemory(record) {
5933
6028
  token_cost: record.token_cost ?? null,
5934
6029
  audience: record.audience ?? null,
5935
6030
  language_type: record.language_type ?? inferLanguageType(record),
5936
- parent_memory_id: record.parent_memory_id ?? null
6031
+ parent_memory_id: record.parent_memory_id ?? null,
6032
+ procedure_for: record.procedure_for ?? null
5937
6033
  };
5938
6034
  _pendingRecords.push(dbRow);
5939
6035
  orgBus.emit({
@@ -5988,6 +6084,8 @@ async function flushBatch() {
5988
6084
  const sourceType = row.source_type ?? null;
5989
6085
  const tier = row.tier ?? 3;
5990
6086
  const supersedesId = row.supersedes_id ?? null;
6087
+ const validFrom = row.valid_from ?? row.timestamp;
6088
+ const invalidAt = row.invalid_at ?? null;
5991
6089
  const draft = row.draft ? 1 : 0;
5992
6090
  const memoryType = row.memory_type ?? "raw";
5993
6091
  const trajectory = row.trajectory ?? null;
@@ -6007,15 +6105,16 @@ async function flushBatch() {
6007
6105
  const audience = row.audience ?? null;
6008
6106
  const languageType = row.language_type ?? null;
6009
6107
  const parentMemoryId = row.parent_memory_id ?? null;
6108
+ const procedureFor = row.procedure_for ?? null;
6010
6109
  const cols = `id, agent_id, agent_role, session_id, timestamp,
6011
6110
  tool_name, project_name,
6012
6111
  has_error, raw_text, vector, version, task_id, importance, status,
6013
6112
  confidence, last_accessed,
6014
6113
  workspace_id, document_id, user_id, char_offset, page_number,
6015
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
6114
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
6016
6115
  intent, outcome, domain, referenced_entities, retrieval_count,
6017
6116
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
6018
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
6117
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
6019
6118
  const metaArgs = [
6020
6119
  intent,
6021
6120
  outcome,
@@ -6031,7 +6130,8 @@ async function flushBatch() {
6031
6130
  tokenCost,
6032
6131
  audience,
6033
6132
  languageType,
6034
- parentMemoryId
6133
+ parentMemoryId,
6134
+ procedureFor
6035
6135
  ];
6036
6136
  const baseArgs = [
6037
6137
  row.id,
@@ -6060,6 +6160,8 @@ async function flushBatch() {
6060
6160
  sourceType,
6061
6161
  tier,
6062
6162
  supersedesId,
6163
+ validFrom,
6164
+ invalidAt,
6063
6165
  draft,
6064
6166
  memoryType,
6065
6167
  trajectory,
@@ -6067,8 +6169,8 @@ async function flushBatch() {
6067
6169
  ];
6068
6170
  return {
6069
6171
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
6070
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
6071
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6172
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
6173
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6072
6174
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
6073
6175
  };
6074
6176
  };
@@ -6184,6 +6286,12 @@ async function searchMemories(queryVector, agentId, options) {
6184
6286
  AND vector IS NOT NULL${statusFilter}${draftFilter}
6185
6287
  AND COALESCE(confidence, 0.7) >= 0.3`;
6186
6288
  const args = [agentId];
6289
+ if (options?.asOf) {
6290
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
6291
+ args.push(options.asOf, options.asOf);
6292
+ } else {
6293
+ sql += ` AND invalid_at IS NULL`;
6294
+ }
6187
6295
  const scope = buildWikiScopeFilter(options, "");
6188
6296
  sql += scope.clause;
6189
6297
  args.push(...scope.args);