@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
@@ -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
  }
@@ -6250,6 +6296,7 @@ function resolveExeSession() {
6250
6296
  const mySession = getMySession();
6251
6297
  if (!mySession) return null;
6252
6298
  const fromSessionName = extractRootExe(mySession);
6299
+ let candidate = null;
6253
6300
  try {
6254
6301
  const key = getSessionKey();
6255
6302
  const parentExe = getParentExe(key);
@@ -6260,13 +6307,47 @@ function resolveExeSession() {
6260
6307
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6261
6308
  `
6262
6309
  );
6263
- return fromSessionName;
6310
+ candidate = fromSessionName;
6311
+ } else {
6312
+ candidate = fromCache;
6264
6313
  }
6265
- return fromCache;
6266
6314
  }
6267
6315
  } catch {
6268
6316
  }
6269
- return fromSessionName ?? mySession;
6317
+ if (!candidate) {
6318
+ candidate = fromSessionName ?? mySession;
6319
+ }
6320
+ if (candidate && isRootSession(candidate)) {
6321
+ try {
6322
+ const transport = getTransport();
6323
+ const liveSessions = transport.listSessions();
6324
+ if (!liveSessions.includes(candidate)) {
6325
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
6326
+ if (liveRoots.length === 1) {
6327
+ process.stderr.write(
6328
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
6329
+ `
6330
+ );
6331
+ return liveRoots[0];
6332
+ } else if (liveRoots.length > 1) {
6333
+ const base = candidate.replace(/\d+$/, "");
6334
+ const match = liveRoots.find((s) => s.startsWith(base));
6335
+ const chosen = match ?? liveRoots[0];
6336
+ process.stderr.write(
6337
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
6338
+ `
6339
+ );
6340
+ return chosen;
6341
+ }
6342
+ process.stderr.write(
6343
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
6344
+ `
6345
+ );
6346
+ }
6347
+ } catch {
6348
+ }
6349
+ }
6350
+ return candidate;
6270
6351
  }
6271
6352
  function isEmployeeAlive(sessionName) {
6272
6353
  return getTransport().isAlive(sessionName);
@@ -6668,7 +6749,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6668
6749
  }
6669
6750
  const spawnCwd = opts?.cwd ?? projectDir;
6670
6751
  const useExeAgent = !!(opts?.model && opts?.provider);
6671
- const agentRtConfig = getAgentRuntime(employeeName);
6752
+ const baseRtConfig = getAgentRuntime(employeeName);
6753
+ const agentRtConfig = {
6754
+ ...baseRtConfig,
6755
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
6756
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
6757
+ };
6672
6758
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
6673
6759
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
6674
6760
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -7961,6 +8047,20 @@ var init_platform_procedures = __esm({
7961
8047
  priority: "p1",
7962
8048
  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
8049
  },
8050
+ // --- Tool guidance ---
8051
+ {
8052
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
8053
+ domain: "tools",
8054
+ priority: "p2",
8055
+ 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."
8056
+ },
8057
+ // --- Release awareness ---
8058
+ {
8059
+ title: "What's New check \u2014 surface new features after update",
8060
+ domain: "support",
8061
+ priority: "p1",
8062
+ 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."
8063
+ },
7964
8064
  // --- Platform vs Customer ownership ---
7965
8065
  {
7966
8066
  title: "What the platform provides vs what you customize",
@@ -8049,13 +8149,13 @@ var init_platform_procedures = __esm({
8049
8149
  title: "MCP tools \u2014 memory, decision, and search",
8050
8150
  domain: "tool-use",
8051
8151
  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.`
8152
+ 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.`
8053
8153
  },
8054
8154
  {
8055
8155
  title: "MCP tools \u2014 task orchestration",
8056
8156
  domain: "tool-use",
8057
8157
  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.'
8158
+ 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.`
8059
8159
  },
8060
8160
  {
8061
8161
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -8085,7 +8185,7 @@ var init_platform_procedures = __esm({
8085
8185
  title: "MCP tools \u2014 admin, config, and operations",
8086
8186
  domain: "tool-use",
8087
8187
  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.'
8188
+ 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.'
8089
8189
  }
8090
8190
  ];
8091
8191
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -8769,6 +8869,8 @@ async function writeMemory(record) {
8769
8869
  source_type: record.source_type ?? null,
8770
8870
  tier: record.tier ?? classifyTier(record),
8771
8871
  supersedes_id: record.supersedes_id ?? null,
8872
+ valid_from: record.valid_from ?? record.timestamp,
8873
+ invalid_at: record.invalid_at ?? null,
8772
8874
  draft: record.draft ? 1 : 0,
8773
8875
  memory_type: memoryType,
8774
8876
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -8787,7 +8889,8 @@ async function writeMemory(record) {
8787
8889
  token_cost: record.token_cost ?? null,
8788
8890
  audience: record.audience ?? null,
8789
8891
  language_type: record.language_type ?? inferLanguageType(record),
8790
- parent_memory_id: record.parent_memory_id ?? null
8892
+ parent_memory_id: record.parent_memory_id ?? null,
8893
+ procedure_for: record.procedure_for ?? null
8791
8894
  };
8792
8895
  _pendingRecords.push(dbRow);
8793
8896
  orgBus.emit({
@@ -8842,6 +8945,8 @@ async function flushBatch() {
8842
8945
  const sourceType = row.source_type ?? null;
8843
8946
  const tier = row.tier ?? 3;
8844
8947
  const supersedesId = row.supersedes_id ?? null;
8948
+ const validFrom = row.valid_from ?? row.timestamp;
8949
+ const invalidAt = row.invalid_at ?? null;
8845
8950
  const draft = row.draft ? 1 : 0;
8846
8951
  const memoryType = row.memory_type ?? "raw";
8847
8952
  const trajectory = row.trajectory ?? null;
@@ -8861,15 +8966,16 @@ async function flushBatch() {
8861
8966
  const audience = row.audience ?? null;
8862
8967
  const languageType = row.language_type ?? null;
8863
8968
  const parentMemoryId = row.parent_memory_id ?? null;
8969
+ const procedureFor = row.procedure_for ?? null;
8864
8970
  const cols = `id, agent_id, agent_role, session_id, timestamp,
8865
8971
  tool_name, project_name,
8866
8972
  has_error, raw_text, vector, version, task_id, importance, status,
8867
8973
  confidence, last_accessed,
8868
8974
  workspace_id, document_id, user_id, char_offset, page_number,
8869
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
8975
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
8870
8976
  intent, outcome, domain, referenced_entities, retrieval_count,
8871
8977
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
8872
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
8978
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
8873
8979
  const metaArgs = [
8874
8980
  intent,
8875
8981
  outcome,
@@ -8885,7 +8991,8 @@ async function flushBatch() {
8885
8991
  tokenCost,
8886
8992
  audience,
8887
8993
  languageType,
8888
- parentMemoryId
8994
+ parentMemoryId,
8995
+ procedureFor
8889
8996
  ];
8890
8997
  const baseArgs = [
8891
8998
  row.id,
@@ -8914,6 +9021,8 @@ async function flushBatch() {
8914
9021
  sourceType,
8915
9022
  tier,
8916
9023
  supersedesId,
9024
+ validFrom,
9025
+ invalidAt,
8917
9026
  draft,
8918
9027
  memoryType,
8919
9028
  trajectory,
@@ -8921,8 +9030,8 @@ async function flushBatch() {
8921
9030
  ];
8922
9031
  return {
8923
9032
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
8924
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
8925
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
9033
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
9034
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
8926
9035
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
8927
9036
  };
8928
9037
  };
@@ -9038,6 +9147,12 @@ async function searchMemories(queryVector, agentId, options) {
9038
9147
  AND vector IS NOT NULL${statusFilter}${draftFilter}
9039
9148
  AND COALESCE(confidence, 0.7) >= 0.3`;
9040
9149
  const args = [agentId];
9150
+ if (options?.asOf) {
9151
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
9152
+ args.push(options.asOf, options.asOf);
9153
+ } else {
9154
+ sql += ` AND invalid_at IS NULL`;
9155
+ }
9041
9156
  const scope = buildWikiScopeFilter(options, "");
9042
9157
  sql += scope.clause;
9043
9158
  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;
@@ -3771,6 +3812,7 @@ function resolveExeSession() {
3771
3812
  const mySession = getMySession();
3772
3813
  if (!mySession) return null;
3773
3814
  const fromSessionName = extractRootExe(mySession);
3815
+ let candidate = null;
3774
3816
  try {
3775
3817
  const key = getSessionKey();
3776
3818
  const parentExe = getParentExe(key);
@@ -3781,13 +3823,47 @@ function resolveExeSession() {
3781
3823
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
3782
3824
  `
3783
3825
  );
3784
- return fromSessionName;
3826
+ candidate = fromSessionName;
3827
+ } else {
3828
+ candidate = fromCache;
3785
3829
  }
3786
- return fromCache;
3787
3830
  }
3788
3831
  } catch {
3789
3832
  }
3790
- return fromSessionName ?? mySession;
3833
+ if (!candidate) {
3834
+ candidate = fromSessionName ?? mySession;
3835
+ }
3836
+ if (candidate && isRootSession(candidate)) {
3837
+ try {
3838
+ const transport = getTransport();
3839
+ const liveSessions = transport.listSessions();
3840
+ if (!liveSessions.includes(candidate)) {
3841
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
3842
+ if (liveRoots.length === 1) {
3843
+ process.stderr.write(
3844
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
3845
+ `
3846
+ );
3847
+ return liveRoots[0];
3848
+ } else if (liveRoots.length > 1) {
3849
+ const base = candidate.replace(/\d+$/, "");
3850
+ const match = liveRoots.find((s) => s.startsWith(base));
3851
+ const chosen = match ?? liveRoots[0];
3852
+ process.stderr.write(
3853
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
3854
+ `
3855
+ );
3856
+ return chosen;
3857
+ }
3858
+ process.stderr.write(
3859
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
3860
+ `
3861
+ );
3862
+ }
3863
+ } catch {
3864
+ }
3865
+ }
3866
+ return candidate;
3791
3867
  }
3792
3868
  var SPAWN_LOCK_DIR, SESSION_CACHE, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS;
3793
3869
  var init_tmux_routing = __esm({
@@ -5107,6 +5183,20 @@ var init_platform_procedures = __esm({
5107
5183
  priority: "p1",
5108
5184
  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
5185
  },
5186
+ // --- Tool guidance ---
5187
+ {
5188
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
5189
+ domain: "tools",
5190
+ priority: "p2",
5191
+ 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."
5192
+ },
5193
+ // --- Release awareness ---
5194
+ {
5195
+ title: "What's New check \u2014 surface new features after update",
5196
+ domain: "support",
5197
+ priority: "p1",
5198
+ 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."
5199
+ },
5110
5200
  // --- Platform vs Customer ownership ---
5111
5201
  {
5112
5202
  title: "What the platform provides vs what you customize",
@@ -5195,13 +5285,13 @@ var init_platform_procedures = __esm({
5195
5285
  title: "MCP tools \u2014 memory, decision, and search",
5196
5286
  domain: "tool-use",
5197
5287
  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.`
5288
+ 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.`
5199
5289
  },
5200
5290
  {
5201
5291
  title: "MCP tools \u2014 task orchestration",
5202
5292
  domain: "tool-use",
5203
5293
  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.'
5294
+ 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.`
5205
5295
  },
5206
5296
  {
5207
5297
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -5231,7 +5321,7 @@ var init_platform_procedures = __esm({
5231
5321
  title: "MCP tools \u2014 admin, config, and operations",
5232
5322
  domain: "tool-use",
5233
5323
  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.'
5324
+ 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.'
5235
5325
  }
5236
5326
  ];
5237
5327
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -5915,6 +6005,8 @@ async function writeMemory(record) {
5915
6005
  source_type: record.source_type ?? null,
5916
6006
  tier: record.tier ?? classifyTier(record),
5917
6007
  supersedes_id: record.supersedes_id ?? null,
6008
+ valid_from: record.valid_from ?? record.timestamp,
6009
+ invalid_at: record.invalid_at ?? null,
5918
6010
  draft: record.draft ? 1 : 0,
5919
6011
  memory_type: memoryType,
5920
6012
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -5933,7 +6025,8 @@ async function writeMemory(record) {
5933
6025
  token_cost: record.token_cost ?? null,
5934
6026
  audience: record.audience ?? null,
5935
6027
  language_type: record.language_type ?? inferLanguageType(record),
5936
- parent_memory_id: record.parent_memory_id ?? null
6028
+ parent_memory_id: record.parent_memory_id ?? null,
6029
+ procedure_for: record.procedure_for ?? null
5937
6030
  };
5938
6031
  _pendingRecords.push(dbRow);
5939
6032
  orgBus.emit({
@@ -5988,6 +6081,8 @@ async function flushBatch() {
5988
6081
  const sourceType = row.source_type ?? null;
5989
6082
  const tier = row.tier ?? 3;
5990
6083
  const supersedesId = row.supersedes_id ?? null;
6084
+ const validFrom = row.valid_from ?? row.timestamp;
6085
+ const invalidAt = row.invalid_at ?? null;
5991
6086
  const draft = row.draft ? 1 : 0;
5992
6087
  const memoryType = row.memory_type ?? "raw";
5993
6088
  const trajectory = row.trajectory ?? null;
@@ -6007,15 +6102,16 @@ async function flushBatch() {
6007
6102
  const audience = row.audience ?? null;
6008
6103
  const languageType = row.language_type ?? null;
6009
6104
  const parentMemoryId = row.parent_memory_id ?? null;
6105
+ const procedureFor = row.procedure_for ?? null;
6010
6106
  const cols = `id, agent_id, agent_role, session_id, timestamp,
6011
6107
  tool_name, project_name,
6012
6108
  has_error, raw_text, vector, version, task_id, importance, status,
6013
6109
  confidence, last_accessed,
6014
6110
  workspace_id, document_id, user_id, char_offset, page_number,
6015
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
6111
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
6016
6112
  intent, outcome, domain, referenced_entities, retrieval_count,
6017
6113
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
6018
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
6114
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
6019
6115
  const metaArgs = [
6020
6116
  intent,
6021
6117
  outcome,
@@ -6031,7 +6127,8 @@ async function flushBatch() {
6031
6127
  tokenCost,
6032
6128
  audience,
6033
6129
  languageType,
6034
- parentMemoryId
6130
+ parentMemoryId,
6131
+ procedureFor
6035
6132
  ];
6036
6133
  const baseArgs = [
6037
6134
  row.id,
@@ -6060,6 +6157,8 @@ async function flushBatch() {
6060
6157
  sourceType,
6061
6158
  tier,
6062
6159
  supersedesId,
6160
+ validFrom,
6161
+ invalidAt,
6063
6162
  draft,
6064
6163
  memoryType,
6065
6164
  trajectory,
@@ -6067,8 +6166,8 @@ async function flushBatch() {
6067
6166
  ];
6068
6167
  return {
6069
6168
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
6070
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
6071
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6169
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
6170
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
6072
6171
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
6073
6172
  };
6074
6173
  };
@@ -6184,6 +6283,12 @@ async function searchMemories(queryVector, agentId, options) {
6184
6283
  AND vector IS NOT NULL${statusFilter}${draftFilter}
6185
6284
  AND COALESCE(confidence, 0.7) >= 0.3`;
6186
6285
  const args = [agentId];
6286
+ if (options?.asOf) {
6287
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
6288
+ args.push(options.asOf, options.asOf);
6289
+ } else {
6290
+ sql += ` AND invalid_at IS NULL`;
6291
+ }
6187
6292
  const scope = buildWikiScopeFilter(options, "");
6188
6293
  sql += scope.clause;
6189
6294
  args.push(...scope.args);