@boardwalk-labs/runner 0.1.20 → 0.2.0

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.
@@ -1,20 +1,15 @@
1
- import { z } from "zod";
2
- import type { LeafCheckpoint, LeafResume } from "@boardwalk-labs/engine/core";
3
- import { AppError } from "./support/index.js";
1
+ import type { LeafCheckpoint } from "@boardwalk-labs/engine/core";
4
2
  /**
5
- * Sleeps at/above this hold-vs-release boundary SUSPEND (release the task; a timer re-dispatches the
6
- * run when due); shorter ones HOLD the process in-memory, where a release + replay round-trip costs
7
- * more than it saves. Mirrors the engine's `SUSPEND_THRESHOLD_MS` so the two runtimes draw the line
8
- * in the same place.
3
+ * Sleeps at/above this boundary SUSPEND on the snapshot substrate (freeze the VM; the wake fires
4
+ * when the sleep is due); shorter ones HOLD the process in-memory, where a snapshot round-trip
5
+ * costs more than it saves. Without a freeze substrate every sleep holds, whatever its length.
9
6
  */
10
7
  export declare const SUSPEND_THRESHOLD_MS = 30000;
11
- /** The durable-seam kinds the journal memoizes (mirrors `run_journal.kind` + the engine's IPC). */
12
- export type JournalKind = "agent" | "step" | "human_input" | "sleep" | "workflow_call";
13
8
  /** Why a seam suspended the run. */
14
9
  export type SuspendReason = "human_input" | "sleep" | "workflow_call";
15
10
  /** A human-in-the-loop gate carried out of a suspending seam (program-level or the in-leaf tool). */
16
11
  export interface HumanInputGate {
17
- /** The stable key the responder answers by (an author/seq key, or the model's tool-call id). */
12
+ /** The stable key the responder answers by (an author/derived key, or the model's tool-call id). */
18
13
  key: string;
19
14
  prompt: string;
20
15
  /** The response form ({@link import("@boardwalk-labs/workflow").HumanInputSpec}); validated on submit. */
@@ -29,13 +24,14 @@ export interface HumanInputGate {
29
24
  /** Everything the broker needs to persist a suspension + the wake condition. */
30
25
  export interface SuspendSignal {
31
26
  reason: SuspendReason;
32
- /** The journal seq of the suspending seam. */
27
+ /** The suspension's within-run key (a monotonic per-run counter): it keys the HITL gate rows the
28
+ * wake joins answers from. Not a journal seq — there is no journal. */
33
29
  seq: number;
34
- /** The seam's determinism fingerprint (recorded on the pending journal entry). */
35
- fingerprint: string;
36
30
  /** Present for `reason: "human_input"` — the gate to register a request row for. */
37
31
  humanInput?: HumanInputGate;
38
- /** A tool-level gate's leaf transcript checkpoint, stored so the leaf resumes where it paused. */
32
+ /** A tool-level gate's leaf transcript checkpoint. On the snapshot substrate the transcript
33
+ * rides in the frozen heap — this field is informational for the control plane, not a resume
34
+ * source. */
39
35
  leafCheckpoint?: LeafCheckpoint;
40
36
  /** Relative wait (ms) for `reason: "sleep"`; the broker computes the absolute wake time. */
41
37
  durationMs?: number;
@@ -43,99 +39,8 @@ export interface SuspendSignal {
43
39
  * woken when this child finalizes (the sweep wakes a parent whose child is terminal). */
44
40
  childRunId?: string;
45
41
  }
46
- /**
47
- * The control signal a host seam raises to suspend the run. Unlike the engine (a child process the
48
- * supervisor kills out-of-band), the backend worker runs the program IN-PROCESS, so a suspend is
49
- * surfaced by the host calling `onSuspend(signal)` and returning a NEVER-resolving promise; the
50
- * worker races that against the program body and tears down. This class exists so a seam that has no
51
- * `onSuspend` wired (the local/test path) can still raise a typed, catchable signal.
52
- */
53
- export declare class SuspendError extends Error {
54
- readonly signal: SuspendSignal;
55
- constructor(signal: SuspendSignal);
56
- }
57
- /** Journal-entry states (mirrors `run_journal.state` + the engine's IPC). */
58
- export declare const JOURNAL_STATES: readonly ["pending", "suspended", "resolved"];
59
- export type JournalEntryState = (typeof JOURNAL_STATES)[number];
60
- /** A memoized journal entry, as the host reads it back on replay (mirrors the engine's IPC shape). */
61
- export interface JournalLookup {
62
- seq: number;
63
- kind: JournalKind;
64
- fingerprint: string;
65
- /** `resolved` ⇒ `result` is the memoized value; `suspended` ⇒ a parked agent leaf (result is the
66
- * {@link LeafResume} the host re-enters with); `pending` ⇒ awaiting an external event (re-suspend). */
67
- state: JournalEntryState;
68
- result: unknown;
69
- }
70
- /** Validate the broker's journal_get response (the worker's run token doesn't exempt the channel from
71
- * validation). The result is genuinely heterogeneous JSON (an agent return / a LeafResume / a
72
- * HumanInputResult), parsed per-kind downstream — so it stays `unknown` here, the validation seam. */
73
- export declare const journalLookupSchema: z.ZodObject<{
74
- seq: z.ZodNumber;
75
- kind: z.ZodEnum<{
76
- sleep: "sleep";
77
- agent: "agent";
78
- step: "step";
79
- human_input: "human_input";
80
- workflow_call: "workflow_call";
81
- }>;
82
- fingerprint: z.ZodString;
83
- state: z.ZodEnum<{
84
- pending: "pending";
85
- suspended: "suspended";
86
- resolved: "resolved";
87
- }>;
88
- result: z.ZodUnknown;
89
- }, z.core.$strip>;
90
- /** The journal the host reads (replay lookup) + writes (resolved seam results). Backed by the broker
91
- * over the run token on hosted runs; absent on the local/test path (no durable suspension). */
92
- export interface JournalSeam {
93
- /** The memoized entry for a seam, or null on a replay miss. */
94
- get(seq: number): Promise<JournalLookup | null>;
95
- /** Record a RESOLVED seam result (idempotent on the run + seq; a resolved entry is immutable). */
96
- put(entry: {
97
- seq: number;
98
- kind: JournalKind;
99
- fingerprint: string;
100
- label: string;
101
- result: unknown;
102
- }): Promise<void>;
103
- }
104
- /**
105
- * The synchronous, monotonic durable-seam counter. Incremented at each journaled seam's ENTRY:
106
- * because a program's synchronous call order is deterministic (even under `Promise.all`, whose
107
- * `.map(...)` runs left-to-right synchronously), the same logical call gets the same `seq` on every
108
- * execution — the journal key that lets a resumed run return a memoized result.
109
- *
110
- * It also drives SILENT REPLAY: a resume starts suppressed (observability — console output, phase
111
- * markers — was already emitted last segment) and goes `live` the moment it reaches the suspending
112
- * seam (the frontier = the highest journaled seq). A fresh run (frontier 0) is live immediately.
113
- */
114
- export declare class SeamSequencer {
115
- private readonly replayFrontier;
42
+ /** A monotonic per-run counter for suspension/gate keys (the `seq` on {@link SuspendSignal}). */
43
+ export declare class SuspensionCounter {
116
44
  private count;
117
- private liveFlag;
118
- constructor(replayFrontier?: number);
119
45
  next(): number;
120
- /** True once execution has crossed the replay frontier — output after this point is NEW. */
121
- get isLive(): boolean;
122
- /** True while re-running already-journaled seams on a resume (observability suppressed). */
123
- isReplaying(): boolean;
124
46
  }
125
- /** The child run id a `workflow_call` seam journals while it waits (parsed back on resume). */
126
- export declare const childRunIdSchema: z.ZodString;
127
- /** A stable content hash of a seam's salient args — the determinism check on replay. Identical
128
- * construction to the engine's `seamFingerprint` so a journal written by one runtime validates in
129
- * the other (the conformance promise). */
130
- export declare function seamFingerprint(parts: readonly unknown[]): string;
131
- /** A seam reached on replay didn't match what the journal recorded at that seq — the workflow's code
132
- * on the path to a suspend changed (a different prompt/model/step name, or a different seam kind).
133
- * Fails the run loudly rather than returning a stale memoized result for the wrong call. */
134
- export declare function determinismError(seq: number, got: JournalKind, recorded: JournalKind): AppError;
135
- /**
136
- * The shape of a SUSPENDED agent leaf's journal result on resume: the transcript checkpoint plus the
137
- * answers the broker joined from the resolved request rows, keyed by tool-call id. `messages` is the
138
- * engine's own serialized transcript round-tripping through JSON — handed straight back to the leaf,
139
- * not re-validated field-by-field. Structurally a {@link LeafResume}.
140
- */
141
- export declare const leafResumeSchema: z.ZodType<LeafResume>;
@@ -1,120 +1,27 @@
1
- // Durable-suspension primitives for the JS-body worker (the durable-suspension design).
1
+ // Suspension primitives for the JS-body worker (the snapshot-substrate model).
2
2
  //
3
- // A run SUSPENDS at a durable seam — a long `sleep`, a `humanInput()` gate, or the in-leaf
4
- // `human_input` tool — by releasing its Fargate task and re-acquiring one on wake. The mechanism is
5
- // a JOURNAL: every memoizable seam (`agent` / `step` / `humanInput` / `sleep`) is keyed by a
6
- // synchronous monotonic `seq` and records its whole result, so a resumed run RESTARTS from the top
7
- // and replays through the journal (a hit returns the memoized value instantly; we never replay an
8
- // LLM turn — we memoize whole results, the `findChildCall` pattern generalized). This is NOT
9
- // Temporal-style turn replay, so it does not violate the no-replay-engine rule (CLAUDE.md).
3
+ // A run SUSPENDS at a waiting seam — a long `sleep`, a `humanInput()` gate, or the in-leaf
4
+ // `human_input` tool — by freezing the WHOLE microVM: the freeze coordinator quiesces platform
5
+ // streams, the host snapshots memory, and the wake restores the literal heap, so the seam's
6
+ // `await` resolves in place. There is no journal and no replay the heap is the durable unit.
10
7
  //
11
- // This file holds the SHARED pieces the host + worker + broker client agree on: the suspend signal a
12
- // seam raises, the journal seam the host reads/writes, the seq sequencer driving silent replay, and
13
- // the determinism fingerprint. The behavior is conformance-pinned to the OSS engine's
14
- // `src/run/child_host.ts` — the two runtimes must suspend + resume identically.
15
- import { createHash } from "node:crypto";
16
- import { z } from "zod";
17
- import { AppError, ErrorCode } from "./support/index.js";
8
+ // A runtime with NO freeze substrate (a self-hosted runner daemon, unit tests, the Fargate
9
+ // break-glass) HOLDS instead: the seam blocks the live process until its condition is met. One
10
+ // mechanism serves every non-snapshot surface; the run pays for the idle wait.
11
+ //
12
+ // This file holds the SHARED pieces the host + worker + broker client agree on: the suspend
13
+ // signal a seam raises (what the broker persists as the wake condition) and the human-input gate
14
+ // it carries.
18
15
  /**
19
- * Sleeps at/above this hold-vs-release boundary SUSPEND (release the task; a timer re-dispatches the
20
- * run when due); shorter ones HOLD the process in-memory, where a release + replay round-trip costs
21
- * more than it saves. Mirrors the engine's `SUSPEND_THRESHOLD_MS` so the two runtimes draw the line
22
- * in the same place.
16
+ * Sleeps at/above this boundary SUSPEND on the snapshot substrate (freeze the VM; the wake fires
17
+ * when the sleep is due); shorter ones HOLD the process in-memory, where a snapshot round-trip
18
+ * costs more than it saves. Without a freeze substrate every sleep holds, whatever its length.
23
19
  */
24
20
  export const SUSPEND_THRESHOLD_MS = 30_000;
25
- /**
26
- * The control signal a host seam raises to suspend the run. Unlike the engine (a child process the
27
- * supervisor kills out-of-band), the backend worker runs the program IN-PROCESS, so a suspend is
28
- * surfaced by the host calling `onSuspend(signal)` and returning a NEVER-resolving promise; the
29
- * worker races that against the program body and tears down. This class exists so a seam that has no
30
- * `onSuspend` wired (the local/test path) can still raise a typed, catchable signal.
31
- */
32
- export class SuspendError extends Error {
33
- signal;
34
- constructor(signal) {
35
- super(`run suspended (${signal.reason}) at seam ${String(signal.seq)}`);
36
- this.name = "SuspendError";
37
- this.signal = signal;
38
- }
39
- }
40
- /** Journal-entry states (mirrors `run_journal.state` + the engine's IPC). */
41
- export const JOURNAL_STATES = ["pending", "suspended", "resolved"];
42
- /** Validate the broker's journal_get response (the worker's run token doesn't exempt the channel from
43
- * validation). The result is genuinely heterogeneous JSON (an agent return / a LeafResume / a
44
- * HumanInputResult), parsed per-kind downstream — so it stays `unknown` here, the validation seam. */
45
- export const journalLookupSchema = z.object({
46
- seq: z.number().int().positive(),
47
- kind: z.enum(["agent", "step", "human_input", "sleep", "workflow_call"]),
48
- fingerprint: z.string(),
49
- state: z.enum(JOURNAL_STATES),
50
- result: z.unknown(),
51
- });
52
- /**
53
- * The synchronous, monotonic durable-seam counter. Incremented at each journaled seam's ENTRY:
54
- * because a program's synchronous call order is deterministic (even under `Promise.all`, whose
55
- * `.map(...)` runs left-to-right synchronously), the same logical call gets the same `seq` on every
56
- * execution — the journal key that lets a resumed run return a memoized result.
57
- *
58
- * It also drives SILENT REPLAY: a resume starts suppressed (observability — console output, phase
59
- * markers — was already emitted last segment) and goes `live` the moment it reaches the suspending
60
- * seam (the frontier = the highest journaled seq). A fresh run (frontier 0) is live immediately.
61
- */
62
- export class SeamSequencer {
63
- replayFrontier;
21
+ /** A monotonic per-run counter for suspension/gate keys (the `seq` on {@link SuspendSignal}). */
22
+ export class SuspensionCounter {
64
23
  count = 0;
65
- liveFlag;
66
- constructor(replayFrontier = 0) {
67
- this.replayFrontier = replayFrontier;
68
- this.liveFlag = replayFrontier === 0;
69
- }
70
24
  next() {
71
- const seq = ++this.count;
72
- if (seq >= this.replayFrontier)
73
- this.liveFlag = true;
74
- return seq;
75
- }
76
- /** True once execution has crossed the replay frontier — output after this point is NEW. */
77
- get isLive() {
78
- return this.liveFlag;
25
+ return ++this.count;
79
26
  }
80
- /** True while re-running already-journaled seams on a resume (observability suppressed). */
81
- isReplaying() {
82
- return !this.liveFlag;
83
- }
84
- }
85
- /** The child run id a `workflow_call` seam journals while it waits (parsed back on resume). */
86
- export const childRunIdSchema = z.string().min(1);
87
- /** A stable content hash of a seam's salient args — the determinism check on replay. Identical
88
- * construction to the engine's `seamFingerprint` so a journal written by one runtime validates in
89
- * the other (the conformance promise). */
90
- export function seamFingerprint(parts) {
91
- return createHash("sha256").update(JSON.stringify(parts)).digest("hex");
92
27
  }
93
- /** A seam reached on replay didn't match what the journal recorded at that seq — the workflow's code
94
- * on the path to a suspend changed (a different prompt/model/step name, or a different seam kind).
95
- * Fails the run loudly rather than returning a stale memoized result for the wrong call. */
96
- export function determinismError(seq, got, recorded) {
97
- const detail = got === recorded
98
- ? `the same "${got}" seam but with different arguments (a changed prompt, model, or step name)`
99
- : `a "${recorded}" call, but this execution reached a "${got}" call`;
100
- return new AppError(ErrorCode.VALIDATION_FAILED, `Nondeterministic replay at seam ${String(seq)}: the journal recorded ${detail}. A workflow's ` +
101
- `code on the path to a suspend/resume must be deterministic — route nondeterministic I/O ` +
102
- `through agent(), step.run(), or workflows.call so it is journaled.`, { kind: "nondeterministic_replay", seq });
103
- }
104
- /**
105
- * The shape of a SUSPENDED agent leaf's journal result on resume: the transcript checkpoint plus the
106
- * answers the broker joined from the resolved request rows, keyed by tool-call id. `messages` is the
107
- * engine's own serialized transcript round-tripping through JSON — handed straight back to the leaf,
108
- * not re-validated field-by-field. Structurally a {@link LeafResume}.
109
- */
110
- export const leafResumeSchema = z.object({
111
- checkpoint: z.object({
112
- messages: z.array(z.custom()),
113
- iteration: z.number().int(),
114
- totals: z.object({
115
- inputTokens: z.number().int(),
116
- outputTokens: z.number().int(),
117
- }),
118
- }),
119
- answers: z.record(z.string(), z.unknown()),
120
- });
@@ -28,7 +28,7 @@ export type ParsedHumanInputSpec = z.infer<typeof humanInputSpecSchema>;
28
28
  export declare function parseHumanInputSpec(raw: unknown): ParsedHumanInputSpec;
29
29
  /** Re-hydrate a stored (jsonb) human-input result into the exact-optional SDK {@link HumanInputResult}
30
30
  * (the host returns it from a resumed `humanInput()` seam). The value was validated on submit, so a
31
- * parse failure here is a corrupt journal — surfaced as a clear program error. */
31
+ * parse failure here is a corrupt stored answer — surfaced as a clear program error. */
32
32
  export declare function normalizeHumanInputResult(raw: unknown): HumanInputResult;
33
33
  /** Convert a validated {@link HumanInputResult} to a {@link JsonValue} for storage (the result is an
34
34
  * interface with optional props, which TS won't structurally accept as JsonValue; an explicit object
@@ -3,7 +3,7 @@
3
3
  // The `HumanInputSpec` is the single source of truth for BOTH the UI form and server-side
4
4
  // validation, so a `humanInput()` gate needs no JSON Schema. A responder's raw submission (from
5
5
  // REST / MCP / CLI / web) is validated here against the gate's stored spec into a typed
6
- // `HumanInputResult`, which becomes the seam's memoized journal value. Pure + exhaustively testable.
6
+ // `HumanInputResult`, the value the seam returns to the program. Pure + exhaustively testable.
7
7
  //
8
8
  // These mirror the SDK's `@boardwalk-labs/workflow` types exactly (the wire contract); we re-derive
9
9
  // them as Zod schemas because the stored `input_spec` is `unknown` jsonb that must be parsed at the
@@ -51,7 +51,7 @@ export function parseHumanInputSpec(raw) {
51
51
  }
52
52
  /** Re-hydrate a stored (jsonb) human-input result into the exact-optional SDK {@link HumanInputResult}
53
53
  * (the host returns it from a resumed `humanInput()` seam). The value was validated on submit, so a
54
- * parse failure here is a corrupt journal — surfaced as a clear program error. */
54
+ * parse failure here is a corrupt stored answer — surfaced as a clear program error. */
55
55
  export function normalizeHumanInputResult(raw) {
56
56
  const parsed = storedResultSchema.safeParse(raw);
57
57
  if (!parsed.success) {
@@ -1,7 +1,7 @@
1
1
  import type { WorkflowHost, AgentOptions, ArtifactBody, ArtifactRef, BrowserSession, BrowserSessionOptions, CallOptions, HumanInputOptions, HumanInputResult, PhaseOptions, SleepArg } from "@boardwalk-labs/workflow/runtime";
2
2
  import type { BrowserSessionManager } from "./browser_session.js";
3
3
  import { type LeafResume } from "@boardwalk-labs/engine/core";
4
- import { type JournalSeam, type SuspendSignal } from "./suspension.js";
4
+ import { type SuspendSignal } from "./suspension.js";
5
5
  import type { FreezeCoordinator } from "./freeze_coordinator.js";
6
6
  /** Parse a `humanInput({ timeout })` string (`"48h"`, `"30m"`, `"90s"`, `"7d"`) to milliseconds, or
7
7
  * null when absent/unparseable (the gate then waits indefinitely). */
@@ -36,10 +36,11 @@ export interface ChildResult {
36
36
  status: string;
37
37
  output: unknown;
38
38
  }
39
- /** Dispatches child runs: `call` holds + returns output (the no-journal path); `start`/`poll` back the
40
- * DURABLE callWorkflow seam (start once, suspend `waiting_for_child` on a non-terminal child, poll on
41
- * resume); `run` is fire-and-forget → run id; `schedule` provisions a durable future/recurring run →
42
- * schedule id. The `signal` lets a hold/start abort promptly when the parent run is cancelled. */
39
+ /** Dispatches child runs: `call` holds in-process + returns the completed child's output (the
40
+ * hold path); `start`/`poll` back the snapshot-substrate callWorkflow seam (start once, freeze
41
+ * `waiting_for_child` on a non-terminal child); `run` is fire-and-forget → run id; `schedule`
42
+ * provisions a durable future/recurring run → schedule id. The `signal` lets a hold/start abort
43
+ * promptly when the parent run is cancelled. */
43
44
  export interface ChildDispatcher {
44
45
  call(slug: string, input: unknown, opts: CallOptions | undefined, signal?: AbortSignal): Promise<unknown>;
45
46
  /** Start (or idempotently re-attach to) a child run; resolves its current state. */
@@ -53,9 +54,10 @@ export interface ChildDispatcher {
53
54
  export interface SecretAccessor {
54
55
  get(name: string): Promise<string>;
55
56
  }
56
- /** Register-without-release: register a HELD HITL gate so it is
57
- * answerable while the run keeps running, and poll for the answer. Backed by the broker's
58
- * `inputs` endpoints. Absent a held gate is only answerable once it freezes. */
57
+ /** Register a HELD HITL gate so it is answerable while the run keeps its process, and poll for
58
+ * the answer. Backed by the broker's `inputs` endpoints. On the snapshot substrate this is the
59
+ * register-without-release half of a freeze; on a no-freeze runtime (a self-hosted daemon) it is
60
+ * the WHOLE mechanism — the seam registers, then holds and polls until answered. */
59
61
  export interface HeldInputPort {
60
62
  register(seq: number, gate: SuspendSignal["humanInput"]): Promise<unknown>;
61
63
  poll(seq: number): Promise<Record<string, unknown>>;
@@ -130,39 +132,21 @@ export interface WorkerWorkflowHostDeps {
130
132
  now?: () => number;
131
133
  /** Override the 7-day hold ceiling (tests). */
132
134
  maxSleepMs?: number;
133
- /**
134
- * Durable-suspension journal (the durable-suspension design): the host memoizes each `agent`/`step`/`sleep`/
135
- * `humanInput` seam here, so a resumed run replays journaled seams instantly instead of re-running
136
- * them. Backed by the broker over the run token on hosted runs. Absent ⇒ no memoization (the
137
- * local/test path): seams run live every time and a suspend can't be resumed.
138
- */
139
- journal?: JournalSeam;
140
- /**
141
- * Raise a durable suspension: the host calls this from a suspending seam, then returns a
142
- * never-resolving promise; the worker races that against the program body and tears the task down
143
- * (the program's own `try/catch` can't swallow a suspend this way). Absent ⇒ a suspending seam
144
- * rejects with {@link SuspendError} instead (the local/test path).
145
- */
146
- onSuspend?: (signal: SuspendSignal) => void;
147
135
  /**
148
136
  * Snapshot-substrate suspension (the microVM freeze model): when present, a suspending seam
149
- * BLOCKS on the coordinator instead of raising `onSuspend` — the platform freezes the whole VM
150
- * and a wake resolves the seam in place, heap intact (no exit, no journal replay). Every hook
151
- * also runs under the coordinator's quiescence gate: a freeze never captures a live platform
152
- * stream, and work arriving while a freeze is pending queues until the wake.
137
+ * BLOCKS on the coordinator — the platform freezes the whole VM and a wake resolves the seam in
138
+ * place, heap intact. Every hook also runs under the coordinator's quiescence gate: a freeze
139
+ * never captures a live platform stream, and work arriving while a freeze is pending queues
140
+ * until the wake. Absent the no-substrate HOLD path: a waiting seam blocks the live process
141
+ * for the whole wait (self-hosted daemons, the Fargate break-glass, unit tests).
153
142
  */
154
143
  freeze?: FreezeCoordinator;
155
- /** Register-without-release for HELD human-input gates. Only
156
- * meaningful alongside `freeze`; absent ⇒ a held gate is answerable only once it freezes. */
144
+ /** Register + poll for HELD human-input gates (see {@link HeldInputPort}). Absent alongside
145
+ * `freeze` ⇒ a held gate is answerable only once it freezes; absent WITHOUT `freeze` ⇒
146
+ * humanInput is unsupported in this runtime and rejects clearly. */
157
147
  heldInput?: HeldInputPort;
158
148
  /** Poll interval for a held gate's answer (default 3s). */
159
149
  heldPollIntervalMs?: number;
160
- /**
161
- * The highest journaled seq at claim (0 on a fresh run). While re-running seams up to this
162
- * frontier on a resume, the host is REPLAYING and observability (phase markers, program logs) is
163
- * suppressed — those lines were emitted in the prior segment.
164
- */
165
- replayFrontier?: number;
166
150
  }
167
151
  export declare class WorkerWorkflowHost implements WorkflowHost {
168
152
  private readonly deps;
@@ -170,18 +154,11 @@ export declare class WorkerWorkflowHost implements WorkflowHost {
170
154
  private readonly now;
171
155
  private readonly maxSleepMs;
172
156
  private readonly heldPollIntervalMs;
173
- /** Synchronous durable-seam counter + silent-replay live flag (the durable-suspension design). */
157
+ /** Monotonic per-run counter keying suspensions + their HITL gate rows. */
174
158
  private readonly seq;
175
159
  /** Run context + on-demand public-API bearer the SDK `runtime` accessor reads off the host. */
176
160
  readonly runtime: RuntimeContext;
177
161
  constructor(deps: WorkerWorkflowHostDeps);
178
- /** True while re-running already-journaled seams on a resume (the worker suppresses program-log +
179
- * phase observability during this window — those lines were emitted in the prior segment). */
180
- isReplaying(): boolean;
181
- /** Raise a durable suspension. With `onSuspend` wired (the real worker) the host signals out-of-band
182
- * and returns a never-resolving promise, so the program's own try/catch can't swallow it; without
183
- * it (the local/test path) it rejects with {@link SuspendError}. */
184
- private suspend;
185
162
  /** Run `fn` only if the run isn't aborted; otherwise REJECT (never throw synchronously) with the
186
163
  * signal's RunAbortedError — every Promise-returning hook funnels through this so callers always
187
164
  * get a rejected promise on abort, not a sync throw. On the snapshot substrate the body also runs
@@ -207,6 +184,13 @@ export declare class WorkerWorkflowHost implements WorkflowHost {
207
184
  * the answer value; never rejects the run (a transient poll error just retries next tick). Runs
208
185
  * OUTSIDE the quiescence gate (it is not run work — it must not block a freeze). */
209
186
  private pollHeldAnswer;
187
+ /**
188
+ * The no-freeze HOLD for a human-input gate: register it with the broker (it surfaces in the
189
+ * inbox/API at once), then poll until the answer arrives — the process stays alive and pays
190
+ * for the wait. Rejects promptly when the run's abort signal fires (cancel / credit stop),
191
+ * which is also how a server-side gate expiry that fails the run unwinds this loop.
192
+ */
193
+ private holdForAnswer;
210
194
  /** Map a froze/aborted freeze outcome to the gate's answer (or throw on an unexpected abort). */
211
195
  private resolveFreezeAnswer;
212
196
  /** The wake's answer for one gate key. A wake whose value is missing the parked gate means the
@@ -215,26 +199,23 @@ export declare class WorkerWorkflowHost implements WorkflowHost {
215
199
  private gateAnswer;
216
200
  setPhase(name: string, opts: PhaseOptions | undefined): void;
217
201
  agent(prompt: string, opts: AgentOptions | undefined): Promise<unknown>;
218
- /** The `agent()` durable seam: a journal hit returns the memoized result (never re-runs the LLM); a
219
- * `suspended` hit resumes a parked leaf from its checkpoint + answers; a miss runs the leaf and
220
- * journals the result. A leaf that PARKS (the model called `human_input`) suspends the run. */
202
+ /** The `agent()` seam: run the leaf to completion. A leaf that PARKS (the model called the
203
+ * `human_input` tool) waits for the answer a freeze on the snapshot substrate, a held poll
204
+ * otherwise and re-enters from its in-memory checkpoint. Nothing is memoized: a crash-restart
205
+ * re-runs the leaf (restart-from-top semantics). */
221
206
  private agentSeam;
222
- /** Program-level `humanInput()`: suspend the run on a gate, resume with the validated answer. The
223
- * SDK marks this optional; the hosted host always implements it. */
207
+ /** Program-level `humanInput()`: wait on a gate a freeze on the snapshot substrate, a held
208
+ * poll otherwise — and return the validated answer. The SDK marks this optional; the hosted
209
+ * host always implements it. */
224
210
  humanInput(opts: HumanInputOptions): Promise<HumanInputResult>;
225
211
  private humanInputSeam;
226
212
  /** Absolute wake time for a `humanInput({ timeout })`, or null when there is none / it's unparseable. */
227
213
  private timeoutExpiry;
228
- /** `step.run(name, fn)`: run `fn` exactly once across restarts, memoizing its result in the journal
229
- * (the escape hatch for nondeterministic work on a suspend/resume path). */
230
- step(name: string, fn: () => unknown): Promise<unknown>;
231
- private stepSeam;
232
214
  callWorkflow(slug: string, input: unknown, opts: CallOptions | undefined): Promise<unknown>;
233
- /** The `workflows.call` durable seam (the durable-suspension design): start the child once + memoize its
234
- * output; a non-terminal child SUSPENDS the parent (`waiting_for_child`, the child's id journaled)
235
- * the parent releases its task and is woken when the child finalizes. On resume the seam polls
236
- * the journaled child and returns its output (or throws on a failed child). Without a journal (the
237
- * local/test path) it falls back to the in-process hold-and-poll. */
215
+ /** The `workflows.call` seam: start the child once (idempotently the child's run row is the
216
+ * durable memo, so a crash-restarted parent re-attaches instead of re-spawning). A non-terminal
217
+ * child suspends the parent `waiting_for_child` on the snapshot substrate (the wake carries the
218
+ * finalized child, heap intact); without one the parent HOLDS in-process and polls. */
238
219
  private callWorkflowSeam;
239
220
  /** Fire-and-forget trigger of another workflow; resolves to the new run's id (no hold/poll). */
240
221
  runWorkflow(slug: string, input: unknown, opts: CallOptions | undefined): Promise<string>;
@@ -245,7 +226,7 @@ export declare class WorkerWorkflowHost implements WorkflowHost {
245
226
  writeArtifact(name: string, contentType: string, body: ArtifactBody, metadata: Record<string, unknown> | undefined): Promise<ArtifactRef>;
246
227
  /** `computer.openBrowser()`: open a program-owned, in-VM browser session (the browser tier of
247
228
  * computer use). Not a durable seam — a session is a live resource, reaped at run end, never
248
- * journaled/replayed. Absent backend ⇒ a clear "not available" error. */
229
+ * persisted. Absent backend ⇒ a clear "not available" error. */
249
230
  openBrowserSession(opts: BrowserSessionOptions | undefined): Promise<BrowserSession>;
250
231
  /** Translate `agent({ session })` into the leaf's `mcp`: append the browser session's in-VM
251
232
  * Playwright MCP (its http ref, which passes assertHostedMcpAllowed and reaches localhost without
@@ -253,9 +234,10 @@ export declare class WorkerWorkflowHost implements WorkflowHost {
253
234
  * no session is bound. */
254
235
  private bindBrowserSession;
255
236
  sleep(arg: SleepArg): Promise<void>;
256
- /** A short sleep HOLDS the task in-process (cheaper than a release + replay round-trip); a long one
257
- * (≥ {@link SUSPEND_THRESHOLD_MS}) SUSPENDS releases the task, and a timer re-dispatches the run
258
- * when due. Journaled so a resumed run replays past an already-elapsed sleep instantly. */
237
+ /** A short sleep HOLDS the task in-process (cheaper than a snapshot round-trip); a long one
238
+ * (≥ {@link SUSPEND_THRESHOLD_MS}) SUSPENDS on the snapshot substrate the VM freezes and the
239
+ * wake resolves this very await, heap intact. Without a freeze substrate EVERY sleep holds,
240
+ * whatever its length (the no-substrate rule: snapshot or hold, never replay). */
259
241
  private sleepSeam;
260
242
  /** Resolve any {@link SleepArg} shape to a millisecond duration from now. */
261
243
  private resolveSleepMs;