Durable Execution
Normative model for Actions, Flows, and distributed runtime behavior
Status: the core Action/Flow model and distributed-build requirement are locked. Plan details, policies, wire format, and deployment APIs are partly direction or open.
Scope
Durable execution MUST be opt-in. Ordinary VibeLang functions MUST remain ordinary eager functions and MUST NOT be executed by the durable scheduler unless selected through an Action or Flow boundary.
Compiler-Recognized API
A Flow MUST be declared with ordinary TypeScript call syntax:
import { durable } from "vibelang:flows"
const Build = durable(function Build(
input: BuildInput,
): Result<Artifact, CompileError | PackageError> {
const compiled = Compile.run({ source: input.source }).unwrap()
return Package.run({ code: compiled.code })
})durable is a compiler-recognized function imported from the compiler-owned vibelang:flows virtual module. It is not a language keyword. The compiler MUST recognize the resolved imported binding, so an aliased import preserves durable behavior while an unrelated function named durable remains ordinary.
The argument MUST be an inline function or a function declaration/value that the compiler can resolve statically. Passing an opaque runtime-selected function MUST be a compile error.
The compiler MUST replace a recognized durable(...) call with a serializable Flow descriptor referencing the emitted Plan IR. It MUST NOT emit a runtime callback wrapper. Uncompiled JavaScript execution MUST fail during vibelang:flows virtual-module loading.
Action
An Action is an abstract runtime operation with:
- an open, replaceable implementation
- a closed typed function signature
- compiler-derived input, success, Error, and requirement information
- compiler-derived persistence schemas or explicit codecs where derivation is impossible
An Action implementation MUST be an ordinary VibeLang function or callback. A fallible implementation returns Result<A, E> or Promise<Result<A, E>>; compiler lifting allows its body to use plain success returns and Error throws. It MUST NOT require an Effect wrapper or failure annotation.
An abstract Action signature has no implementation body from which to infer failures, so a fallible signature MUST state an explicit Result<A, E> or Promise<Result<A, E>> return type.
If an implementation calls Capability.context() directly or invokes a function with context requirements, those capabilities MUST enter the Action implementation's compiler-inferred requirement row. They MUST NOT become explicit Action inputs or source-level context parameters.
Source authors MUST NOT repeat Action types as separate schema arguments.
Durable Boundary
Every value crossing an Action or Flow persistence boundary MUST satisfy the compiler-checked durable codec contract.
Plain data SHOULD derive the contract automatically. Functions, capabilities, process handles, and other ephemeral values MUST be rejected unless they define an explicit durable representation.
any and unknown MUST require an explicit codec at the boundary.
Flow
A Flow is the closed program produced when the compiler lowers a function passed to durable(...) into a statically analyzable execution-plan template.
The compiler MUST lower the function's checked syntax, control flow, and data flow into Plan IR. It MUST NOT invoke the source function with proxy or symbolic JavaScript values to discover the graph.
An Action.run expression inside the lowered function MUST emit a plan node and a typed symbolic Result. Calling .unwrap() on it MUST emit the Result error-propagation edge. Neither template compilation nor planning MUST execute the Action implementation.
Property access and argument passing on a symbolic result MUST create typed projections and dependency edges when representable in Plan IR.
The durable source function MUST be removable after the compiler emits its plan, schemas, channel rows, identities, and debug map. A planner or coordinator MUST NOT require the source function or a live function side table.
Flow Purity
A durable source function MAY capture compiler-known immutable values, including results produced through comptime(...), only. It MUST NOT observe runtime clock, randomness, environment, mutable state, services, or I/O while the compiler constructs the template.
Runtime-dependent control flow MUST be represented explicitly in Plan IR. The compiler MUST reject an operation that would inspect a symbolic value without a corresponding IR representation.
Compilation Phases
A durable implementation MUST distinguish:
- template compilation, which emits target-neutral Plan IR
- deployment build, which resolves providers and emits coordinator, worker, and manifest artifacts
- plan/preview, which reads emitted Plan IR, validates and optionally specializes known inputs, and reports the graph without dispatching Actions
- execution, which creates or resumes a durable run and schedules ready nodes from the Plan IR
Plan/preview MUST NOT load or invoke the function originally passed to durable(...). It MUST NOT load or invoke an Action implementation. A branch or fan-out whose value is not known to the planner MUST remain an explicit conditional or parameterized template in the reported plan.
The deployment build MUST check the complete provider dependency closure for every emitted worker artifact. Provider layers MUST satisfy the context requirements of each selected Action implementation before deployment.
Ordering and Concurrency
Action ordering MUST be determined by data and control edges, not source position alone. Independent Action calls MAY execute concurrently.
Programs requiring order without a data dependency MUST use an explicit sequencing construct. The construct's syntax is open.
Replay
Every completed Action invocation MUST be journaled within its Flow execution, including Error Results and nondeterministic successes.
After restart, the same Flow execution MUST observe the committed outcome instead of repeating the completed invocation.
Replay is mandatory execution history, not an optional cross-execution cache.
Recovery
Recovery safety and cross-execution reuse MUST be independent policy dimensions.
The runtime SHOULD distinguish idempotent, compensable, and irreversible recovery behavior. It MUST NOT retry an ambiguously committed irreversible Action automatically.
The runtime provides at-least-once execution only where retry is allowed. Exactly-once external effects require idempotency, fencing, hermetic sealing, or compensation outside the scheduler's basic guarantee.
Reuse
Cross-execution reuse MUST distinguish:
- memoization, where one committed acceptable result becomes canonical for an explicit key
- content caching, where the complete content key asserts reproducible equivalence
A memoized result MUST NOT be promoted automatically to a deterministic content-cache entry.
A content key SHOULD include canonical inputs, implementation and policy digests, relevant dependency identities, toolchain and target facts, and explicit invalidation salts.
Distributed Deployment
A deployment build MUST be capable of emitting a coordinator plus separate tree-shaken TypeScript, native, or Wasm worker artifacts and a routing manifest.
Placement MUST belong to provider or deployment configuration rather than the abstract Action signature. Multiple implementations of one Action MAY target different worker pools or machines.
Security
Workers MUST receive only their declared provider authority. Messages crossing coordinator and worker boundaries MUST be validated against derived codecs.
Secrets, sandboxing, artifact access, worker authentication, fencing, and attestation are runtime concerns but MUST preserve the compiler-derived Action contract.
Open Questions
The following remain open: stable source IDs, plan loop and fan-out semantics, explicit sequencing, wire encoding, schema migration, deployment syntax, provider selection, and large-artifact reference protocols.