@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
@@ -3334,6 +3334,20 @@ async function ensureSchema() {
3334
3334
  });
3335
3335
  } catch {
3336
3336
  }
3337
+ try {
3338
+ await client.execute({
3339
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3340
+ args: []
3341
+ });
3342
+ } catch {
3343
+ }
3344
+ try {
3345
+ await client.execute({
3346
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3347
+ args: []
3348
+ });
3349
+ } catch {
3350
+ }
3337
3351
  await client.executeMultiple(`
3338
3352
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3339
3353
  content_text,
@@ -3585,6 +3599,22 @@ async function ensureSchema() {
3585
3599
  );
3586
3600
  } catch {
3587
3601
  }
3602
+ for (const col of [
3603
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3604
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3605
+ ]) {
3606
+ try {
3607
+ await client.execute(col);
3608
+ } catch {
3609
+ }
3610
+ }
3611
+ try {
3612
+ await client.execute({
3613
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3614
+ args: []
3615
+ });
3616
+ } catch {
3617
+ }
3588
3618
  try {
3589
3619
  await client.execute({
3590
3620
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3627,6 +3657,13 @@ async function ensureSchema() {
3627
3657
  } catch {
3628
3658
  }
3629
3659
  }
3660
+ try {
3661
+ await client.execute({
3662
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3663
+ args: []
3664
+ });
3665
+ } catch {
3666
+ }
3630
3667
  try {
3631
3668
  await client.execute({
3632
3669
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -3687,7 +3724,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3687
3724
  import os8 from "os";
3688
3725
  import path10 from "path";
3689
3726
  import { jwtVerify, importSPKI } from "jose";
3690
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
3727
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
3691
3728
  var init_license = __esm({
3692
3729
  "src/lib/license.ts"() {
3693
3730
  "use strict";
@@ -3695,6 +3732,7 @@ var init_license = __esm({
3695
3732
  LICENSE_PATH = path10.join(EXE_AI_DIR, "license.key");
3696
3733
  CACHE_PATH = path10.join(EXE_AI_DIR, "license-cache.json");
3697
3734
  DEVICE_ID_PATH = path10.join(EXE_AI_DIR, "device-id");
3735
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3698
3736
  PLAN_LIMITS = {
3699
3737
  free: { devices: 1, employees: 1, memories: 5e3 },
3700
3738
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -4351,8 +4389,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
4351
4389
  const complexity = input.complexity ?? "standard";
4352
4390
  const sessionScope = earlySessionScope;
4353
4391
  await client.execute({
4354
- 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)
4355
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4392
+ 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)
4393
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4356
4394
  args: [
4357
4395
  id,
4358
4396
  input.title,
@@ -4372,6 +4410,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
4372
4410
  0,
4373
4411
  null,
4374
4412
  sessionScope,
4413
+ input.spawnRuntime ?? null,
4414
+ input.spawnModel ?? null,
4375
4415
  now,
4376
4416
  now
4377
4417
  ]
@@ -4428,7 +4468,9 @@ ${input.context}
4428
4468
  budgetTokens: input.budgetTokens ?? null,
4429
4469
  budgetFallbackModel: input.budgetFallbackModel ?? null,
4430
4470
  tokensUsed: 0,
4431
- tokensWarnedAt: null
4471
+ tokensWarnedAt: null,
4472
+ spawnRuntime: input.spawnRuntime ?? null,
4473
+ spawnModel: input.spawnModel ?? null
4432
4474
  };
4433
4475
  }
4434
4476
  async function listTasks(input) {
@@ -4478,7 +4520,9 @@ async function listTasks(input) {
4478
4520
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
4479
4521
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
4480
4522
  tokensUsed: Number(r.tokens_used ?? 0),
4481
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
4523
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
4524
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
4525
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
4482
4526
  }));
4483
4527
  }
4484
4528
  function isTmuxSessionAlive(identifier) {
@@ -5846,6 +5890,8 @@ async function updateTask(input) {
5846
5890
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
5847
5891
  tokensUsed: Number(row.tokens_used ?? 0),
5848
5892
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
5893
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
5894
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
5849
5895
  nextTask
5850
5896
  };
5851
5897
  }
@@ -6341,6 +6387,7 @@ function resolveExeSession() {
6341
6387
  const mySession = getMySession();
6342
6388
  if (!mySession) return null;
6343
6389
  const fromSessionName = extractRootExe(mySession);
6390
+ let candidate = null;
6344
6391
  try {
6345
6392
  const key = getSessionKey();
6346
6393
  const parentExe = getParentExe(key);
@@ -6351,13 +6398,47 @@ function resolveExeSession() {
6351
6398
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6352
6399
  `
6353
6400
  );
6354
- return fromSessionName;
6401
+ candidate = fromSessionName;
6402
+ } else {
6403
+ candidate = fromCache;
6355
6404
  }
6356
- return fromCache;
6357
6405
  }
6358
6406
  } catch {
6359
6407
  }
6360
- return fromSessionName ?? mySession;
6408
+ if (!candidate) {
6409
+ candidate = fromSessionName ?? mySession;
6410
+ }
6411
+ if (candidate && isRootSession(candidate)) {
6412
+ try {
6413
+ const transport = getTransport();
6414
+ const liveSessions = transport.listSessions();
6415
+ if (!liveSessions.includes(candidate)) {
6416
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
6417
+ if (liveRoots.length === 1) {
6418
+ process.stderr.write(
6419
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
6420
+ `
6421
+ );
6422
+ return liveRoots[0];
6423
+ } else if (liveRoots.length > 1) {
6424
+ const base = candidate.replace(/\d+$/, "");
6425
+ const match = liveRoots.find((s) => s.startsWith(base));
6426
+ const chosen = match ?? liveRoots[0];
6427
+ process.stderr.write(
6428
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
6429
+ `
6430
+ );
6431
+ return chosen;
6432
+ }
6433
+ process.stderr.write(
6434
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
6435
+ `
6436
+ );
6437
+ }
6438
+ } catch {
6439
+ }
6440
+ }
6441
+ return candidate;
6361
6442
  }
6362
6443
  function isEmployeeAlive(sessionName) {
6363
6444
  return getTransport().isAlive(sessionName);
@@ -6759,7 +6840,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6759
6840
  }
6760
6841
  const spawnCwd = opts?.cwd ?? projectDir;
6761
6842
  const useExeAgent = !!(opts?.model && opts?.provider);
6762
- const agentRtConfig = getAgentRuntime(employeeName);
6843
+ const baseRtConfig = getAgentRuntime(employeeName);
6844
+ const agentRtConfig = {
6845
+ ...baseRtConfig,
6846
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
6847
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
6848
+ };
6763
6849
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
6764
6850
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
6765
6851
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -8019,6 +8105,20 @@ var init_platform_procedures = __esm({
8019
8105
  priority: "p1",
8020
8106
  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."
8021
8107
  },
8108
+ // --- Tool guidance ---
8109
+ {
8110
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
8111
+ domain: "tools",
8112
+ priority: "p2",
8113
+ 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."
8114
+ },
8115
+ // --- Release awareness ---
8116
+ {
8117
+ title: "What's New check \u2014 surface new features after update",
8118
+ domain: "support",
8119
+ priority: "p1",
8120
+ 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."
8121
+ },
8022
8122
  // --- Platform vs Customer ownership ---
8023
8123
  {
8024
8124
  title: "What the platform provides vs what you customize",
@@ -8107,13 +8207,13 @@ var init_platform_procedures = __esm({
8107
8207
  title: "MCP tools \u2014 memory, decision, and search",
8108
8208
  domain: "tool-use",
8109
8209
  priority: "p1",
8110
- 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.`
8210
+ 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.`
8111
8211
  },
8112
8212
  {
8113
8213
  title: "MCP tools \u2014 task orchestration",
8114
8214
  domain: "tool-use",
8115
8215
  priority: "p1",
8116
- 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.'
8216
+ 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.`
8117
8217
  },
8118
8218
  {
8119
8219
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -8143,7 +8243,7 @@ var init_platform_procedures = __esm({
8143
8243
  title: "MCP tools \u2014 admin, config, and operations",
8144
8244
  domain: "tool-use",
8145
8245
  priority: "p1",
8146
- 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.'
8246
+ 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.'
8147
8247
  }
8148
8248
  ];
8149
8249
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -8827,6 +8927,8 @@ async function writeMemory(record) {
8827
8927
  source_type: record.source_type ?? null,
8828
8928
  tier: record.tier ?? classifyTier(record),
8829
8929
  supersedes_id: record.supersedes_id ?? null,
8930
+ valid_from: record.valid_from ?? record.timestamp,
8931
+ invalid_at: record.invalid_at ?? null,
8830
8932
  draft: record.draft ? 1 : 0,
8831
8933
  memory_type: memoryType,
8832
8934
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -8845,7 +8947,8 @@ async function writeMemory(record) {
8845
8947
  token_cost: record.token_cost ?? null,
8846
8948
  audience: record.audience ?? null,
8847
8949
  language_type: record.language_type ?? inferLanguageType(record),
8848
- parent_memory_id: record.parent_memory_id ?? null
8950
+ parent_memory_id: record.parent_memory_id ?? null,
8951
+ procedure_for: record.procedure_for ?? null
8849
8952
  };
8850
8953
  _pendingRecords.push(dbRow);
8851
8954
  orgBus.emit({
@@ -8900,6 +9003,8 @@ async function flushBatch() {
8900
9003
  const sourceType = row.source_type ?? null;
8901
9004
  const tier = row.tier ?? 3;
8902
9005
  const supersedesId = row.supersedes_id ?? null;
9006
+ const validFrom = row.valid_from ?? row.timestamp;
9007
+ const invalidAt = row.invalid_at ?? null;
8903
9008
  const draft = row.draft ? 1 : 0;
8904
9009
  const memoryType = row.memory_type ?? "raw";
8905
9010
  const trajectory = row.trajectory ?? null;
@@ -8919,15 +9024,16 @@ async function flushBatch() {
8919
9024
  const audience = row.audience ?? null;
8920
9025
  const languageType = row.language_type ?? null;
8921
9026
  const parentMemoryId = row.parent_memory_id ?? null;
9027
+ const procedureFor = row.procedure_for ?? null;
8922
9028
  const cols = `id, agent_id, agent_role, session_id, timestamp,
8923
9029
  tool_name, project_name,
8924
9030
  has_error, raw_text, vector, version, task_id, importance, status,
8925
9031
  confidence, last_accessed,
8926
9032
  workspace_id, document_id, user_id, char_offset, page_number,
8927
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
9033
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
8928
9034
  intent, outcome, domain, referenced_entities, retrieval_count,
8929
9035
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
8930
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
9036
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
8931
9037
  const metaArgs = [
8932
9038
  intent,
8933
9039
  outcome,
@@ -8943,7 +9049,8 @@ async function flushBatch() {
8943
9049
  tokenCost,
8944
9050
  audience,
8945
9051
  languageType,
8946
- parentMemoryId
9052
+ parentMemoryId,
9053
+ procedureFor
8947
9054
  ];
8948
9055
  const baseArgs = [
8949
9056
  row.id,
@@ -8972,6 +9079,8 @@ async function flushBatch() {
8972
9079
  sourceType,
8973
9080
  tier,
8974
9081
  supersedesId,
9082
+ validFrom,
9083
+ invalidAt,
8975
9084
  draft,
8976
9085
  memoryType,
8977
9086
  trajectory,
@@ -8979,8 +9088,8 @@ async function flushBatch() {
8979
9088
  ];
8980
9089
  return {
8981
9090
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
8982
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
8983
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
9091
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
9092
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
8984
9093
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
8985
9094
  };
8986
9095
  };
@@ -9096,6 +9205,12 @@ async function searchMemories(queryVector, agentId, options) {
9096
9205
  AND vector IS NOT NULL${statusFilter}${draftFilter}
9097
9206
  AND COALESCE(confidence, 0.7) >= 0.3`;
9098
9207
  const args = [agentId];
9208
+ if (options?.asOf) {
9209
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
9210
+ args.push(options.asOf, options.asOf);
9211
+ } else {
9212
+ sql += ` AND invalid_at IS NULL`;
9213
+ }
9099
9214
  const scope = buildWikiScopeFilter(options, "");
9100
9215
  sql += scope.clause;
9101
9216
  args.push(...scope.args);
package/dist/tui/App.js CHANGED
@@ -3586,6 +3586,20 @@ async function ensureSchema() {
3586
3586
  });
3587
3587
  } catch {
3588
3588
  }
3589
+ try {
3590
+ await client.execute({
3591
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3592
+ args: []
3593
+ });
3594
+ } catch {
3595
+ }
3596
+ try {
3597
+ await client.execute({
3598
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3599
+ args: []
3600
+ });
3601
+ } catch {
3602
+ }
3589
3603
  await client.executeMultiple(`
3590
3604
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3591
3605
  content_text,
@@ -3837,6 +3851,22 @@ async function ensureSchema() {
3837
3851
  );
3838
3852
  } catch {
3839
3853
  }
3854
+ for (const col of [
3855
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3856
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3857
+ ]) {
3858
+ try {
3859
+ await client.execute(col);
3860
+ } catch {
3861
+ }
3862
+ }
3863
+ try {
3864
+ await client.execute({
3865
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3866
+ args: []
3867
+ });
3868
+ } catch {
3869
+ }
3840
3870
  try {
3841
3871
  await client.execute({
3842
3872
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3879,6 +3909,13 @@ async function ensureSchema() {
3879
3909
  } catch {
3880
3910
  }
3881
3911
  }
3912
+ try {
3913
+ await client.execute({
3914
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3915
+ args: []
3916
+ });
3917
+ } catch {
3918
+ }
3882
3919
  try {
3883
3920
  await client.execute({
3884
3921
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -4368,7 +4405,7 @@ var init_license = __esm({
4368
4405
  LICENSE_PATH = path9.join(EXE_AI_DIR, "license.key");
4369
4406
  CACHE_PATH = path9.join(EXE_AI_DIR, "license-cache.json");
4370
4407
  DEVICE_ID_PATH = path9.join(EXE_AI_DIR, "device-id");
4371
- API_BASE = "https://askexe.com/cloud";
4408
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
4372
4409
  RETRY_DELAY_MS = 500;
4373
4410
  LICENSE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
4374
4411
  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEeHztAMOpR/ZMh+rWuOASjEZ54CGY
@@ -5021,8 +5058,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
5021
5058
  const complexity = input.complexity ?? "standard";
5022
5059
  const sessionScope = earlySessionScope;
5023
5060
  await client.execute({
5024
- 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)
5025
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5061
+ 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)
5062
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
5026
5063
  args: [
5027
5064
  id,
5028
5065
  input.title,
@@ -5042,6 +5079,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
5042
5079
  0,
5043
5080
  null,
5044
5081
  sessionScope,
5082
+ input.spawnRuntime ?? null,
5083
+ input.spawnModel ?? null,
5045
5084
  now,
5046
5085
  now
5047
5086
  ]
@@ -5098,7 +5137,9 @@ ${input.context}
5098
5137
  budgetTokens: input.budgetTokens ?? null,
5099
5138
  budgetFallbackModel: input.budgetFallbackModel ?? null,
5100
5139
  tokensUsed: 0,
5101
- tokensWarnedAt: null
5140
+ tokensWarnedAt: null,
5141
+ spawnRuntime: input.spawnRuntime ?? null,
5142
+ spawnModel: input.spawnModel ?? null
5102
5143
  };
5103
5144
  }
5104
5145
  async function listTasks(input) {
@@ -5148,7 +5189,9 @@ async function listTasks(input) {
5148
5189
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
5149
5190
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
5150
5191
  tokensUsed: Number(r.tokens_used ?? 0),
5151
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
5192
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
5193
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
5194
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
5152
5195
  }));
5153
5196
  }
5154
5197
  function isTmuxSessionAlive(identifier) {
@@ -6445,6 +6488,8 @@ async function updateTask(input) {
6445
6488
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
6446
6489
  tokensUsed: Number(row.tokens_used ?? 0),
6447
6490
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
6491
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
6492
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
6448
6493
  nextTask
6449
6494
  };
6450
6495
  }
@@ -6940,6 +6985,7 @@ function resolveExeSession() {
6940
6985
  const mySession = getMySession();
6941
6986
  if (!mySession) return null;
6942
6987
  const fromSessionName = extractRootExe(mySession);
6988
+ let candidate = null;
6943
6989
  try {
6944
6990
  const key = getSessionKey();
6945
6991
  const parentExe = getParentExe(key);
@@ -6950,13 +6996,47 @@ function resolveExeSession() {
6950
6996
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6951
6997
  `
6952
6998
  );
6953
- return fromSessionName;
6999
+ candidate = fromSessionName;
7000
+ } else {
7001
+ candidate = fromCache;
6954
7002
  }
6955
- return fromCache;
6956
7003
  }
6957
7004
  } catch {
6958
7005
  }
6959
- return fromSessionName ?? mySession;
7006
+ if (!candidate) {
7007
+ candidate = fromSessionName ?? mySession;
7008
+ }
7009
+ if (candidate && isRootSession(candidate)) {
7010
+ try {
7011
+ const transport = getTransport();
7012
+ const liveSessions = transport.listSessions();
7013
+ if (!liveSessions.includes(candidate)) {
7014
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
7015
+ if (liveRoots.length === 1) {
7016
+ process.stderr.write(
7017
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
7018
+ `
7019
+ );
7020
+ return liveRoots[0];
7021
+ } else if (liveRoots.length > 1) {
7022
+ const base = candidate.replace(/\d+$/, "");
7023
+ const match = liveRoots.find((s) => s.startsWith(base));
7024
+ const chosen = match ?? liveRoots[0];
7025
+ process.stderr.write(
7026
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
7027
+ `
7028
+ );
7029
+ return chosen;
7030
+ }
7031
+ process.stderr.write(
7032
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
7033
+ `
7034
+ );
7035
+ }
7036
+ } catch {
7037
+ }
7038
+ }
7039
+ return candidate;
6960
7040
  }
6961
7041
  function isEmployeeAlive(sessionName) {
6962
7042
  return getTransport().isAlive(sessionName);
@@ -7358,7 +7438,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
7358
7438
  }
7359
7439
  const spawnCwd = opts?.cwd ?? projectDir;
7360
7440
  const useExeAgent = !!(opts?.model && opts?.provider);
7361
- const agentRtConfig = getAgentRuntime(employeeName);
7441
+ const baseRtConfig = getAgentRuntime(employeeName);
7442
+ const agentRtConfig = {
7443
+ ...baseRtConfig,
7444
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
7445
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
7446
+ };
7362
7447
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
7363
7448
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
7364
7449
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -8991,6 +9076,20 @@ var init_platform_procedures = __esm({
8991
9076
  priority: "p1",
8992
9077
  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."
8993
9078
  },
9079
+ // --- Tool guidance ---
9080
+ {
9081
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
9082
+ domain: "tools",
9083
+ priority: "p2",
9084
+ 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."
9085
+ },
9086
+ // --- Release awareness ---
9087
+ {
9088
+ title: "What's New check \u2014 surface new features after update",
9089
+ domain: "support",
9090
+ priority: "p1",
9091
+ 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."
9092
+ },
8994
9093
  // --- Platform vs Customer ownership ---
8995
9094
  {
8996
9095
  title: "What the platform provides vs what you customize",
@@ -9079,13 +9178,13 @@ var init_platform_procedures = __esm({
9079
9178
  title: "MCP tools \u2014 memory, decision, and search",
9080
9179
  domain: "tool-use",
9081
9180
  priority: "p1",
9082
- 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.`
9181
+ 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.`
9083
9182
  },
9084
9183
  {
9085
9184
  title: "MCP tools \u2014 task orchestration",
9086
9185
  domain: "tool-use",
9087
9186
  priority: "p1",
9088
- 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.'
9187
+ 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.`
9089
9188
  },
9090
9189
  {
9091
9190
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -9115,7 +9214,7 @@ var init_platform_procedures = __esm({
9115
9214
  title: "MCP tools \u2014 admin, config, and operations",
9116
9215
  domain: "tool-use",
9117
9216
  priority: "p1",
9118
- 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.'
9217
+ 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.'
9119
9218
  }
9120
9219
  ];
9121
9220
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -12936,6 +13035,8 @@ async function writeMemory(record) {
12936
13035
  source_type: record.source_type ?? null,
12937
13036
  tier: record.tier ?? classifyTier(record),
12938
13037
  supersedes_id: record.supersedes_id ?? null,
13038
+ valid_from: record.valid_from ?? record.timestamp,
13039
+ invalid_at: record.invalid_at ?? null,
12939
13040
  draft: record.draft ? 1 : 0,
12940
13041
  memory_type: memoryType,
12941
13042
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -12954,7 +13055,8 @@ async function writeMemory(record) {
12954
13055
  token_cost: record.token_cost ?? null,
12955
13056
  audience: record.audience ?? null,
12956
13057
  language_type: record.language_type ?? inferLanguageType(record),
12957
- parent_memory_id: record.parent_memory_id ?? null
13058
+ parent_memory_id: record.parent_memory_id ?? null,
13059
+ procedure_for: record.procedure_for ?? null
12958
13060
  };
12959
13061
  _pendingRecords.push(dbRow);
12960
13062
  orgBus.emit({
@@ -13009,6 +13111,8 @@ async function flushBatch() {
13009
13111
  const sourceType = row.source_type ?? null;
13010
13112
  const tier = row.tier ?? 3;
13011
13113
  const supersedesId = row.supersedes_id ?? null;
13114
+ const validFrom = row.valid_from ?? row.timestamp;
13115
+ const invalidAt = row.invalid_at ?? null;
13012
13116
  const draft = row.draft ? 1 : 0;
13013
13117
  const memoryType = row.memory_type ?? "raw";
13014
13118
  const trajectory = row.trajectory ?? null;
@@ -13028,15 +13132,16 @@ async function flushBatch() {
13028
13132
  const audience = row.audience ?? null;
13029
13133
  const languageType = row.language_type ?? null;
13030
13134
  const parentMemoryId = row.parent_memory_id ?? null;
13135
+ const procedureFor = row.procedure_for ?? null;
13031
13136
  const cols = `id, agent_id, agent_role, session_id, timestamp,
13032
13137
  tool_name, project_name,
13033
13138
  has_error, raw_text, vector, version, task_id, importance, status,
13034
13139
  confidence, last_accessed,
13035
13140
  workspace_id, document_id, user_id, char_offset, page_number,
13036
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
13141
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
13037
13142
  intent, outcome, domain, referenced_entities, retrieval_count,
13038
13143
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
13039
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
13144
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
13040
13145
  const metaArgs = [
13041
13146
  intent,
13042
13147
  outcome,
@@ -13052,7 +13157,8 @@ async function flushBatch() {
13052
13157
  tokenCost,
13053
13158
  audience,
13054
13159
  languageType,
13055
- parentMemoryId
13160
+ parentMemoryId,
13161
+ procedureFor
13056
13162
  ];
13057
13163
  const baseArgs = [
13058
13164
  row.id,
@@ -13081,6 +13187,8 @@ async function flushBatch() {
13081
13187
  sourceType,
13082
13188
  tier,
13083
13189
  supersedesId,
13190
+ validFrom,
13191
+ invalidAt,
13084
13192
  draft,
13085
13193
  memoryType,
13086
13194
  trajectory,
@@ -13088,8 +13196,8 @@ async function flushBatch() {
13088
13196
  ];
13089
13197
  return {
13090
13198
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
13091
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
13092
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
13199
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
13200
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
13093
13201
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
13094
13202
  };
13095
13203
  };
@@ -13205,6 +13313,12 @@ async function searchMemories(queryVector, agentId, options) {
13205
13313
  AND vector IS NOT NULL${statusFilter}${draftFilter}
13206
13314
  AND COALESCE(confidence, 0.7) >= 0.3`;
13207
13315
  const args = [agentId];
13316
+ if (options?.asOf) {
13317
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
13318
+ args.push(options.asOf, options.asOf);
13319
+ } else {
13320
+ sql += ` AND invalid_at IS NULL`;
13321
+ }
13208
13322
  const scope = buildWikiScopeFilter(options, "");
13209
13323
  sql += scope.clause;
13210
13324
  args.push(...scope.args);