Skip to main content

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:

  1. Input validation: the workflow input is validated against the input type's JSON Schema (external types validate as {}, accepting anything); failure → WorkflowInputValidationError
  2. Sandbox loading: the compiled artifact runs inside subprocess → isolated-vm → trusted bootstrap ($runtime ABI); the only egress is $host.invoke(op, payload), and values crossing the boundary must be JSON
  3. Deterministic execution: pipeline/stage/if/parallel/parallelMap/ verify/require/projections/builtins execute inside the bootstrap; agent calls and events forward to the host side
  4. 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

CodeError classRaised when
workflow-input-validationWorkflowInputValidationErrorinput failed its Schema
output-validationOutputValidationErroragent output (repairs exhausted) or workflow output failed
agent-invocationAgentInvocationErroragent call failed / timed out
capability-violationCapabilityViolationErrornon-member resolution, invalid target, ...
limit-exceededLimitExceededErroragent_runs exceeded
workflow-timeoutWorkflowTimeoutErrorduration exceeded
assertionWorkflowAssertionErrora require failed
cancelledFlowCancelledErrorexternal cancellation
isolateFlowIsolateErrorinternal isolate error
worker-crashedFlowWorkerCrashedErrorsandbox 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

Next steps