FLOW Run SDK/1
Status: closed candidate projection of
FLOW Run/1. The TypeScript implementation is staged as
private @jigging/flow@0.1.0-alpha.1; the Python implementation remains an
unpublished 0.0.0 candidate. The complete shared corpus has passed under the
Bun peer and an independently implemented Python host peer; publication and a
general third-party conformance label remain separate release decisions.
This document fixes the public component-author interface for the Run/1 slice. It does not add wire behavior. When this document and Run/1 differ, Run/1 owns the protocol and this projection must be corrected.
1. Surface and ownership
The TypeScript package root is @jigging/flow. The Python distribution is
flowmd-sdk, imported as flowmd_sdk. These root modules expose Run SDK/1
only.
Both expose only:
The TypeScript projection additionally names FlowCall, EffectCall, and
CallOptions; Python expresses the same values as keyword-only method
arguments and uses ordinary task cancellation.
serve owns protocol stdin and stdout for the process and serves exactly one
root Run. Application logs go to stderr. Calling other protocol, resolver,
Binding, provider, sandbox, Jig administration, Agent, or graph APIs
through this SDK is impossible because none are exposed.
2. TypeScript
run.signal reports root cancellation. If a call-specific signal is already
aborted when callFlow or callEffect is invoked, or becomes aborted while
that call is pending, the returned promise rejects with an OperationError
whose code is exactly CANCELLED. The SDK sends request/cancel if the
request reached the wire. This cancels the local wait promptly; it does not
claim that remote work was undone.
Code which catches only to observe this cancellation must rethrow every other failure:
In particular, passing a call-specific signal does not convert an unrelated effect, operation, protocol, channel, validation, or programming failure into cancellation or success.
3. Python
serve owns and creates the process event loop, so it is a synchronous
entrypoint and rejects use inside an already-running asyncio loop. Root
cancellation cancels the handler task with ordinary asyncio.CancelledError.
Cancelling a task awaiting call_flow or call_effect cancels that local wait
and sends request/cancel if the request reached the wire.
4. Values and snapshots
Public type annotations describe JSON-shaped values, but the wire accepts only
bounded FLOW JSON/1. SDKs validate every value crossing the
boundary. Invalid call arguments fail locally before a request is admitted;
invalid handler results become INVALID_RESULT.
Inbound values are isolated decoded snapshots. Each admitted outbound call is also snapshotted before asynchronous dispatch, so later caller mutation cannot change its wire meaning. The SDKs do not recursively freeze application containers; readonly or frozen outer declarations are authoring guidance, not a new runtime object model.
The SDK never creates an operationId. Component code supplies a stable ID
whose retry and deduplication meaning is defined by Run/1.
5. Results and errors
callFlow and call_flow return the complete child RunResult, including its
outcome. In TypeScript, RunResult is itself a JsonValue and may be retained
directly inside another Run result without rebuilding or casting it.
callEffect and call_effect unwrap a successful effect { value }.
A declared capability error raises EffectError, carrying errorName and
data in TypeScript or error_name and data in Python. Its human exception
message is not portable; authors branch only on the named fields.
Operational failure raises:
Authors inspect code, message, and details; they do not branch on the
human message. In Python, omitted details are represented by None.
The eleven Run/1 operational codes may cross the wire. PROTOCOL_ERROR and
CHANNEL_LOST are local-only classifications and are never serialized as
operational errors. An unhandled valid wire-visible OperationError from the
root handler is preserved. An ordinary exception, a local-only code, or
malformed error metadata becomes EXECUTION_FAILED.
A standard JSON-RPC error returned for a correctly emitted child request is a
fatal peer incompatibility. The SDK closes the channel as PROTOCOL_ERROR
rather than presenting it as an ordinary call failure.
6. Completion and cancellation
The handler may issue concurrent child calls; the SDK continues reading while
responses arrive in any order and serializes writes. At most 64
component-originated requests are live on the wire. It emits at most 65,536
requests during the channel lifetime; a later call fails locally with
OperationError code RESOURCE_EXHAUSTED and emits no request.
Handler return closes admission. The SDK does not detach calls: it requests cancellation for remaining owned work, waits for each wire request to settle, and refuses root success when the handler abandoned a live call. A cancelled local waiter retains an internal wire tombstone until its response or channel termination so a late response is not misclassified as an unknown ID. An explicitly cancelled and observed local wait is not abandonment: the handler may return a result, but the SDK sends that root result only after the cancelled wire request settles.
The deadline is exposed as context, not implemented as an SDK timer. Jig or another host remains responsible for enforcing it and terminating an uncooperative process.
7. Minimal root examples
TypeScript:
Python:
These root-only examples work with the direct Jig alpha. That host currently
answers component-originated flow/call and effect/call operations with
UNAVAILABLE; the operations remain part of portable Run/1.
8. Portable child-call examples
A host which supplies a child-Flow slot may execute the following TypeScript:
The equivalent Python is:
The implementations live under packages/flow-sdk/ and
packages/flowmd-sdk/. The shared executable seed is
conformance/run-1/.