@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
@@ -3356,6 +3356,20 @@ async function ensureSchema() {
3356
3356
  });
3357
3357
  } catch {
3358
3358
  }
3359
+ try {
3360
+ await client.execute({
3361
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_runtime TEXT`,
3362
+ args: []
3363
+ });
3364
+ } catch {
3365
+ }
3366
+ try {
3367
+ await client.execute({
3368
+ sql: `ALTER TABLE tasks ADD COLUMN spawn_model TEXT`,
3369
+ args: []
3370
+ });
3371
+ } catch {
3372
+ }
3359
3373
  await client.executeMultiple(`
3360
3374
  CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
3361
3375
  content_text,
@@ -3607,6 +3621,22 @@ async function ensureSchema() {
3607
3621
  );
3608
3622
  } catch {
3609
3623
  }
3624
+ for (const col of [
3625
+ "ALTER TABLE memories ADD COLUMN valid_from TEXT",
3626
+ "ALTER TABLE memories ADD COLUMN invalid_at TEXT"
3627
+ ]) {
3628
+ try {
3629
+ await client.execute(col);
3630
+ } catch {
3631
+ }
3632
+ }
3633
+ try {
3634
+ await client.execute({
3635
+ sql: `UPDATE memories SET valid_from = timestamp WHERE valid_from IS NULL`,
3636
+ args: []
3637
+ });
3638
+ } catch {
3639
+ }
3610
3640
  try {
3611
3641
  await client.execute({
3612
3642
  sql: `ALTER TABLE memories ADD COLUMN memory_type TEXT DEFAULT 'raw'`,
@@ -3649,6 +3679,13 @@ async function ensureSchema() {
3649
3679
  } catch {
3650
3680
  }
3651
3681
  }
3682
+ try {
3683
+ await client.execute({
3684
+ sql: `ALTER TABLE memories ADD COLUMN procedure_for TEXT`,
3685
+ args: []
3686
+ });
3687
+ } catch {
3688
+ }
3652
3689
  try {
3653
3690
  await client.execute({
3654
3691
  sql: `UPDATE tasks SET status = 'closed' WHERE status = 'done' AND result IS NOT NULL`,
@@ -3709,7 +3746,7 @@ import { pathToFileURL as pathToFileURL2 } from "url";
3709
3746
  import os8 from "os";
3710
3747
  import path11 from "path";
3711
3748
  import { jwtVerify, importSPKI } from "jose";
3712
- var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, PLAN_LIMITS;
3749
+ var LICENSE_PATH, CACHE_PATH, DEVICE_ID_PATH, API_BASE, PLAN_LIMITS;
3713
3750
  var init_license = __esm({
3714
3751
  "src/lib/license.ts"() {
3715
3752
  "use strict";
@@ -3717,6 +3754,7 @@ var init_license = __esm({
3717
3754
  LICENSE_PATH = path11.join(EXE_AI_DIR, "license.key");
3718
3755
  CACHE_PATH = path11.join(EXE_AI_DIR, "license-cache.json");
3719
3756
  DEVICE_ID_PATH = path11.join(EXE_AI_DIR, "device-id");
3757
+ API_BASE = process.env.EXE_CLOUD_ENDPOINT ?? "https://askexe.com/cloud";
3720
3758
  PLAN_LIMITS = {
3721
3759
  free: { devices: 1, employees: 1, memories: 5e3 },
3722
3760
  pro: { devices: 3, employees: 5, memories: 1e5 },
@@ -4540,8 +4578,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
4540
4578
  const complexity = input2.complexity ?? "standard";
4541
4579
  const sessionScope = earlySessionScope;
4542
4580
  await client.execute({
4543
- 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)
4544
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4581
+ 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)
4582
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
4545
4583
  args: [
4546
4584
  id,
4547
4585
  input2.title,
@@ -4561,6 +4599,8 @@ ${scopeMismatchWarning}` : scopeMismatchWarning;
4561
4599
  0,
4562
4600
  null,
4563
4601
  sessionScope,
4602
+ input2.spawnRuntime ?? null,
4603
+ input2.spawnModel ?? null,
4564
4604
  now,
4565
4605
  now
4566
4606
  ]
@@ -4617,7 +4657,9 @@ ${input2.context}
4617
4657
  budgetTokens: input2.budgetTokens ?? null,
4618
4658
  budgetFallbackModel: input2.budgetFallbackModel ?? null,
4619
4659
  tokensUsed: 0,
4620
- tokensWarnedAt: null
4660
+ tokensWarnedAt: null,
4661
+ spawnRuntime: input2.spawnRuntime ?? null,
4662
+ spawnModel: input2.spawnModel ?? null
4621
4663
  };
4622
4664
  }
4623
4665
  async function listTasks(input2) {
@@ -4667,7 +4709,9 @@ async function listTasks(input2) {
4667
4709
  budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
4668
4710
  budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
4669
4711
  tokensUsed: Number(r.tokens_used ?? 0),
4670
- tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
4712
+ tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null,
4713
+ spawnRuntime: r.spawn_runtime !== null && r.spawn_runtime !== void 0 ? String(r.spawn_runtime) : null,
4714
+ spawnModel: r.spawn_model !== null && r.spawn_model !== void 0 ? String(r.spawn_model) : null
4671
4715
  }));
4672
4716
  }
4673
4717
  function isTmuxSessionAlive(identifier) {
@@ -5964,6 +6008,8 @@ async function updateTask(input2) {
5964
6008
  budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
5965
6009
  tokensUsed: Number(row.tokens_used ?? 0),
5966
6010
  tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
6011
+ spawnRuntime: row.spawn_runtime !== void 0 && row.spawn_runtime !== null ? String(row.spawn_runtime) : null,
6012
+ spawnModel: row.spawn_model !== void 0 && row.spawn_model !== null ? String(row.spawn_model) : null,
5967
6013
  nextTask
5968
6014
  };
5969
6015
  }
@@ -6459,6 +6505,7 @@ function resolveExeSession() {
6459
6505
  const mySession = getMySession();
6460
6506
  if (!mySession) return null;
6461
6507
  const fromSessionName = extractRootExe(mySession);
6508
+ let candidate = null;
6462
6509
  try {
6463
6510
  const key = getSessionKey();
6464
6511
  const parentExe = getParentExe(key);
@@ -6469,13 +6516,47 @@ function resolveExeSession() {
6469
6516
  `[tmux-routing] WARN: cache says "${fromCache}" but session name says "${fromSessionName}". Trusting session name.
6470
6517
  `
6471
6518
  );
6472
- return fromSessionName;
6519
+ candidate = fromSessionName;
6520
+ } else {
6521
+ candidate = fromCache;
6473
6522
  }
6474
- return fromCache;
6475
6523
  }
6476
6524
  } catch {
6477
6525
  }
6478
- return fromSessionName ?? mySession;
6526
+ if (!candidate) {
6527
+ candidate = fromSessionName ?? mySession;
6528
+ }
6529
+ if (candidate && isRootSession(candidate)) {
6530
+ try {
6531
+ const transport = getTransport();
6532
+ const liveSessions = transport.listSessions();
6533
+ if (!liveSessions.includes(candidate)) {
6534
+ const liveRoots = liveSessions.filter((s) => isRootSession(s));
6535
+ if (liveRoots.length === 1) {
6536
+ process.stderr.write(
6537
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. Using live coordinator "${liveRoots[0]}".
6538
+ `
6539
+ );
6540
+ return liveRoots[0];
6541
+ } else if (liveRoots.length > 1) {
6542
+ const base = candidate.replace(/\d+$/, "");
6543
+ const match = liveRoots.find((s) => s.startsWith(base));
6544
+ const chosen = match ?? liveRoots[0];
6545
+ process.stderr.write(
6546
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead. ${liveRoots.length} live roots found, using "${chosen}".
6547
+ `
6548
+ );
6549
+ return chosen;
6550
+ }
6551
+ process.stderr.write(
6552
+ `[tmux-routing] WARN: resolved session "${candidate}" is dead and no live coordinator found.
6553
+ `
6554
+ );
6555
+ }
6556
+ } catch {
6557
+ }
6558
+ }
6559
+ return candidate;
6479
6560
  }
6480
6561
  function isEmployeeAlive(sessionName) {
6481
6562
  return getTransport().isAlive(sessionName);
@@ -6877,7 +6958,12 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
6877
6958
  }
6878
6959
  const spawnCwd = opts?.cwd ?? projectDir;
6879
6960
  const useExeAgent = !!(opts?.model && opts?.provider);
6880
- const agentRtConfig = getAgentRuntime(employeeName);
6961
+ const baseRtConfig = getAgentRuntime(employeeName);
6962
+ const agentRtConfig = {
6963
+ ...baseRtConfig,
6964
+ ...opts?.runtimeOverride ? { runtime: opts.runtimeOverride } : {},
6965
+ ...opts?.modelOverride ? { model: opts.modelOverride } : {}
6966
+ };
6881
6967
  const useCodex = !useExeAgent && agentRtConfig.runtime === "codex";
6882
6968
  const useOpencode = !useExeAgent && !useCodex && agentRtConfig.runtime === "opencode";
6883
6969
  const ccProvider = useExeAgent || useCodex || useOpencode ? DEFAULT_PROVIDER : detectActiveProvider();
@@ -8170,6 +8256,20 @@ var init_platform_procedures = __esm({
8170
8256
  priority: "p1",
8171
8257
  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."
8172
8258
  },
8259
+ // --- Tool guidance ---
8260
+ {
8261
+ title: "How to use company_actions \u2014 execute business actions through gateway connectors",
8262
+ domain: "tools",
8263
+ priority: "p2",
8264
+ 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."
8265
+ },
8266
+ // --- Release awareness ---
8267
+ {
8268
+ title: "What's New check \u2014 surface new features after update",
8269
+ domain: "support",
8270
+ priority: "p1",
8271
+ 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."
8272
+ },
8173
8273
  // --- Platform vs Customer ownership ---
8174
8274
  {
8175
8275
  title: "What the platform provides vs what you customize",
@@ -8258,13 +8358,13 @@ var init_platform_procedures = __esm({
8258
8358
  title: "MCP tools \u2014 memory, decision, and search",
8259
8359
  domain: "tool-use",
8260
8360
  priority: "p1",
8261
- 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.`
8361
+ 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.`
8262
8362
  },
8263
8363
  {
8264
8364
  title: "MCP tools \u2014 task orchestration",
8265
8365
  domain: "tool-use",
8266
8366
  priority: "p1",
8267
- 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.'
8367
+ 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.`
8268
8368
  },
8269
8369
  {
8270
8370
  title: "MCP tools \u2014 knowledge graph (GraphRAG)",
@@ -8294,7 +8394,7 @@ var init_platform_procedures = __esm({
8294
8394
  title: "MCP tools \u2014 admin, config, and operations",
8295
8395
  domain: "tool-use",
8296
8396
  priority: "p1",
8297
- 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.'
8397
+ 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.'
8298
8398
  }
8299
8399
  ];
8300
8400
  PLATFORM_PROCEDURE_TITLES = new Set(
@@ -8978,6 +9078,8 @@ async function writeMemory(record) {
8978
9078
  source_type: record.source_type ?? null,
8979
9079
  tier: record.tier ?? classifyTier(record),
8980
9080
  supersedes_id: record.supersedes_id ?? null,
9081
+ valid_from: record.valid_from ?? record.timestamp,
9082
+ invalid_at: record.invalid_at ?? null,
8981
9083
  draft: record.draft ? 1 : 0,
8982
9084
  memory_type: memoryType,
8983
9085
  trajectory: record.trajectory ? JSON.stringify(record.trajectory) : null,
@@ -8996,7 +9098,8 @@ async function writeMemory(record) {
8996
9098
  token_cost: record.token_cost ?? null,
8997
9099
  audience: record.audience ?? null,
8998
9100
  language_type: record.language_type ?? inferLanguageType(record),
8999
- parent_memory_id: record.parent_memory_id ?? null
9101
+ parent_memory_id: record.parent_memory_id ?? null,
9102
+ procedure_for: record.procedure_for ?? null
9000
9103
  };
9001
9104
  _pendingRecords.push(dbRow);
9002
9105
  orgBus.emit({
@@ -9051,6 +9154,8 @@ async function flushBatch() {
9051
9154
  const sourceType = row.source_type ?? null;
9052
9155
  const tier = row.tier ?? 3;
9053
9156
  const supersedesId = row.supersedes_id ?? null;
9157
+ const validFrom = row.valid_from ?? row.timestamp;
9158
+ const invalidAt = row.invalid_at ?? null;
9054
9159
  const draft = row.draft ? 1 : 0;
9055
9160
  const memoryType = row.memory_type ?? "raw";
9056
9161
  const trajectory = row.trajectory ?? null;
@@ -9070,15 +9175,16 @@ async function flushBatch() {
9070
9175
  const audience = row.audience ?? null;
9071
9176
  const languageType = row.language_type ?? null;
9072
9177
  const parentMemoryId = row.parent_memory_id ?? null;
9178
+ const procedureFor = row.procedure_for ?? null;
9073
9179
  const cols = `id, agent_id, agent_role, session_id, timestamp,
9074
9180
  tool_name, project_name,
9075
9181
  has_error, raw_text, vector, version, task_id, importance, status,
9076
9182
  confidence, last_accessed,
9077
9183
  workspace_id, document_id, user_id, char_offset, page_number,
9078
- source_path, source_type, tier, supersedes_id, draft, memory_type, trajectory, content_hash,
9184
+ source_path, source_type, tier, supersedes_id, valid_from, invalid_at, draft, memory_type, trajectory, content_hash,
9079
9185
  intent, outcome, domain, referenced_entities, retrieval_count,
9080
9186
  chain_position, review_status, context_window_pct, file_paths, commit_hash,
9081
- duration_ms, token_cost, audience, language_type, parent_memory_id`;
9187
+ duration_ms, token_cost, audience, language_type, parent_memory_id, procedure_for`;
9082
9188
  const metaArgs = [
9083
9189
  intent,
9084
9190
  outcome,
@@ -9094,7 +9200,8 @@ async function flushBatch() {
9094
9200
  tokenCost,
9095
9201
  audience,
9096
9202
  languageType,
9097
- parentMemoryId
9203
+ parentMemoryId,
9204
+ procedureFor
9098
9205
  ];
9099
9206
  const baseArgs = [
9100
9207
  row.id,
@@ -9123,6 +9230,8 @@ async function flushBatch() {
9123
9230
  sourceType,
9124
9231
  tier,
9125
9232
  supersedesId,
9233
+ validFrom,
9234
+ invalidAt,
9126
9235
  draft,
9127
9236
  memoryType,
9128
9237
  trajectory,
@@ -9130,8 +9239,8 @@ async function flushBatch() {
9130
9239
  ];
9131
9240
  return {
9132
9241
  sql: hasVector ? `INSERT OR IGNORE INTO memories (${cols})
9133
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
9134
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
9242
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories (${cols})
9243
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
9135
9244
  args: hasVector ? [...baseArgs, vectorToBlob(row.vector), ...sharedArgs, ...metaArgs] : [...baseArgs, ...sharedArgs, ...metaArgs]
9136
9245
  };
9137
9246
  };
@@ -9247,6 +9356,12 @@ async function searchMemories(queryVector, agentId, options) {
9247
9356
  AND vector IS NOT NULL${statusFilter}${draftFilter}
9248
9357
  AND COALESCE(confidence, 0.7) >= 0.3`;
9249
9358
  const args = [agentId];
9359
+ if (options?.asOf) {
9360
+ sql += ` AND (valid_from IS NULL OR valid_from <= ?) AND (invalid_at IS NULL OR invalid_at > ?)`;
9361
+ args.push(options.asOf, options.asOf);
9362
+ } else {
9363
+ sql += ` AND invalid_at IS NULL`;
9364
+ }
9250
9365
  const scope = buildWikiScopeFilter(options, "");
9251
9366
  sql += scope.clause;
9252
9367
  args.push(...scope.args);