Runtime, sandbox & errors
A .flow file is not interpreted — it is compiled to restricted
JavaScript and run in a sandbox. This page covers the execution model, the
host-side agent pipeline, events and error codes.
Execution model
The full journey of one run:
- Input validation: the workflow input is validated against the input
type's JSON Schema (external types validate as
{}, accepting anything); failure →WorkflowInputValidationError - Sandbox loading: the compiled artifact runs inside subprocess →
isolated-vm→ trusted bootstrap ($runtimeABI); the only egress is$host.invoke(op, payload), and values crossing the boundary must be JSON - Deterministic execution: pipeline/stage/if/parallel/parallelMap/
verify/require/projections/builtins execute inside the bootstrap;
agentcalls and events forward to the host side - Output validation: the final return value is validated once more
against the output Schema; failure →
OutputValidationError
Steps 2 and 4 mean: data crossing boundaries is always trusted and serializable.
The host-side agent pipeline
Every agent() walks the same pipeline:
check cancellation / deadline
→ agent_runs budget (exceeded = LimitExceededError)
→ concurrency gate (semaphore)
→ target resolution
team.main / team.member → membership validation (failure = CapabilityViolationError)
→ tools narrowing (request ∩ host policy)
→ write policy descriptor
→ OutputContract{schema, repairAttempts}
→ [attempt loop]
timed invocation: AbortSignal of min(timeout, remaining deadline)
racing the call (late results never take effect)
→ structured → Ajv validation
→ invalid & budget left → repair re-invoke with {previousOutput, errors}
→ failed & code ∈ retry.on & attempts left → retry
→ events agent.started / agent.completed / agent.failed
Events
FlowEventSink.emit forwards all events:
workflow.started / workflow.completed / workflow.failed
pipeline.started / pipeline.completed / pipeline.failed
stage.started / stage.completed / stage.failed
agent.started / agent.completed / agent.failed
verify.evaluated / require.failed / progress
emit progress {...} produces only progress events — the Runtime doesn't
care who consumes them; CLI / WebSocket / OpenTelemetry integration is the
host's choice. The event stream is the first-class observability entry.
Runtime error codes
| Code | Error class | Raised when |
|---|---|---|
workflow-input-validation | WorkflowInputValidationError | input failed its Schema |
output-validation | OutputValidationError | agent output (repairs exhausted) or workflow output failed |
agent-invocation | AgentInvocationError | agent call failed / timed out |
capability-violation | CapabilityViolationError | non-member resolution, invalid target, ... |
limit-exceeded | LimitExceededError | agent_runs exceeded |
workflow-timeout | WorkflowTimeoutError | duration exceeded |
assertion | WorkflowAssertionError | a require failed |
cancelled | FlowCancelledError | external cancellation |
isolate | FlowIsolateError | internal isolate error |
worker-crashed | FlowWorkerCrashedError | sandbox subprocess crashed (all pending promises reject) |
Errors cross processes as DTOs (__flowError: true + code/message/stage/
span/details), reconstructed as typed errors on the host side. retry.on
matches these error codes, never messages.
The serialization boundary
Values crossing process / isolate / host boundaries are JSON only: null / bool / number / string / array / plain object. That is why the language has no function values, class instances or circular references — every data structure is serializable by construction, a compilation target rather than a runtime convention.
Compile time vs runtime
Errors come in two layers, compile first:
- Compile time (
flow check): syntax, semantics, types — fifty-plus diagnostic codes, see the language reference. Error-level diagnostics prevent code generation - Runtime: the table above — most map onto declarative structures
(
limits/timeout/require/expect): write failure modes as declarations and let the runtime watch them for you