Skip to main content

codewhale_workflow_js/
lib.rs

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; a failed slot becomes `null`;
23//!   at most [`PARALLEL_MAX_ITEMS`] items.
24//! * `pipeline(items, ...stages)` — per-item stage chains with no barrier
25//!   between stages; a stage error drops that item to `null`; same item cap.
26//! * `log(msg)` / `phase(title)` — progress events forwarded to the driver.
27//! * `budget.total` / `budget.spent()` / `budget.remaining()` — live driver
28//!   snapshots (`total` is `null` and `remaining()` is `Infinity` when no
29//!   ceiling is configured).
30//!
31//! `Date.now()`, `new Date()`, `Date.parse/UTC`, and `Math.random()` throw:
32//! runs must be deterministic so recorded traces can be replayed.
33//!
34//! # Ownership boundaries
35//!
36//! Token accounting and reservation (design §5.3) belong to the driver; the
37//! VM only reads snapshots and fast-fails a spawn when the pool is already
38//! exhausted. Fleet roster resolution for `profile` also happens driver-side;
39//! this crate normalizes and token-validates the profile string, nothing
40//! more.
41
42mod driver;
43mod error;
44mod schema;
45pub mod testing;
46mod vm;
47
48pub use driver::{
49    BudgetSnapshot, ProgressEvent, SpawnedTask, TaskCompletion, TaskRequest, WorkflowDriver,
50    normalize_profile,
51};
52pub use error::{DriverError, WorkflowJsError};
53pub use vm::{VmLimits, WorkflowVm};
54
55/// Maximum `task()` spawn attempts per run (design §4.3). Counted in the VM
56/// before the driver is consulted, so a runaway `loop-until-dry` terminates
57/// even if the driver would keep admitting work.
58pub const WORKFLOW_LIFETIME_CAP: u64 = 1000;
59
60/// Maximum items per `parallel()` or `pipeline()` call (design §4.2).
61pub const PARALLEL_MAX_ITEMS: usize = 4096;