Spore Workflow Language
Define portable workflow graphs, action interfaces, execution behavior, and durable state independently of a particular runtime. Runspore is one implementation; Temporal and other engines may implement supported layers.
Definition Model
Status: Draft normative contract. Part: definition. Requires: none for structure; actual execution also requires a declared execution profile.
1. Model and boundaries
A workflow definition is a portable directed graph of nodes with typed entry, named outcomes and explicit routes. It can be generated from SWL-DOT or another frontend. The JSON Schema describes its base serialized shape.
An action contract describes the input/output and behavioral obligations of an operation. A binding supplies an implementation on a particular host and is not embedded in the definition. An action contract can be fulfilled by a Temporal Activity, in-process function, external worker, HTTP operation, or WASM component, provided the behavior matches and the host authorizes the binding.
An activity names an action contract, but it is not itself a command. A graph node is a control-flow position. Two nodes may invoke the same action with different input and parameters.
2. Top-level members
| Field | Requirement | Meaning |
|---|---|---|
language | MUST equal swl in the draft | Model identity, not a Runspore dialect |
name | required | Workflow identifier |
start | required | Existing node ID |
nodes | required | Nonempty map of nodes by unique ID |
actions | optional when no activities | Map of action interfaces by ID |
requires | optional | Required semantic profiles; default ["core"] when executed |
limits | optional | Host-independent safety caps; default 16 visits per node, 256 activations |
inputSchema | optional | JSON Schema for workflow inputs |
IDs use [A-Za-z_][A-Za-z0-9_-]{0,63}. Outcome names use [a-z][a-z0-9-]{0,63} except reserved failed and exhausted. UTF-8 presentation labels may differ from IDs. Unknown required semantics MUST be rejected before side effects.
3. Node kinds
| Kind | Required members | Effect |
|---|---|---|
activity | action, routes | Schedules the referenced action; chooses the route for its outcome |
await-signal | signal, nonempty routes | Suspends until a matching signal is accepted |
complete | none | Ends successfully, evaluating optional output mapping |
fail | error (code, message) | Ends with a business failure |
Every outgoing routes member maps a named outcome to an existing target node. Nonterminal activity nodes MUST route every action-declared business outcome. Failure routing uses the reserved failed key; it is not an action-declared business outcome. An exhausted route is required exactly when loopLimits is present and permits controlled escape from a cycle. Terminal nodes MUST NOT have routes. Duplicate outcome keys are invalid.
loopLimits maps a node outcome to a positive integer: how many consecutive occurrences may route normally. The next occurrence routes exhausted. The workflow-wide visit cap is a safety boundary, not a business route.
Every node MUST be reachable from start. From every node there MUST exist a graph path to a terminal. This is *structural* reachability; a reachable terminal is not guaranteed to be taken at runtime.
4. Action interfaces versus bindings
Action contract fields (draft core):
| Field | Rule |
|---|---|
effect | required: pure, read-only, idempotent, or unsafe |
outcomes | required nonempty array of unique business outcomes, normally ["ok"] |
inputSchema / outputSchemas | optional schema contracts; output schemas keyed by outcome |
retry.maxAttempts | optional positive count including the initial delivery attempt; default 1 |
capabilities | optional required features; MUST be checked against the host before execution |
Effects are obligations, not enforceable properties of an arbitrary executable. unsafe means the host must not automatically repeat an invocation with unknown result. Another host implementation must document and meet the same observable contract; using identical JSON schemas alone is insufficient.
An implementation binding is separate deployment configuration, for example:
{
"bindings": {
"reserve": {
"provider": "temporal-activity",
"type": "reserveCapacity"
}
}
}This illustrative binding is not a required SWL protocol. Provider-specific credentials, capabilities and authorization remain host concerns.
5. Mapping and data
The mandatory minimal expression profile supports JSON values plus:
{"$get":["input","task"]}: copy a value at a path through the evaluation context;{"$literal":{"$get":["input","task"]}}: return nested data without evaluating it.
A path begins with input, nodes, from or run. Path segments are object keys or nonnegative array indexes. Missing paths are errors, not silently null. The context contains initial input, latest result per node ({outcome,output,visit}), the incoming route (from, or null at start), and an immutable run identity.
There is no implicit JavaScript evaluation, truthiness or mutable global context. Conditionals, joins, JSON Logic and further operators belong to explicit optional expression profiles with separate fixtures. JSON Schema dialect and numeric-domain compatibility must be declared rather than inferred.
6. Portability
A definition can be validated, inspected and rendered without an action implementation. To execute, a host MUST resolve the required action interfaces to authorized bindings satisfying the declared capabilities, and MUST reject unsupported semantic profiles before scheduling effects.
Graphviz appearance (label, shape, colors) is non-semantic and never part of the normalized execution model. Two presentations of the same graph may produce the same normalized definition.
SWL-DOT: graph authoring notation
Status: Draft normative syntax binding. Part: source-notation. Requires: definition.
SWL-DOT is a constrained subset of Graphviz DOT syntax, with separately standardized workflow meaning. It is neither arbitrary DOT nor an extension to Graphviz's grammar. Graphviz need not be installed for a parser or runtime.
1. Source folder and compilation
A source workflow directory may contain workflow.dot (topology) and settings.json (action interfaces, mappings, limits, node configuration). A compiler combines them into the normalized Definition Model. A runtime MAY accept the normalized definition directly, without ever parsing DOT.
The graph's name and graph-specific fields start, kind and on are authoritative in the DOT notation; settings.json MUST NOT silently override them. The optional settings file cannot define executable script or native-function bindings as if they were portable action interfaces; host bindings remain separate. Additional referenced schema files are explicitly declared rather than discovered by globbing.
2. Grammar profile
Accepted constructs are: one named digraph, graph assignments start = "step";, node declarations step [kind="activity"];, directed edges a -> b [on="ok"];, edge chains a -> b -> c;, quoted and simple bare IDs, and DOT line/block comments.
The default node kind is activity. The default edge outcome is ok. An await-signal node uses named events (often received or an explicit business outcome), and terminal nodes use complete or fail. Edges route named outcomes—not evaluation of an arbitrary string label.
Source profiles MUST reject ambiguous unsupported DOT constructs such as undirected graphs, ports and subgraphs, rather than guess their control-flow meaning. A conformant extended frontend may accept more DOT syntax only when its normalization is unambiguous and it advertises that extension.
digraph "review" {
start = "build";
build [kind="activity", label="Build"];
approve [kind="await-signal", label="Approval"];
done [kind="complete"];
denied [kind="fail"];
build -> approve [on="ok"];
build -> denied [on="failed"];
approve -> done [on="approved"];
approve -> denied [on="rejected"];
}The label values above have no execution meaning. A tool must not infer an action from the visual shape or treat a drawn caption as a route. A renderer that ignores SWL attributes may show the graph but does not thereby validate the workflow.
3. Attributes, defaults and errors
| Location | Semantic attribute | Meaning |
|---|---|---|
| graph | start | Required node ID |
| node | kind | activity (default), await-signal, complete, fail |
| edge | on | Named outcome; ok if omitted |
Graphviz-only rendering attributes may be accepted from an explicit allowlist and discarded during semantic normalization. Unknown semantic attributes, conflicting repeated declarations, unknown target nodes, duplicate outgoing outcomes and unsupported required extensions MUST be errors. Namespaced presentation extensions may be retained without becoming control-flow rules.
The initial notation profile intentionally does not require a legacy Runspore dialect marker. Existing Runspore workflows need an explicit compatibility/migration adapter; claiming that an unmodified Runspore parser accepts SWL-DOT is incorrect.
4. Source mapping and round-trip tools
A parser SHOULD expose source locations and comments to editors separately from the semantic definition. Re-rendering should preserve meaning; a lossless editor additionally preserves authorship details and unrelated presentation attributes.
The normalized JSON model is the runtime interchange format. DOT layout hints MUST NOT affect canonical identities or execution decisions.
Execution Semantics
Status: Draft normative behavioral contract. Part: execution. Requires: definition, invocation for action-bearing hosts. This document defines the small sequential core, not parallel execution.
1. Operational model
A run has exactly one active position or a terminal result. Nodes are entered one at a time, cycles are permitted, and each visit is a distinct activation. The pure transition engine receives a validated pinned definition, prior state and accepted event, and produces a new state and authorized commands. The transition function MUST NOT perform I/O, read a clock or choose an implementation binding.
transition(definition, previousState?, acceptedEvent)
-> { nextState, commands, diagnostics }The authority that commits a transition is responsible for ensuring that a command is durably authorized before it is dispatched. An emitted command is an intent, not proof the effect completed. A standalone non-durable interpreter MAY be simpler but cannot claim the Durability part.
2. Entering nodes
activity: compute its input mapping and schedule one invocation of the bound action. Status becomesrunninguntil a result or intervention.await-signal: enterwaiting. A signal with the expected name and an allowed outcome selects a route.complete: evaluate its output and endcompleted.fail: endfailedwith the declared error.
A run begins at start. Upon taking a route, the source node's latest {visit,outcome,output} is recorded. The target counts a new visit and may schedule more work immediately. An implementation MUST bound internal transitions even where no external activity yields.
3. Outcome routing and loops
A successful action's declared business outcome selects exactly its route. Operational failure selects a failed route if one exists; absent one, the run becomes terminal failed. Business negative outcomes (such as rejected) are not automatically operational failures.
An outcome-limited route with limit N is followed for the first N consecutive results from that node; the next occurrence selects the required exhausted route. Other outcomes reset the streak. Retry of one invocation does not count as a new node visit. Looping through graph edges does.
Workflow-wide visit and activation limits are terminal safety boundaries. They MUST NOT be silently converted into an authored business branch.
4. Attempts, unknown outcomes and effects
An invocation has a stable logical ID and a separate delivery-attempt number. retry.maxAttempts counts the first attempt. A retryable failure may authorize the next attempt until its cap is reached; terminal operational failure follows failed or fails the run.
When an invocation's result is unknown (worker crash, lease loss, lost acknowledgement), an unsafe effect MUST stop for intervention rather than repeat automatically. A host may repeat a pure, read-only or idempotent action within its attempt cap, using the same logical invocation and idempotency key. Exhaustion of attempts with still-unknown outcome must not pretend the external effect failed; it requires an explicit resolution mechanism.
The standard cannot guarantee exactly-once external effects. Implementations are responsible for making stated idempotency obligations true at the integration boundary.
5. Signals, cancellation and sequencing
The core signal profile accepts a matching signal while a workflow is waiting at the intended node. Events that arrive when no matching waiter exists are reported unmatched; an optional buffered-signal profile may define durable early delivery, order and capacity.
Cancellation is a durable control request with an observable run status, not proof that every already-authorized external effect was interrupted or reversed. In-flight cancellation acknowledgements, compensation and hard stop behavior require additional declared profiles.
Accepted events for a given run MUST have a well-defined order. Duplicate or stale results MUST NOT advance the run twice. Runtime adapters MAY use different private histories but MUST preserve the same observable routing, work authorization and results for claimed profiles.
6. State-machine acceptance examples
startatactivity: exactly one logical invocation is authorized.result(ok): records result and enters the routed target.signal(approved)at matching waiter: routes once; duplicate delivery does not.result(unknown)from unsafe invocation: parks for an explicit human/system resolution.- A limited result routes via
exhaustedonly after the allowed streak. - Re-entering a node after a business loop creates a new invocation; retrying its same invocation does not.
See Conformance for what can currently be tested. Features beyond this core, notably parallel forks, joins, subworkflow calls, timers and compensation, require separately specified semantics and tests.
Invocation and Control
Status: Draft normative contract. Part: invocation-control. Requires: definition for action identity; execution when driving workflows.
SWL distinguishes the workflow caller, the running workflow, the action provider, and the durable host. It does not prescribe HTTP, Temporal, NATS, WASM, or shell commands.
1. Three independent dimensions
- A client can start a workflow and receive a run handle immediately, or await its result.
- An action can return its result inline, or return an accepted operation handle and complete later.
- A workflow can execute sequentially, suspend waiting for external input, or use a separately declared parallel profile.
A JavaScript Promise is not necessarily durable deferred completion. A provider returning accepted means the operation can be identified and recovered; it does not mean business success.
2. Abstract action lifecycle
invoke(actionContract, invocationId, input, parameters, idempotencyKey)
-> completed(outcome, output)
| accepted(operationHandle)
| failed(error, retryable)
| unknown
complete(operationHandle, completionMessageId, outcome, output)
cancel(operationHandle, cancellationRequestId)
inspect(operationHandle) -> statusThese are conceptual operations; this document does not fix a network wire protocol. A host adapter MUST define authentication, authorization, serialization, deadlines, correlation and the authority that records completions.
The operation handle is opaque, not a portable continuation or a credential to be sent to arbitrary clients. The invoker must be able to reattach to an accepted operation after a lost acknowledgement; if it cannot, the outcome is uncertain rather than safe to repeat.
3. Identities and deduplication
Keep separate:
runId: one execution of a pinned workflow;activationId: one visit to a node;invocationId: one logical call of an action;attempt: transport or execution delivery attempt of that invocation;operationHandle: provider-side durable operation, when applicable;completionMessageIdandrequestId: at-least-once input deduplication.
The same invocation may be redelivered with the same idempotency key. Re-visiting a node through a workflow edge creates a fresh invocation. A duplicate completion is not another business outcome. A late result from an old attempt or cancelled run must not authorize a second transition.
4. Control plane
Treat the following operations distinctly:
| Control | Semantics |
|---|---|
inspect | Read current state; no side effects |
signal | Asynchronous external input, acknowledged at an explicit acceptance stage |
update | Tracked request/response change with validation and a defined completion receipt |
cancel | Request to stop future work and attempt cancellation of in-flight work |
resolve | Explicit resolution of an uncertain invocation |
A successful HTTP response can mean *received*, *durably accepted*, *applied*, or *finished*. The binding MUST make that meaning explicit. A retry after a lost response must not create unintended duplicate work.
5. Timeout and retry authority
Dispatch time, one execution attempt, a long-running operation and a workflow run can have different deadlines. Worker leases are not operation deadlines. Cancellation requested, acknowledged, and effect compensated are separate facts.
A particular logical invocation boundary MUST have one retry authority. Mapping an SWL policy onto Temporal's retry configuration requires exact comparison of attempt counting, retryable errors and delay behavior, not copying similarly named fields.
Potential bindings: Temporal Activities/Updates/Signals, Nexus operations, REST callbacks, in-process functions and Runspore workers. These are options, not mandatory dependencies. See research.
Durability, Storage and State Interchange
Status: Draft normative and proposed optional profiles. Part: state. Requires: execution and invocation for durable action-bearing engines.
The state specification has three separate contracts. An implementation may claim durable behavior without implementing the optional storage-provider API or cross-host resume exchange.
1. Durable behavior profile
After an acknowledged durable event, an implementation MUST preserve its accepted order, deduplication identity, authorized work intents and subsequent logical state across the claimed failure model. A host must not dispatch an effect whose intent cannot be recovered after a crash.
A workflow execution MUST pin its definition, required semantic profiles and bound action interfaces (or a reproducible immutable resolution of them). A new deployment must not silently reinterpret an already-running workflow.
External exactly-once execution is not guaranteed; use stable logical invocation IDs, idempotency or reconciliation. An unprovable effect result is represented as unknown and resolved explicitly where required.
2. Optional store-provider contract
A storage implementation provides semantic operations, not only key/value methods.
| Operation | Required behavior |
|---|---|
createRun | Idempotent by scoped request ID and body digest |
appendEvent | Atomically accept one ordered event or return its receipt |
commitTransition | Compare expected revision and atomically persist state, consumed event and work intents |
claimWork | Acquire an owner and fencing token with an expiry |
completeWork | Reject stale owners/tokens and deduplicate recorded completion |
readRun / listDueWork | Consistent reads and recovery of due work |
Repeating a request ID with identical content returns the same receipt; reusing it with conflicting content is an error. Fenced claims cannot be resurrected by a late heartbeat. The storage implementation must define failure/recovery outcomes for a lost response after a successful commit.
The contract permits SQL, log-based storage or another safe implementation. It does not prescribe SQLite tables, a vendor, or a host process count.
3. State inspection (portable view)
An inspector may read: run ID, definition identity, execution status, active position(s), latest node outcomes, pending signals, outstanding operation summaries, blockers and recent accepted events. Inspection does not imply a new runtime can resume execution.
A state view SHOULD be safe to share without embedding credentials, capability grants, secret environment variables, native handles or plaintext sensitive action inputs.
4. Optional resumable interchange
A resumable export must describe all semantic state necessary at a declared supported boundary: pinned definition/semantics/bindings, run input and context, positions, activation counters, retry/loop counters, accepted messages and deduplication receipts, timers/deadlines, outstanding operations and extension-specific state.
Initial interoperability SHOULD target quiescent boundaries only: the source is fenced before the destination is activated. An in-flight operation needs a transferable, authenticated handle and an explicitly specified handoff profile.
A JSON snapshot of Temporal internals is not a portable SWL continuation. A matching normalized definition alone is not enough to migrate running executions between hosts.
5. Mandatory failure tests for claimed durable implementations
- Crash between event acceptance and transition commit.
- Crash after committed intent but before dispatch.
- External effect succeeds but its acknowledgement is lost.
- Duplicate event, duplicate completion, stale attempt and conflicting request body.
- Lease expiry racing with completion; late heartbeat must fail.
- Concurrent coordinator commits at the same revision.
- Cancellation racing with deferred completion.
- Recovery after host restart with immutable pinned definition.
A passing in-memory reducer test does not establish durable-host conformance.
Distribution and Artifacts
Status: Draft optional contract. Part: distribution. Requires: definition; optionally source-notation.
Distribution is not required to parse or run a definition already provided by a trusted host.
A package contains an immutable normalized definition and an explicitly declared set of assets (instructions, schemas and executables) that a chosen binding needs. A manifest records language release, required feature profiles, content digests, resource boundaries and declared dependencies. Undeclared adjacent files do not join a package implicitly.
A portable package MUST NOT require the Runspore CLI, Rust toolchain, WASM loader, or SQLite. A particular host MAY offer WASM packaging, container packaging or another format as a separate distribution profile.
A compiler MUST reject path traversal, absolute paths, missing required assets, digest mismatches and unsupported required profiles before an effect is scheduled. A signed package is not automatically authorized to invoke a capability.
Canonical semantic identity should be computed from a declared deterministic serialization of the normalized definition. Visual layout/presentation changes must not silently change runtime decisions. Source provenance or artifact digests may independently change when source files or packaging encoders change.
Open design point: the first stable canonical encoding and digest domain. The public draft deliberately does not claim that any existing Runspore-specific binary/digest is the SWL interchange format. Finalize with fixtures and a second independent implementation.
Conformance, Compatibility and Versioning
Status: Draft conformance policy, not a published certification program.
1. Claim exactly what was implemented
A software project SHOULD publish a machine-readable manifest naming:
- SWL specification release or draft commit;
- implemented parts (definition, source-notation, execution, invocation-control, durable-state, store-provider, state-interchange, distribution);
- supported feature profiles and expression/schema dialects;
- limits (size, depth, visits, timers, supported action kinds);
- tested conformance fixture revision.
No implementation may imply it supports every SWL feature just because it parses SWL-DOT.
2. Behavioral boundaries
| Claim | Required evidence |
|---|---|
| Source | Parse and normalize valid and invalid examples; ensure presentation does not alter semantics |
| Definition | Schema and graph invariants, action contracts and capability validation |
| Execution | Equivalent accepted event traces yield matching observable commands, routes, states and result codes |
| Invocation | Inline/deferred, duplication, retry authority, cancellation and correlation |
| Durable behavior | Crash, fencing, deduplication, atomicity and restart tests |
| Store provider | Multi-coordinator concurrency and lost-acknowledgement tests |
| State interchange | Verified export/import with source fencing at supported boundaries |
Equivalent observable traces do not mean private database bytes or Temporal event-history records must match those of Runspore. A separately declared byte-exact canonical encoding may impose stronger constraints.
3. Feature profiles (candidates)
core is sequential execution with activity, await-signal, complete/fail, named routes, explicit failures and basic mapping. Other independently declared capabilities may include:
buffered-signalsandtimers;deferred-actionsandtracked-updates;parallel,subworkflowsandcompensation;expressions-jsonlogic,state-exportandstore-provider.
These names are draft design targets, not claims that all corresponding execution algorithms have been specified. A feature name without normative rules and positive/negative fixtures is not conformant.
4. Verification
The first official suite should include valid/invalid definition fixtures, DOT parse cases, trace fixtures, rejected feature declarations and action-contract errors, plus recovery fault injection for stateful hosts. Tests SHOULD cover ambiguous edge cases: explicit vs default outcomes; missing vs null values; unknown effect result; stale completion; retries after crashes; timers; cancellation races; safe migration fences.
A non-Rust implementation must derive expected behavior from the published spec and fixtures, rather than call Runspore's reducer and claim independence.
5. Changes and authority
Spec releases are immutable. Editorial errata that affect expected behavior require a test. New required fields, changed routing, value normalization, identifiers or canonical encodings are breaking changes unless a separately negotiated feature/release gate permits them.
The standard wins over any one implementation. When code and normative text disagree, open an issue and a conformance fixture; do not silently redefine SWL from implementation details.
Standards review and prior art
This document is informative. SWL is general-purpose and intentionally does not require AI agents, a specific programming language, a database, or a transport.
| Reference | Useful idea | Boundary for SWL |
|---|---|---|
| StrongDM Attractor | Explicit execution algorithm, handler contracts, routing order, checkpoint and acceptance rules | Do not use visual node shapes/edge labels to select execution; AI-specific configuration stays in optional providers |
| W3C SCXML | Event ordering and run-to-completion behavior | Hierarchical statecharts and the full SCXML data model are not core requirements |
| Open Workflow | Typed task definitions, external integrations, event wait, workflow lifecycle | Separate task interface from implementation and detached work from awaited deferred completion |
| Amazon States Language | Retry/Catch precedence, timeout scopes, parallel routing | Explicit attempt counting, result/error categories, failure semantics |
| Common Workflow Language | Mandatory requirements versus optional hints, abstract process definitions | Explicit profiles and capability rejection rather than hidden implementation assumptions |
| Nexus RPC | Inline results, long-running operation handles, callbacks, cancellation | Candidate transport binding, not a mandated protocol |
| Temporal | Durable history, deterministic replay and language SDKs | SWL may run atop Temporal without a second persistent state authority |
Specifically valuable distinctions
- Attractor demonstrates how much ambiguity is removed by normative state-transition algorithms. SWL should borrow that rigor, not its AI-oriented shape semantics.
- SCXML demonstrates the importance of specifying accepted order, internal progress and event consumption rather than saying only "wait for signal".
- Open Workflow separates invocation and event waiting. SWL must not confuse an operation-accepted receipt with completed business work.
- AWS Step Functions and Temporal do not count retries identically under similarly named fields; an adapter must test semantics rather than copy names.
- CWL's requirements/hints distinction is a good model for supporting subsets of SWL.
- Nexus is promising for async operation boundaries, especially where Temporal itself remains the durable orchestrator; confirm actual SDK/server topology before selecting a concrete binding.
- Separately test behavior under different engines; schema equality is insufficient to demonstrate equivalent side effects or safe state migration.
Future investigation
Publish detailed comparison cases on event races, parallel joins, compensations, child workflow identities, safe migration, and expression semantics. Add interoperable traces instead of increasing the mandatory core without evidence.