@boardwalk-labs/runner 0.2.17 → 0.3.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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/dist/runtime/agent/budget.d.ts +43 -24
  3. package/dist/runtime/agent/budget.js +108 -57
  4. package/dist/runtime/agent/events.d.ts +1 -1
  5. package/dist/runtime/broker_child_dispatcher.d.ts +5 -2
  6. package/dist/runtime/broker_child_dispatcher.js +24 -6
  7. package/dist/runtime/budget_gate.d.ts +58 -21
  8. package/dist/runtime/budget_gate.js +227 -46
  9. package/dist/runtime/host_capabilities.d.ts +4 -0
  10. package/dist/runtime/host_capabilities.js +25 -0
  11. package/dist/runtime/host_server.d.ts +137 -0
  12. package/dist/runtime/host_server.js +562 -0
  13. package/dist/runtime/index.js +45 -12
  14. package/dist/runtime/leaf_executor.d.ts +7 -4
  15. package/dist/runtime/leaf_executor.js +11 -6
  16. package/dist/runtime/program_runner.d.ts +62 -42
  17. package/dist/runtime/program_runner.js +156 -101
  18. package/dist/runtime/program_worker.d.ts +22 -11
  19. package/dist/runtime/program_worker.js +26 -48
  20. package/dist/runtime/python_program.d.ts +68 -0
  21. package/dist/runtime/python_program.js +270 -0
  22. package/dist/runtime/run_context.d.ts +13 -0
  23. package/dist/runtime/run_context.js +77 -0
  24. package/dist/runtime/runner_control_client.d.ts +23 -0
  25. package/dist/runtime/runner_control_client.js +69 -147
  26. package/dist/runtime/shell_exec.d.ts +17 -0
  27. package/dist/runtime/shell_exec.js +144 -0
  28. package/dist/runtime/support/index.d.ts +6 -0
  29. package/dist/runtime/support/index.js +14 -0
  30. package/dist/runtime/wire/inference_proxy.js +1 -1
  31. package/dist/runtime/wire/run.d.ts +5 -2
  32. package/dist/runtime/workflow_host.d.ts +53 -7
  33. package/dist/runtime/workflow_host.js +82 -42
  34. package/package.json +4 -3
@@ -34,13 +34,15 @@ import { BUDGET_GUARDRAIL_RATE } from "./agent/model_rates.js";
34
34
  import { SecretRedactor } from "./agent/secret_redactor.js";
35
35
  import { RecordingSecretResolver } from "./recording_secret_resolver.js";
36
36
  import { EngineLeafExecutor } from "./leaf_executor.js";
37
- import { BudgetGate } from "./budget_gate.js";
37
+ import { BudgetGate, ComputeBreachWatcher } from "./budget_gate.js";
38
38
  import { parseByoProviders } from "./direct_inference.js";
39
39
  import { applyIdentityToEnv, connectIdentityRelayFd, relayFdFromEnv, workerDiagnostics, } from "./identity_relay.js";
40
40
  import { FreezeCoordinator } from "./freeze_coordinator.js";
41
41
  import { reseedUserspaceCsprng } from "./uniqueness_reseed.js";
42
42
  import { resetHttpConnectionPool } from "./http_pool_reset.js";
43
43
  import { WorkerWorkflowHost } from "./workflow_host.js";
44
+ import { buildHostCapabilities } from "./host_capabilities.js";
45
+ import { runShell } from "./shell_exec.js";
44
46
  import { BrowserSessionManager } from "./browser_session.js";
45
47
  import { loadGuestBrowserConfig, makeGuestBrowserBackend, connectSessionMcp, } from "./browser_session_backend.js";
46
48
  import { ScreenCapture } from "./screen_capture.js";
@@ -165,10 +167,6 @@ export function assembleWorkerDeps(runtime) {
165
167
  // the bill — the platform meters the actual per-leaf usage.
166
168
  rate: BUDGET_GUARDRAIL_RATE,
167
169
  startedAt: Date.now(),
168
- // deadline_seconds is WALL-CLOCK from the run's ORIGINAL start (incl. suspended idle), so a run
169
- // resumed past its deadline trips on its first cap check. Falls back to now on the first session
170
- // (startedAt is being set this session anyway).
171
- deadlineStartedAt: run.startedAt ?? Date.now(),
172
170
  });
173
171
  // Per-session id for per-turn metering identifiers (`<runId>:<sessionId>:<leafIndex>:<turnSeq>`) —
174
172
  // a fresh value per worker session keeps a resumed run's events from colliding with a prior
@@ -239,10 +237,11 @@ export function assembleWorkerDeps(runtime) {
239
237
  // it). Workspace-rooted at the same `/workspace` the leaf + tools use. Closed on the run's teardown
240
238
  // (returned below as `lsp` → program_worker's finally) so no language-server process leaks.
241
239
  const lspService = new LspService({ workspaceDir: runtime.workspaceRoot });
242
- // Budget gate (docs/SUSPEND_POLICY.md Decision 3): a `max_usd` breach PARKS the run for approval
243
- // instead of failing it. The gate needs the HOST (to park) but the host is constructed below with
244
- // `leaf` a genuine cycle, broken with a late-bound ref: `clear()` only ever runs mid-run, long
245
- // after both exist. Wired to `budgetHost` immediately after the host is built.
240
+ // Budget gate (docs/SUSPEND_POLICY.md Decision 3): a breach of ANY budget cap (`max_usd` /
241
+ // `max_tokens` / `max_compute_seconds`) PARKS the run for approval instead of failing it. The
242
+ // gate needs the HOST (to park) but the host is constructed below with `leaf` a genuine
243
+ // cycle, broken with a late-bound ref: `clear()` only ever runs mid-run, long after both exist.
244
+ // Wired to `budgetHost` immediately after the host is built.
246
245
  let budgetHost = null;
247
246
  const budgetGate = new BudgetGate(budget, {
248
247
  budgetClearance: (gate) => {
@@ -253,6 +252,13 @@ export function assembleWorkerDeps(runtime) {
253
252
  return budgetHost.budgetClearance(gate);
254
253
  },
255
254
  });
255
+ // Compute burns continuously (unlike usd/tokens, which only move at model calls), so a
256
+ // `max_compute_seconds` breach between seams is DETECTED on this timer and logged; the park
257
+ // itself lands at the run's next capability seam (see the watcher's doc block for why a
258
+ // timer-initiated park needs fleet-host coordination that doesn't exist yet). Stopped by the
259
+ // orchestrator on every terminal path via the returned `budgetWatch` handle.
260
+ const computeWatch = new ComputeBreachWatcher(budget, { runId: run.id });
261
+ computeWatch.start();
256
262
  const leaf = new EngineLeafExecutor({
257
263
  inference: broker,
258
264
  // Direct BYO (D7): the claim's registry + the run's RECORDING resolver, so a provider key
@@ -404,9 +410,20 @@ export function assembleWorkerDeps(runtime) {
404
410
  // (absent ⇒ the host throws "not available on this runner image").
405
411
  ...(browserSessions !== undefined ? { browserSessions } : {}),
406
412
  phases: phaseTracker,
413
+ // The protocol's `shell` capability: host-side execution in the run's workspace (the old
414
+ // in-process execSync moved behind the wire with the redesign).
415
+ shell: (cmd, opts) => runShell(cmd, opts, { workspaceRoot: runtime.workspaceRoot, signal }),
416
+ // Live budget state for `usage.get` — read off the run-level meter (run-cumulative).
417
+ usage: () => budget.usageSnapshot(),
418
+ // Budget clearance at the host's blocking/spending seams (sleep/shell/workflows.call): the
419
+ // park points for a compute breach that lands between model calls.
420
+ budgetGate,
407
421
  });
408
422
  // Close the budget gate's cycle: the leaf (built above) parks THROUGH the host (built just now).
409
423
  budgetHost = host;
424
+ // The protocol server's dispatch seam — a thin adapter over the host (workflows.call gains
425
+ // its output_schema slot, null until the broker delivers the callee's schema).
426
+ const capabilities = buildHostCapabilities(host);
410
427
  // Late-bind the coordinator's per-run hooks now that the run-scoped objects exist.
411
428
  if (freeze !== undefined) {
412
429
  const coordinator = freeze;
@@ -452,6 +469,11 @@ export function assembleWorkerDeps(runtime) {
452
469
  redactor.record(wake.api_token);
453
470
  }
454
471
  activeFlusher?.excludeIdle(wake.wall_clock_ms - freezeWallMs);
472
+ // The budget meter excludes the same frozen window: `max_compute_seconds` is ACTIVE
473
+ // on-CPU compute by definition (it "EXCLUDES sleep/wait" — the manifest schema's own
474
+ // words), so a sleep/humanInput/child-wait freeze must not count toward the cap any
475
+ // more than it counts toward billing.
476
+ budget.excludeIdle(wake.wall_clock_ms - freezeWallMs);
455
477
  // Only now that the frozen window is excluded may the flush timer run again.
456
478
  activeFlusher?.resume();
457
479
  // Resume capture in a fresh segment epoch (a suspend/resume boundary is a segment boundary).
@@ -462,7 +484,7 @@ export function assembleWorkerDeps(runtime) {
462
484
  // Hand the redactor back too: the worker scrubs a terminal error's message with it;
463
485
  // workspace (when opted in) hydrates at start + persists at terminal.
464
486
  return Promise.resolve({
465
- host,
487
+ capabilities,
466
488
  redactor,
467
489
  phases: phaseTracker,
468
490
  // The orchestrator closes the per-run LSP on every terminal path (close() is idempotent + never
@@ -479,6 +501,9 @@ export function assembleWorkerDeps(runtime) {
479
501
  setProgramDir: (dir) => {
480
502
  programDir = dir;
481
503
  },
504
+ // The orchestrator stops the compute-breach watcher on every terminal path (the interval is
505
+ // unref'd, so this is hygiene, not liveness).
506
+ budgetWatch: { stop: () => computeWatch.stop() },
482
507
  // Always present now: hydrate must run BEFORE the program, but whether anything compounds
483
508
  // isn't known until the run's agent() calls have registered their memory dirs. A run that
484
509
  // selects nothing persists nothing and pays for nothing (WorkspaceStore.persist returns early).
@@ -508,7 +533,15 @@ export function assembleWorkerDeps(runtime) {
508
533
  // lifecycle transition the claim IS — the wire's `running` frame.
509
534
  runEvents.resumeAfter(claimed.lastEventCursor);
510
535
  runEvents.emit({ kind: "run_status", status: "running" });
511
- return claimed.run;
536
+ return {
537
+ run: claimed.run,
538
+ context: {
539
+ ...(claimed.workflowVersion !== undefined
540
+ ? { workflowVersion: claimed.workflowVersion }
541
+ : {}),
542
+ ...(claimed.environment !== undefined ? { environment: claimed.environment } : {}),
543
+ },
544
+ };
512
545
  },
513
546
  },
514
547
  versions: {
@@ -528,7 +561,7 @@ export function assembleWorkerDeps(runtime) {
528
561
  // Extract the verified artifact tarball via system `tar` (same impl as workspace snapshots).
529
562
  extractArchive: (tgzPath, destDir) => new TarWorkspaceArchiver().extract(tgzPath, destDir),
530
563
  // Guarantee /workspace exists before the program runs (override-safe; the image also pre-creates
531
- // it). Lets authors write to /workspace without a defensive mkdir — see WorkflowMeta.workspace.
564
+ // it). Lets authors write to /workspace without a defensive mkdir — see the descriptor's `workspace` field.
532
565
  ensureWorkspace: async () => {
533
566
  await mkdir(runtime.workspaceRoot, { recursive: true });
534
567
  },
@@ -31,14 +31,17 @@ export interface EngineLeafExecutorDeps {
31
31
  * breach so the loop terminates mid-flight (the budget authority). */
32
32
  budget: BudgetMeter;
33
33
  /**
34
- * Budget clearance, awaited before EVERY model call (docs/SUSPEND_POLICY.md Decision 3). When the
35
- * run's `max_usd` cap is breached this PARKS the run at a gate — the engine just sees a model call
36
- * that took a long time, because the VM froze and resumed underneath it and resolves once a
37
- * responder raises the cap. Rejects with `BudgetGateCancelled` if they decline.
34
+ * Budget clearance, awaited before EVERY model call (docs/SUSPEND_POLICY.md Decision 3). When ANY
35
+ * of the run's budget caps (`max_usd` / `max_tokens` / `max_compute_seconds`) is breached this
36
+ * PARKS the run at a gate the engine just sees a model call that took a long time, because the
37
+ * VM froze and resumed underneath it and resolves once a responder raises the breached cap.
38
+ * Rejects with `BudgetGateCancelled` if they decline.
38
39
  *
39
40
  * Why here and not at the `reportUsage` breach check below: that callback is synchronous and fires
40
41
  * mid-turn, and per SUSPEND_POLICY Decision 1 a partial turn is never discarded. The model-call
41
42
  * boundary is the first safe place to park, which bounds overrun at one in-flight turn per leaf.
43
+ * (The workflow host adds further park points at its `sleep`/`shell`/`workflows.call` seams for
44
+ * the continuously-burning compute cap.)
42
45
  *
43
46
  * Absent ⇒ the legacy behavior: a breach fails the run at `reportUsage`.
44
47
  */
@@ -3,8 +3,8 @@
3
3
  // In the JS-body model the run is the workflow PROGRAM; the agent loop is no longer "the run" — it
4
4
  // is an ephemeral leaf the program invokes via `agent(prompt, opts)`. This is the LeafExecutor the
5
5
  // worker's WorkflowHost delegates `agent()` to. It runs ONE leaf to completion via the engine's
6
- // `runAgentLeaf` (`@boardwalk-labs/engine/core`) — the SAME loop `boardwalk dev` and the self-hosted
7
- // server run — supplying a broker-backed `LeafIo`: the model call routes through the Runner Control
6
+ // `runAgentLeaf` (`@boardwalk-labs/engine/core`) — the SAME loop the self-hosted
7
+ // server runs — supplying a broker-backed `LeafIo`: the model call routes through the Runner Control
8
8
  // API (the worker holds no model creds), events flow onto the run's v1 event stream, usage meters
9
9
  // per-leaf + per-model through the broker, and secrets are redacted out of all model-bound content.
10
10
  //
@@ -138,9 +138,14 @@ export class EngineLeafExecutor {
138
138
  currentTurnId = turnId;
139
139
  sink.emit(toRunEventBody(body, identity), turnId);
140
140
  },
141
- // Usage flows to the budget authority after EVERY model call. Feed the run-level meter and, if
142
- // a cap is now breached, THROW the engine loop propagates it and the run fails BUDGET_EXCEEDED
143
- // before another model call is dispatched. Also fire per-leaf, per-model metering to the broker.
141
+ // Usage flows to the budget authority after EVERY model call. Feed the run-level meter; what a
142
+ // breach then does depends on whether the budget gate is wired. With a gate (production): do
143
+ // NOT throw the in-flight turn completes (its tools run; SUSPEND_POLICY Decision 1 never
144
+ // discards a partial turn) and the run PARKS at its next park point (the next `streamModel`
145
+ // clearance, or a host capability seam). Without a gate (legacy/test paths): THROW — the
146
+ // engine loop propagates it and the run fails BUDGET_EXCEEDED before another model call is
147
+ // dispatched. On the gate path the breach turn ALSO meters to the broker below — its tokens
148
+ // were genuinely spent and must be billed (the old always-throw path silently skipped that).
144
149
  reportUsage: (modelRefForUsage, usage) => {
145
150
  const delta = toUsageDelta(usage);
146
151
  // Cap on the turn's REAL upstream cost when the broker reported one (the result frame's
@@ -151,7 +156,7 @@ export class EngineLeafExecutor {
151
156
  pendingCostMicros = null;
152
157
  this.deps.budget.addUsage(delta, realCostUsd);
153
158
  const breach = this.deps.budget.capBreachReason();
154
- if (breach !== null) {
159
+ if (breach !== null && this.deps.budgetGate === undefined) {
155
160
  throw new EngineError("BUDGET_EXCEEDED", `agent() leaf exceeded the run budget cap (${breach})`);
156
161
  }
157
162
  this.deps.meterUsage?.({
@@ -1,14 +1,12 @@
1
- import type { WorkflowHost } from "@boardwalk-labs/workflow/runtime";
1
+ import { type ContextData } from "@boardwalk-labs/workflow/runtime";
2
+ import { type HostCapabilities } from "./host_server.js";
2
3
  /**
3
4
  * Link THIS runtime's `@boardwalk-labs/workflow` into the exec dir's `node_modules` so the program's
4
5
  * bare import resolves from ANY program root — this link, not an ancestor `node_modules`, is what
5
- * makes resolution work. (Worth stating plainly: a stale comment claiming the extraction dir had to
6
- * sit under `/app` so the import could walk up to `/app/node_modules` outlived this function by
7
- * several versions and sent a later reader chasing a dependency that no longer exists. The live
8
- * fleet has no `/app` at all and resolves fine.) A symlink — not a copy is load-bearing: Node
9
- * resolves it to the REAL path, so the program gets the same module instance the host adapter was
10
- * installed on (the singleton contract). `junction` covers Windows without elevation; a failed link
11
- * is only logged, since a resolvable ancestor may still exist.
6
+ * makes resolution work. A symlink not a copy is load-bearing: Node resolves it to the REAL
7
+ * path, so the program gets the same module instance the loader's protocol client was installed
8
+ * on (the active-host singleton in the SDK's host_client). `junction` covers Windows without
9
+ * elevation; a failed link is only logged, since a resolvable ancestor may still exist.
12
10
  */
13
11
  export declare function ensureSdkLink(execDir: string): Promise<void>;
14
12
  /** Resolve the program's entry module inside the extraction dir, refusing any path that escapes it.
@@ -17,10 +15,18 @@ export declare function ensureSdkLink(execDir: string): Promise<void>;
17
15
  * outside `dir` throws rather than importing code from elsewhere on the machine. */
18
16
  export declare function resolveEntryPath(dir: string, entry: string): string;
19
17
  /**
20
- * Enforce I2: the extracted program must not live inside the run's workspace. Incidental separation
21
- * is what broke before — the extraction root used to be `process.cwd()`, so it landed inside the
22
- * workspace on exactly the lanes whose cwd was already correct. Compares RESOLVED paths, and treats
23
- * "the workspace itself" as inside.
18
+ * Resolve a program entry by LANGUAGE, binding the ratified artifact layout: a TS/JS entry is a
19
+ * BUILT module at the artifact root (`index.mjs` the CLI bundles, the api-server type-strips);
20
+ * a `.py` entry is a SOURCE path under `.bw-src/` (`main.py` `<dir>/.bw-src/main.py` Python
21
+ * has no bundle step, the shipped source is what runs). A leading `./` on the stored entry is
22
+ * tolerated (`join` collapses it). Both lanes keep the containment guard; for Python the guard's
23
+ * base is the `.bw-src` tree itself, so an escaping entry (`../evil.py`) is rejected even when
24
+ * it would still land inside the extract dir.
25
+ */
26
+ export declare function resolveProgramEntryPath(dir: string, entry: string): string;
27
+ /**
28
+ * Enforce I2: the extracted program must not live inside the run's workspace. Compares RESOLVED
29
+ * paths, and treats "the workspace itself" as inside.
24
30
  */
25
31
  export declare function assertProgramRootOutsideWorkspace(programRoot: string, workspaceRoot: string): void;
26
32
  export interface RunProgramArgs {
@@ -28,17 +34,24 @@ export interface RunProgramArgs {
28
34
  runId: string;
29
35
  /** The VERIFIED program artifact tarball (sha256 already checked against the pinned digest). */
30
36
  tarball: Uint8Array;
31
- /** Entry module to import after extraction (a safe relative POSIX path, e.g. `index.mjs`). */
37
+ /** Entry module to import after extraction (a safe relative POSIX path). TS/JS: the BUILT
38
+ * module at the artifact root (`index.mjs`). Python: the SOURCE path relative to the
39
+ * artifact's `.bw-src/` tree (`main.py` ⇒ `.bw-src/main.py` on disk). */
32
40
  entry: string;
33
- /** Trigger payload exposed to the program as `import { input } from "@boardwalk-labs/workflow"`. */
41
+ /** The run's RAW JSON input (trigger payload / inline input). The revival pass is CLIENT-side. */
34
42
  input: unknown;
35
- /** Experiment config exposed as `import { config } from "@boardwalk-labs/workflow"` ({} for non-eval).
36
- * Arbitrary JSON from the run row; narrowed to the SDK's JsonValue at the installConfig boundary. */
37
- config: Record<string, unknown>;
43
+ /** The stored derived input schema (`null` for an untyped workflow) carried on `bootstrap`
44
+ * so the SDK revives rich fields (`date-time` `Date`, base64 `Uint8Array`, …). */
45
+ inputSchema: Record<string, unknown> | null;
46
+ /** The stored derived output schema (`null` ⇒ the return persists unvalidated). */
47
+ outputSchema: Record<string, unknown> | null;
48
+ /** The context DATA for `bootstrap` (P3.3) — the client builds the live `Context` from it. */
49
+ context: ContextData;
38
50
  }
39
51
  export interface ProgramRunnerDeps {
40
- /** The host adapter the program's hooks delegate to (agent leaf, sleep hold, child calls, secrets). */
41
- host: WorkflowHost;
52
+ /** The capability seam the host server dispatches onto (agent leaf, sleep hold, child calls,
53
+ * secrets, shell, usage, auth, browser, phase). */
54
+ capabilities: HostCapabilities;
42
55
  /**
43
56
  * The run's `/workspace` — the working directory AND `HOME` for author code (I1). Must already
44
57
  * exist (the orchestrator's `ensureWorkspace` guarantees it); a missing workspace fails the run
@@ -46,42 +59,49 @@ export interface ProgramRunnerDeps {
46
59
  */
47
60
  workspaceRoot: string;
48
61
  /**
49
- * Root the program artifact extracts under. MUST be outside {@link workspaceRoot} (I2) — enforced,
50
- * because a bundle inside the workspace is tarred into every pre-sleep snapshot and, since each
51
- * run's dir name is unique, accumulates there forever.
62
+ * Root the program artifact extracts under. MUST be outside {@link workspaceRoot} (I2) —
63
+ * enforced, because a bundle inside the workspace is tarred into every pre-sleep snapshot.
52
64
  */
53
65
  programRoot: string;
54
66
  /**
55
67
  * Extract a gzipped tar file into a directory (created already). System `tar` in production
56
- * (matches WorkspaceArchiver); injected in tests. The artifact's relative layout is preserved so
57
- * on-disk assets (markdown skills, templates) sit where the program expects them.
68
+ * (matches WorkspaceArchiver); injected in tests.
58
69
  */
59
70
  extract: (tgzPath: string, destDir: string) => Promise<void>;
60
71
  /**
61
- * Called once with the extracted program directory, right after the artifact is unpacked (before the
62
- * program body runs). The worker uses it to point the `agent()` leaf at the run's bundled files
63
- * (`<dir>/skills/<name>.md`). Optional — the local/test path may omit it.
72
+ * Called once with the extracted program directory, right after the artifact is unpacked
73
+ * (before `run()` is invoked). The worker uses it to point the `agent()` leaf at the run's
74
+ * bundled files (`<dir>/skills/<name>.md`).
64
75
  */
65
76
  onExtracted?: (programDir: string) => void;
66
77
  /**
67
- * Scrubs known secret values out of a string (the run's `SecretRedactor.redactText`). Applied to a
68
- * top-level throw's message before it is logged AND before it is returned to the worker — a program
69
- * that resolves a secret and then throws it in an error message must NOT land that secret raw in the
70
- * logs or the finalized run output. Defaults to identity (tests/local).
78
+ * Scrubs known secret values out of a string (the run's `SecretRedactor.redactText`). Applied
79
+ * to a thrown error's message/code/hint before logging + finalize. Defaults to identity.
71
80
  */
72
81
  redactText?: (text: string) => string;
73
82
  /**
74
- * Called once with the program's declared output IFF the program called `output(value)` (so an
75
- * explicit `output(null)` still fires, but a program that never declared one does NOT). The worker
76
- * wires this to emit an `output` activity entry into the run's event log. Best-effort: it must not
77
- * throw (a telemetry hiccup can't change the run's result). Absent ⇒ no output entry.
83
+ * Called once with the run's reported return IFF it is non-null (a void return sends null,
84
+ * which is not an author-declared output). The worker wires this to emit an `output` activity
85
+ * entry into the run's event log. Best-effort: it must not throw.
78
86
  */
79
87
  onOutput?: (value: unknown) => void;
88
+ /**
89
+ * The run's cooperative-cancellation signal. The host server pushes the `cancel` notification
90
+ * to the program when it fires (the SDK aborts `context.signal`); the capability layer already
91
+ * honors it server-side at every hook.
92
+ */
93
+ signal?: AbortSignal | undefined;
94
+ /** Override the socket directory (tests). Default `os.tmpdir()` (short paths — sun_path cap). */
95
+ sockDir?: string | undefined;
96
+ /** Interpreter a `.py` entry is launched with (P5.5). Default `python3`, resolved on PATH —
97
+ * the base image bakes one CPython (P5.3). May be an absolute path. */
98
+ pythonInterpreter?: string | undefined;
99
+ /** How long after SIGTERM an aborted Python child gets before SIGKILL. Default 5s. */
100
+ pythonKillGraceMs?: number | undefined;
80
101
  }
81
- /** Terminal result of running a workflow program. `output` is what the program declared via
82
- * `output(value)` (null when it never did); for a failure it's null and `error` is set. A waiting
83
- * seam (sleep / humanInput / workflows.call) never surfaces here — it freezes with the VM or
84
- * holds the process, and the body simply continues when the wait is over. */
102
+ /** Terminal result of running a workflow program. `output` is the validated value `run()`
103
+ * returned (`null` for void); for a failure it's null and `error` is set. A waiting seam never
104
+ * surfaces here — it freezes with the VM or holds the process, and `run()` simply continues. */
85
105
  export type ProgramResult = {
86
106
  kind: "completed";
87
107
  output: unknown;
@@ -95,8 +115,8 @@ export type ProgramResult = {
95
115
  };
96
116
  };
97
117
  /**
98
- * Run a workflow program to completion. Installs the host + input, extracts the VERIFIED artifact +
99
- * dynamic-imports its entry (which runs the body), and returns the terminal result. Always tears the
100
- * runtime state down and removes the temp tree afterward.
118
+ * Run a workflow program to completion: start the host-protocol server, extract the VERIFIED
119
+ * artifact, drive the loader (`bootstrap` import entry `run(input, context)`
120
+ * `reportReturn`), and return the terminal result. Always tears the server + temp tree down.
101
121
  */
102
122
  export declare function runWorkflowProgram(args: RunProgramArgs, deps: ProgramRunnerDeps): Promise<ProgramResult>;