| 1 | //! Dynamic Workflow runtime for CodeWhale. |
| 2 | //! |
| 3 | //! This crate is the imperative half of Workflow: a sandboxed QuickJS |
| 4 | //! (rquickjs) runtime that executes a model-authored JS program which |
| 5 | //! dispatches fleet-routed subagents via `task()`, fans out with |
| 6 | //! `parallel()`/`pipeline()`, reports progress with `log()`/`phase()`, and |
| 7 | //! scales itself to a token pool via the `budget` global. The static, |
| 8 | //! declarative IR (record/replay, model policy) stays in `codewhale-workflow`; |
| 9 | //! this crate only speaks to the outside world through the |
| 10 | //! [`WorkflowDriver`] seam, so it is fully testable without spawning a real |
| 11 | //! subagent (see [`testing::FakeDriver`]). |
| 12 | //! |
| 13 | //! # Script surface |
| 14 | //! |
| 15 | //! Every script runs inside an async function with these globals: |
| 16 | //! |
| 17 | //! * `args` — the invocation input, verbatim. |
| 18 | //! * `await task(opts)` — dispatch one subagent; resolves to the full result |
| 19 | //! text, or to a parsed + schema-validated object when `opts.responseSchema` |
| 20 | //! is set. Throws on rejection, failure, cancellation, budget exhaustion, |
| 21 | //! or once [`WORKFLOW_LIFETIME_CAP`] spawn attempts have been made. |
| 22 | //! * `parallel(thunks)` — all-settled fan-out; an ordinary failed slot becomes |
| 23 | //! `null`; schema-contract failures and run cancellation still fail the run; |
| 24 | //! at most [`PARALLEL_MAX_ITEMS`] items. |
| 25 | //! * `pipeline(items, ...stages)` — per-item stage chains with no barrier |
| 26 | //! between stages; an ordinary stage error drops that item to `null`, while |
| 27 | //! schema-contract failures and cancellation still fail the run; same cap. |
| 28 | //! * `log(msg)` / `phase(title)` — progress events forwarded to the driver. |
| 29 | //! * `budget.total` / `budget.spent()` / `budget.remaining()` — live driver |
| 30 | //! snapshots (`total` is `null` and `remaining()` is `Infinity` when no |
| 31 | //! ceiling is configured). |
| 32 | //! |
| 33 | //! `Date.now()`, `new Date()`, `Date.parse/UTC`, and `Math.random()` throw: |
| 34 | //! runs must be deterministic so recorded traces can be replayed. |
| 35 | //! |
| 36 | //! # Ownership boundaries |
| 37 | //! |
| 38 | //! Token accounting and admission belong to the driver; the VM only reads |
| 39 | //! snapshots and fast-fails a spawn when the shared pool is already exhausted. |
| 40 | //! Already-running parallel children can reconcile above the hint because |
| 41 | //! provider usage arrives at response boundaries, not token-by-token. Fleet |
| 42 | //! roster resolution for `profile` also happens driver-side; this crate |
| 43 | //! normalizes and token-validates the profile string, nothing more. |
| 44 | |
| 45 | mod driver; |
| 46 | mod error; |
| 47 | mod schema; |
| 48 | pub mod testing; |
| 49 | mod vm; |
| 50 | |
| 51 | pub use driver::{ |
| 52 | BudgetSnapshot, ProgressEvent, SpawnedTask, TaskCompletion, TaskRequest, WorkflowDriver, |
| 53 | normalize_profile, |
| 54 | }; |
| 55 | pub use error::{DriverError, WorkflowJsError}; |
| 56 | pub use vm::{VmLimits, WorkflowRunCancel, WorkflowVm}; |
| 57 | |
| 58 | /// Maximum `task()` spawn attempts per run (design §4.3). Counted in the VM |
| 59 | /// before the driver is consulted, so a runaway `loop-until-dry` terminates |
| 60 | /// even if the driver would keep admitting work. |
| 61 | /// |
| 62 | /// Product scale: up to 1_000 agents per Workflow run. |
| 63 | pub const WORKFLOW_LIFETIME_CAP: u64 = 1000; |
| 64 | |
| 65 | /// Maximum concurrently executing agents within one Workflow run. |
| 66 | /// |
| 67 | /// Fan-out may *declare* more work via `parallel()` / `pipeline()`, but the |
| 68 | /// host admits at most this many live `task()` children at once; additional |
| 69 | /// spawns wait for a slot. |
| 70 | pub const WORKFLOW_MAX_CONCURRENT: usize = 16; |
| 71 | |
| 72 | /// Maximum items per `parallel()` or `pipeline()` call (design §4.2). |
| 73 | /// Kept at the per-run agent ceiling so a single fan-out cannot declare more |
| 74 | /// work than the lifetime cap can ever complete. |
| 75 | pub const PARALLEL_MAX_ITEMS: usize = 1000; |
| 76 |