返回 CodeWhale
lib.rs
根目录 / crates / workflow-js / src / 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; 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
76 lines RUST