@bastani/atomic 0.9.62 → 0.9.63

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 (29) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/package.json +1 -1
  4. package/dist/builtin/subagents/package.json +1 -1
  5. package/dist/builtin/web-access/package.json +1 -1
  6. package/dist/builtin/workflows/builtin/{chunk-vzbec50q.js → chunk-95r61ve5.js} +1 -1
  7. package/dist/builtin/workflows/builtin/{chunk-a2j0x1a6.js → chunk-qbthhjjk.js} +1 -1
  8. package/dist/builtin/workflows/builtin/{chunk-zhyqnf5g.js → chunk-rv4xxwd6.js} +69 -26
  9. package/dist/builtin/workflows/builtin/goal.js +2 -2
  10. package/dist/builtin/workflows/builtin/index.js +3 -3
  11. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  12. package/dist/builtin/workflows/package.json +1 -1
  13. package/dist/builtin/workflows/src/durable/journal/internal.d.ts +11 -1
  14. package/dist/builtin/workflows/src/durable/journal/sql-run-journal.d.ts +2 -0
  15. package/dist/builtin/workflows/src/extension/index.bundle.mjs +106 -50
  16. package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +106 -50
  17. package/dist/builtin/workflows/src/index.js +106 -50
  18. package/dist/builtin/workflows/src/local-worker-entry.mjs +106 -50
  19. package/dist/core/storage/database.d.ts +11 -1
  20. package/dist/core/storage/database.d.ts.map +1 -1
  21. package/dist/core/storage/database.js.map +1 -1
  22. package/dist/core/storage/index.d.ts +1 -1
  23. package/dist/core/storage/index.d.ts.map +1 -1
  24. package/dist/core/storage/index.js.map +1 -1
  25. package/dist/core/storage/node-sqlite.d.ts.map +1 -1
  26. package/dist/core/storage/node-sqlite.js +194 -23
  27. package/dist/core/storage/node-sqlite.js.map +1 -1
  28. package/npm-shrinkwrap.json +32 -32
  29. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.63] - 2026-11-02
6
+
7
+ Workflow journals that survive a stalled lock holder, and pooled roots that stay responsive with thousands of finished runs.
8
+
9
+ - **A stalled journal lock holder no longer fails everyone:** journal writes wait through up to 10 minutes of a write lock held by a stalled process instead of giving up after 30 s, and group commits release the lock every 100 ms; with 300 pooled children and a holder stopped for 45 s, the root and all 300 children now complete instead of all failing ([#4186](https://github.com/bastani-inc/atomic/pull/4186))
10
+ - **Activity batches stay fast with many finished runs:** applying a pooled root's activity looks runs up by id instead of scanning the store, so with 5,000 finished runs a batch takes 11 ms instead of 101 to 145 ms ([#4184](https://github.com/bastani-inc/atomic/pull/4184))
11
+
12
+ ### Fixed
13
+
14
+ - Workflow runs no longer fail when another process stalls while it holds the workflow journal's write lock, for example starved of CPU on a heavily loaded host, paused by garbage collection, or stopped by a signal. Every other journal writer used to give up after 30 s with `database is locked … gave up after 30xxx ms waiting for the write lock`, which failed pooled roots together with all their children, although the holder commits as soon as it runs again. Journal writes now keep waiting through up to 10 minutes of such a stall, retrying less often after the first 30 s, and a writer that does give up names the process holding the lock and how long ago it took it. Registering a run no longer waits for the lock synchronously, which froze the process for up to 5 s, and a long batch of queued transactions commits what it has every 100 ms, so other processes' writers get in between. With 300 pooled children and another process stopped for 45 s holding the lock, the root and all 300 children used to fail; now all of them keep running ([#4183](https://github.com/bastani-inc/atomic/issues/4183))
15
+ - A root with many children on local workers (`workflows.localWorkers`) no longer stalls its terminal for a tenth of a second or more each time it applies its children's latest activity, once the host has accumulated thousands of finished runs. Applying a batch of activity found each published run by scanning every run in the store, several times per child; runs are now looked up by id. With 300 busy children and 5,000 finished runs in the store, applying a batch now takes p90 11 ms instead of 101–145 ms, and the main thread's p99 lag drops from 88–111 ms to 11 ms ([#4182](https://github.com/bastani-inc/atomic/issues/4182))
16
+
17
+ ## [0.9.63-alpha.1] - 2026-11-02
18
+
19
+ Workflow journals that survive a stalled lock holder, and pooled roots that stay responsive with thousands of finished runs.
20
+
21
+ - **A stalled journal lock holder no longer fails everyone:** journal writes wait through up to 10 minutes of a write lock held by a stalled process instead of giving up after 30 s, and group commits release the lock every 100 ms; with 300 pooled children and a holder stopped for 45 s, the root and all 300 children now complete instead of all failing ([#4186](https://github.com/bastani-inc/atomic/pull/4186))
22
+ - **Activity batches stay fast with many finished runs:** applying a pooled root's activity looks runs up by id instead of scanning the store, so with 5,000 finished runs a batch takes 11 ms instead of 101 to 145 ms ([#4184](https://github.com/bastani-inc/atomic/pull/4184))
23
+
24
+ ### Fixed
25
+
26
+ - Workflow runs no longer fail when another process stalls while it holds the workflow journal's write lock, for example starved of CPU on a heavily loaded host, paused by garbage collection, or stopped by a signal. Every other journal writer used to give up after 30 s with `database is locked … gave up after 30xxx ms waiting for the write lock`, which failed pooled roots together with all their children, although the holder commits as soon as it runs again. Journal writes now keep waiting through up to 10 minutes of such a stall, retrying less often after the first 30 s, and a writer that does give up names the process holding the lock and how long ago it took it. Registering a run no longer waits for the lock synchronously, which froze the process for up to 5 s, and a long batch of queued transactions commits what it has every 100 ms, so other processes' writers get in between. With 300 pooled children and another process stopped for 45 s holding the lock, the root and all 300 children used to fail; now all of them keep running ([#4183](https://github.com/bastani-inc/atomic/issues/4183))
27
+ - A root with many children on local workers (`workflows.localWorkers`) no longer stalls its terminal for a tenth of a second or more each time it applies its children's latest activity, once the host has accumulated thousands of finished runs. Applying a batch of activity found each published run by scanning every run in the store, several times per child; runs are now looked up by id. With 300 busy children and 5,000 finished runs in the store, applying a batch now takes p90 11 ms instead of 101–145 ms, and the main thread's p99 lag drops from 88–111 ms to 11 ms ([#4182](https://github.com/bastani-inc/atomic/issues/4182))
28
+
5
29
  ## [0.9.62] - 2026-11-02
6
30
 
7
31
  Pooled roots that keep dispatching under load, children that survive a stalled worker, and Intercom sends that no longer report a delivered message as failed.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.62",
3
+ "version": "0.9.63",
4
4
  "private": true,
5
5
  "description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.62",
3
+ "version": "0.9.63",
4
4
  "private": true,
5
5
  "description": "Atomic's pi-compatible MCP integration for server connections, OAuth, tools, resources, and terminal management.",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.9.62",
3
+ "version": "0.9.63",
4
4
  "private": true,
5
5
  "description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
6
6
  "contributors": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.9.62",
3
+ "version": "0.9.63",
4
4
  "private": true,
5
5
  "description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
6
6
  "contributors": [
@@ -37,7 +37,7 @@ import {
37
37
  parseFailureDiagnostics,
38
38
  summarizeReviewConvergence,
39
39
  reverify_consolidated_batch
40
- } from "./chunk-zhyqnf5g.js";
40
+ } from "./chunk-rv4xxwd6.js";
41
41
  import {
42
42
  VERIFICATION_SCALE
43
43
  } from "./chunk-n1910xc4.js";
@@ -39,7 +39,7 @@ import {
39
39
  reviewerFailureText,
40
40
  summarizeReviewConvergence,
41
41
  reverify_consolidated_batch
42
- } from "./chunk-zhyqnf5g.js";
42
+ } from "./chunk-rv4xxwd6.js";
43
43
  import {
44
44
  fold_usage
45
45
  } from "./chunk-tgt0s5e5.js";
@@ -1329,13 +1329,13 @@ class WorkflowObservationRuntime {
1329
1329
  return run.rootRunId;
1330
1330
  if (run.parentRunId === undefined)
1331
1331
  return run.id;
1332
- const parent = this.runs().find((candidate) => candidate.id === run.parentRunId);
1332
+ const parent = this.runs.find(run.parentRunId);
1333
1333
  return parent ? this.rootRunId(parent) : run.parentRunId;
1334
1334
  }
1335
1335
  lifecycle(target, attribution = "unknown") {
1336
1336
  if (this.recovering && !this.liveRunIds.has(target.runId))
1337
1337
  return;
1338
- const run = this.runs().find((candidate) => candidate.id === target.runId);
1338
+ const run = this.runs.find(target.runId);
1339
1339
  if (!run)
1340
1340
  return;
1341
1341
  const rootRunId = this.rootRunId(run);
@@ -1356,7 +1356,7 @@ class WorkflowObservationRuntime {
1356
1356
  });
1357
1357
  }
1358
1358
  control(runId, action, attribution = "unknown") {
1359
- const run = this.runs().find((candidate) => candidate.id === runId);
1359
+ const run = this.runs.find(runId);
1360
1360
  if (!run)
1361
1361
  return;
1362
1362
  if ((action === "quit" || action === "kill" || action === "pause") && this.liveRunIds.has(runId))
@@ -1367,7 +1367,7 @@ class WorkflowObservationRuntime {
1367
1367
  this.invalidate({ runId });
1368
1368
  }
1369
1369
  heartbeat(runId, scheduledAt, intervalMinutes) {
1370
- const run = this.runs().find((candidate) => candidate.id === runId);
1370
+ const run = this.runs.find(runId);
1371
1371
  if (run)
1372
1372
  this.emit({
1373
1373
  kind: "heartbeat",
@@ -1393,7 +1393,7 @@ class WorkflowObservationRuntime {
1393
1393
  if (changes === undefined) {
1394
1394
  const next = new Map;
1395
1395
  const nextOwners = new Map;
1396
- for (const run of this.runs()) {
1396
+ for (const run of this.runs.all()) {
1397
1397
  const owners = nextOwners.get(run.id) ?? new Map;
1398
1398
  nextOwners.set(run.id, owners);
1399
1399
  this.captureRun(run, this.previous.has(`run:${run.id}`), next, owners);
@@ -1420,10 +1420,15 @@ class WorkflowObservationRuntime {
1420
1420
  nodes.tools.add(change.toolNodeId);
1421
1421
  changed.set(change.runId, nodes);
1422
1422
  }
1423
- for (const run of this.runs()) {
1424
- const change = changed.get(run.id);
1425
- if (change === undefined)
1423
+ const ordered = [...changed.keys()].flatMap((runId) => {
1424
+ const position = this.runs.position(runId);
1425
+ return position === undefined ? [] : [{ runId, position }];
1426
+ }).sort((left, right) => left.position - right.position);
1427
+ for (const { runId } of ordered) {
1428
+ const run = this.runs.find(runId);
1429
+ if (run === undefined)
1426
1430
  continue;
1431
+ const change = changed.get(runId);
1427
1432
  const existing = this.previous.has(`run:${run.id}`);
1428
1433
  const nodes = change === "run" || !existing ? undefined : change;
1429
1434
  const next = new Map;
@@ -1461,7 +1466,7 @@ class WorkflowObservationRuntime {
1461
1466
  keys.push(key);
1462
1467
  const previous = this.previous.get(key);
1463
1468
  if (!historical && this.liveRunIds.has(target.runId) && previous?.status !== target.status) {
1464
- this.lifecycle(previous && previous.kind !== "prompt" && target.kind !== "prompt" ? { ...target, previousStatus: previous.status } : target, this.runs().find((candidate) => candidate.id === target.runId)?.origin);
1469
+ this.lifecycle(previous && previous.kind !== "prompt" && target.kind !== "prompt" ? { ...target, previousStatus: previous.status } : target, this.runs.find(target.runId)?.origin);
1465
1470
  }
1466
1471
  };
1467
1472
  visit("run", `run:${run.id}`, { kind: "run", runId: run.id, status: run.status });
@@ -1591,6 +1596,7 @@ function applyRunEndMetadata(run, metadata) {
1591
1596
  function createStoreState() {
1592
1597
  return {
1593
1598
  runs: [],
1599
+ runPositions: new Map,
1594
1600
  notices: [],
1595
1601
  listeners: new Set,
1596
1602
  invalidationListeners: new Set,
@@ -1600,11 +1606,27 @@ function createStoreState() {
1600
1606
  version: 0
1601
1607
  };
1602
1608
  }
1609
+ function reindexRuns(state) {
1610
+ state.runPositions.clear();
1611
+ state.runs.forEach((run, position) => {
1612
+ if (!state.runPositions.has(run.id))
1613
+ state.runPositions.set(run.id, position);
1614
+ });
1615
+ }
1616
+ function runPosition(state, runId) {
1617
+ const position = state.runPositions.get(runId);
1618
+ if (position !== undefined && state.runs[position]?.id === runId)
1619
+ return position;
1620
+ if (position === undefined && state.runPositions.size === state.runs.length)
1621
+ return;
1622
+ reindexRuns(state);
1623
+ return state.runPositions.get(runId);
1624
+ }
1603
1625
  function createStoreContext(state = createStoreState()) {
1604
1626
  let cachedGraphVersion = -1;
1605
1627
  let cachedGraphSnapshot;
1606
1628
  const graphBuilder = new GraphStoreSnapshotBuilder;
1607
- const observation = new WorkflowObservationRuntime(() => state.runs, bumpAndNotify);
1629
+ const observation = new WorkflowObservationRuntime({ all: () => state.runs, find: findRun, position: (runId) => runPosition(state, runId) }, bumpAndNotify);
1608
1630
  function snapshot() {
1609
1631
  return JSON.parse(JSON.stringify({ runs: state.runs, notices: state.notices, version: state.version }));
1610
1632
  }
@@ -1618,7 +1640,7 @@ function createStoreContext(state = createStoreState()) {
1618
1640
  function graphRun(runId) {
1619
1641
  if (cachedGraphSnapshot !== undefined && cachedGraphVersion === state.version)
1620
1642
  return findSnapshotRun(cachedGraphSnapshot.runs, runId);
1621
- const run = state.runs.find((candidate) => candidate.id === runId);
1643
+ const run = findRun(runId);
1622
1644
  return run === undefined ? undefined : graphBuilder.buildRun(run);
1623
1645
  }
1624
1646
  let batchDepth = 0;
@@ -1672,7 +1694,29 @@ function createStoreContext(state = createStoreState()) {
1672
1694
  }
1673
1695
  }
1674
1696
  function findRun(runId) {
1675
- return state.runs.find((r) => r.id === runId);
1697
+ const position = runPosition(state, runId);
1698
+ return position === undefined ? undefined : state.runs[position];
1699
+ }
1700
+ function placeRun(run) {
1701
+ const position = runPosition(state, run.id);
1702
+ if (position !== undefined) {
1703
+ state.runs[position] = run;
1704
+ return;
1705
+ }
1706
+ state.runPositions.set(run.id, state.runs.length);
1707
+ state.runs.push(run);
1708
+ }
1709
+ function removeRunAt(position) {
1710
+ const [removed] = state.runs.splice(position, 1);
1711
+ if (removed === undefined)
1712
+ return;
1713
+ state.runPositions.delete(removed.id);
1714
+ for (let index = position;index < state.runs.length; index++)
1715
+ state.runPositions.set(state.runs[index].id, index);
1716
+ }
1717
+ function clearRuns() {
1718
+ state.runs.length = 0;
1719
+ state.runPositions.clear();
1676
1720
  }
1677
1721
  function findStage(run, stageId) {
1678
1722
  return run.stages.find((s) => s.id === stageId);
@@ -1728,6 +1772,10 @@ function createStoreContext(state = createStoreState()) {
1728
1772
  batch,
1729
1773
  bumpAndNotify,
1730
1774
  findRun,
1775
+ runPosition: (runId) => runPosition(state, runId),
1776
+ placeRun,
1777
+ removeRunAt,
1778
+ clearRuns,
1731
1779
  findStage,
1732
1780
  rejectPrompt,
1733
1781
  rejectStagePrompt,
@@ -2488,6 +2536,9 @@ function createRunStoreMethods(context) {
2488
2536
  runs() {
2489
2537
  return state.runs;
2490
2538
  },
2539
+ run(runId) {
2540
+ return context.findRun(runId);
2541
+ },
2491
2542
  notices() {
2492
2543
  return state.notices;
2493
2544
  },
@@ -2507,11 +2558,7 @@ function createRunStoreMethods(context) {
2507
2558
  return null;
2508
2559
  },
2509
2560
  recordRunStart(run) {
2510
- const index = state.runs.findIndex((existing) => existing.id === run.id);
2511
- if (index === -1)
2512
- state.runs.push(run);
2513
- else
2514
- state.runs[index] = run;
2561
+ context.placeRun(run);
2515
2562
  context.bumpAndNotify({ runId: run.id });
2516
2563
  },
2517
2564
  recordRunSummary(runId, summary, summarizedThrough) {
@@ -2652,17 +2699,13 @@ function createRunStoreMethods(context) {
2652
2699
  restored.result = metadata.result;
2653
2700
  if (metadata.budgetState !== undefined)
2654
2701
  restored.budgetState = metadata.budgetState;
2655
- const index = state.runs.findIndex((candidate) => candidate.id === source.id);
2656
- if (index < 0)
2657
- state.runs.push(restored);
2658
- else
2659
- state.runs[index] = restored;
2702
+ context.placeRun(restored);
2660
2703
  context.bumpAndNotify();
2661
2704
  return true;
2662
2705
  },
2663
2706
  removeRun(runId) {
2664
- const index = state.runs.findIndex((r) => r.id === runId);
2665
- if (index < 0)
2707
+ const index = context.runPosition(runId);
2708
+ if (index === undefined)
2666
2709
  return false;
2667
2710
  const run = state.runs[index];
2668
2711
  const pending = run.pendingPrompt;
@@ -2674,7 +2717,7 @@ function createRunStoreMethods(context) {
2674
2717
  for (const stage of run.stages) {
2675
2718
  state.stagePromptAnswers.delete(context.stagePromptAnswerKey(runId, stage.id));
2676
2719
  }
2677
- state.runs.splice(index, 1);
2720
+ context.removeRunAt(index);
2678
2721
  for (let i = state.notices.length - 1;i >= 0; i--) {
2679
2722
  if (state.notices[i]?.runId === runId)
2680
2723
  state.notices.splice(i, 1);
@@ -2766,7 +2809,7 @@ function createRunStoreMethods(context) {
2766
2809
  clear() {
2767
2810
  if (state.runs.length === 0 && state.notices.length === 0 && state.resolvers.size === 0 && state.stagePromptAnswers.size === 0 && state.stagePromptDrafts.size === 0)
2768
2811
  return;
2769
- state.runs.length = 0;
2812
+ context.clearRuns();
2770
2813
  state.notices.length = 0;
2771
2814
  for (const entry of state.resolvers.values()) {
2772
2815
  entry.reject(new Error("atomic-workflows: store cleared"));
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  goal_default
3
- } from "./chunk-vzbec50q.js";
4
- import"./chunk-zhyqnf5g.js";
3
+ } from "./chunk-95r61ve5.js";
4
+ import"./chunk-rv4xxwd6.js";
5
5
  import"./chunk-2377r57g.js";
6
6
  import"./chunk-z0d03ktm.js";
7
7
  import"./chunk-n1910xc4.js";
@@ -16,8 +16,8 @@ import {
16
16
  } from "./chunk-8d40nyz9.js";
17
17
  import {
18
18
  goal_default
19
- } from "./chunk-vzbec50q.js";
20
- import"./chunk-zhyqnf5g.js";
19
+ } from "./chunk-95r61ve5.js";
20
+ import"./chunk-rv4xxwd6.js";
21
21
  import"./chunk-z0d03ktm.js";
22
22
  import {
23
23
  open_claude_design_default
@@ -27,7 +27,7 @@ import {
27
27
  } from "./chunk-37gm3bzk.js";
28
28
  import {
29
29
  ralph_default
30
- } from "./chunk-a2j0x1a6.js";
30
+ } from "./chunk-qbthhjjk.js";
31
31
  import {
32
32
  tournament_default
33
33
  } from "./chunk-2f1shjah.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ralph_default
3
- } from "./chunk-a2j0x1a6.js";
4
- import"./chunk-zhyqnf5g.js";
3
+ } from "./chunk-qbthhjjk.js";
4
+ import"./chunk-rv4xxwd6.js";
5
5
  import"./chunk-2377r57g.js";
6
6
  import"./chunk-z0d03ktm.js";
7
7
  import"./chunk-n1910xc4.js";
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.62",
3
+ "version": "0.9.63",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -1,7 +1,17 @@
1
- import type { SqliteExecutor } from "@bastani/atomic/storage";
1
+ import type { SqliteExecutor, SqliteTransactionOptions } from "@bastani/atomic/storage";
2
2
  import { type AppendOutcome, type Lease, type RunJournal, type RunJournalBatchAppend, type RunSnapshotTail, type RunVersion } from "./types.js";
3
3
  export type { RunJournalBatchAppend as JournalBatchAppend, RunSnapshotTail, RunVersion } from "./types.js";
4
4
  export declare const JOURNAL_READ_PAGE_SIZE = 256;
5
+ /**
6
+ * How every journal write transaction waits for the write lock: up to 10 minutes of no commit anywhere, far past the
7
+ * 30 s a connection waits by default. A process stalled holding the lock (starved of CPU, paused for garbage
8
+ * collection, stopped by a signal) commits or rolls back once it runs again, and failing the waiting write would only
9
+ * fail its run without freeing the lock (#4183). Only waiting for the lock is longer: once a write has the lock it
10
+ * checks its lease or work lease as before, so a stall longer than a lease ends in that lease's expiry and fencing,
11
+ * never in a write applied under a lease that was lost meanwhile. Waiters keep their writers'-queue order only while
12
+ * they keep polling; that order is fairness, not correctness, which the checks inside each transaction keep.
13
+ */
14
+ export declare const JOURNAL_TRANSACTION: SqliteTransactionOptions;
5
15
  /** Event types the durable backend folds without replacing the run's handle; every other event carries it. */
6
16
  export declare const NON_SNAPSHOT_EVENT_TYPES: readonly ["CheckpointRecorded", "MessageSettled", "MessageDelivered"];
7
17
  /** Lets a durable backend re-read only the runs that changed. */
@@ -17,6 +17,8 @@ export declare class SqlRunJournal implements RunJournal {
17
17
  get descriptor(): StorageDescriptor;
18
18
  get path(): string;
19
19
  private assertOpen;
20
+ /** A journal write transaction, waiting out a write lock held by a stalled process (`JOURNAL_TRANSACTION`). */
21
+ private transaction;
20
22
  private operation;
21
23
  private snapshotJson;
22
24
  createRun(header: RunHeader): Promise<"created" | "exists">;