--- url: https://flow.jig.md/guide/overview.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # Build on reusable know-how FLOW is the independent standard for packaging and invoking reusable methods. A **Flow** puts code, Agent judgment, or both behind one method boundary. A compatible host supplies local powers.
AUTHOR A METHOD

Build your first Flow.

Choose a language and a compatible host. Understand the package and handler boundary.

Start building ↗
IMPLEMENT THE STANDARD

Find the exact contract.

Work from the public package, values, protocol, SDK, and interoperability specifications.

Open the specification map ↗
UNDERSTAND THE DESIGN

Keep the boundary small.

See why implementation freedom and locally supplied authority matter to reuse.

Why FLOW exists ↗
FOR AGENTS

Read the source of truth.

Fetch focused Markdown or the full public bundle, without adopting host-specific assumptions.

Get the context ↗
## Find your task | I want to… | Read | | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | See code and Agent work compose through one boundary | [Understand FLOW](https://flow.jig.md/guide/understand.md), then [the Jig example](https://jig.md/guide/request-triage) | | Package a reusable procedure | [Start building](https://flow.jig.md/guide/start.md), then [Package/1](https://flow.jig.md/spec/package-format.md) | | Write a Python method and exercise the protocol | [Python SDK guide](https://flow.jig.md/guide/python.md) | | Write TypeScript under a qualified Jig build | [Jig's first Flow](https://jig.md/guide/) | | Understand portable input and output values | [JSON/1](https://flow.jig.md/spec/json-values.md) | | Validate inputs, settings, or results | [Schema/1](https://flow.jig.md/spec/schema-files.md) | | Launch a method or implement a host | [Run/1](https://flow.jig.md/spec/run-protocol.md) | | Implement or use an SDK | [Run SDK/1](https://flow.jig.md/spec/run-sdk.md) | | Define an independently maintained capability interface | [Capability Contract/1](https://flow.jig.md/spec/capability-contracts.md) | | Define exact live-message meaning | [Channel Contract/1](https://flow.jig.md/spec/channel-contracts.md) | | Resolve terminology or a common question | [Concepts and questions](https://flow.jig.md/guide/concepts.md) | ## What belongs to a host? Providers, credentials, permission, containment, and local execution support are host decisions. FLOW does not make those choices for the consumer. [Jig](https://jig.md/) documents one host; a host's supported languages are separate from the languages with FLOW SDKs. ## How to read this documentation The sidebar keeps authoring, explanation, and exact reference visible from every page. Use search for a field or concept, and the outline to jump within a long specification. Copy Markdown when you need the page in an Agent's context. The standard is prerelease. An SDK candidate in source is not necessarily a published package. Authoring guides identify their execution prerequisites; implementers should qualify a concrete revision against the public [conformance material](https://github.com/jiggy/jig/tree/main/conformance/run-1). --- url: https://flow.jig.md/guide/for-agents.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # Read FLOW as Markdown Agents and developers share the same public source of truth. Each page has a plain-text Markdown version, and the site publishes a generated index and full-content bundle. Reading them requires no account or API key. | Resource | Use it for | | ------------------------------------------------------------------- | ---------------------------------------------------- | | [Documentation index](https://flow.jig.md/llms.txt) | Discover the relevant guide or exact contract | | [Full documentation](https://flow.jig.md/llms-full.txt) | Read the whole site when your context budget permits | | [Task map as Markdown](https://flow.jig.md/guide/overview.md) | Select a path for authoring or implementation | | [Specification map as Markdown](https://flow.jig.md/guide/index.md) | Find every normative boundary | **Copy Markdown** copies the page content. **View Markdown** opens its plain-text version. The HTML page `/spec/run-protocol` corresponds to `/spec/run-protocol.md`. ## The architecture to preserve Executable Flows put code, Agent judgment, and mixed methods behind one input, outcome, and result boundary. Compose through the method's contract; do not require a different calling model just because its implementation uses an Agent. FLOW owns the portable boundary. Jig owns local authority and execution lifecycle. A shared interface does not establish equal judgment, cost, or required powers. See [why FLOW exists](https://flow.jig.md/guide/understand.md) and the [complete Jig example](https://jig.md/guide/request-triage). ## A focused starting instruction ```text title="Documentation context" Read https://flow.jig.md/llms.txt and fetch the pages relevant to my task. Use the exact public specification for package or protocol requirements. Keep FLOW independent of host admission, providers, credentials, and policy. Distinguish SDK availability from a host's supported implementations. Do not infer permission to execute from a package's declared capabilities. Validate against the public conformance material for the selected revision. ``` These instructions supply context, not execution authority. The consumer and receiving host remain responsible for their powers. ## Choose only the context you need - **Package author:** [authoring path](https://flow.jig.md/guide/start.md), [Package/1](https://flow.jig.md/spec/package-format.md), and your SDK guide. - **Host implementer:** [Run/1](https://flow.jig.md/spec/run-protocol.md), [JSON/1](https://flow.jig.md/spec/json-values.md), and the public conformance corpus. - **SDK implementer:** [Run SDK/1](https://flow.jig.md/spec/run-sdk.md) together with Run/1. - **Interface author:** [capability contracts](https://flow.jig.md/spec/capability-contracts.md) or [channel contracts](https://flow.jig.md/spec/channel-contracts.md). Jig's operator configuration belongs in [Jig's documentation](https://jig.md/guide/for-agents). It is not a portable FLOW requirement. ## Check meaning before assuming support The specifications are prerelease. A source SDK candidate may not yet be published, and a host may support only a subset of implementations. Use a qualified revision and report missing support directly. The public Markdown includes explanatory pages and normative specifications; the latter define exact requirements. The site does not include internal repository instructions or transfer a method author's authority to its reader. --- url: https://flow.jig.md/guide/concepts.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # Concepts and questions FLOW's core idea is capability compounding: useful methods become a starting point for further capability. These definitions introduce its boundaries; the linked specifications define exact requirements. | Term | Meaning | Exact or explanatory source | | ------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------- | | FLOW | Independent package and invocation standard | [Why FLOW exists](https://flow.jig.md/guide/understand.md) | | Flow | Package containing one reusable method | [Package/1](https://flow.jig.md/spec/package-format.md) | | `FLOW.md` | Required readable package description and metadata | [Package/1](https://flow.jig.md/spec/package-format.md) | | Host | Consumer that supplies invocation and local execution policy | [Run/1](https://flow.jig.md/spec/run-protocol.md) | | Runtime | Program or library advancing a method's internal execution | [The boundary](https://flow.jig.md/guide/understand.md#a-small-boundary-room-for-the-method) | | Run/1 | Protocol for one finite process exchange | [Run/1](https://flow.jig.md/spec/run-protocol.md) | | Outcome | Method-declared result meaning, paired with output data | [Run/1](https://flow.jig.md/spec/run-protocol.md) | | Schema | A declared shape used to validate a portable value | [Schema/1](https://flow.jig.md/spec/schema-files.md) | | Capability contract | Exact interface for independently maintained collaborators | [Capability Contract/1](https://flow.jig.md/spec/capability-contracts.md) | | Channel contract | Exact declared meaning for live messages | [Channel Contract/1](https://flow.jig.md/spec/channel-contracts.md) | ## Is FLOW tied to Jig? No. Jig is one host. FLOW does not require Jig's admission, containment, provider, or persistence model. Another host can implement the public boundary and choose its own local policy. ## Is every Flow executable? No. A description-only package can share guidance and resources. It needs an implementation before it can perform a Run/1 invocation. ## Does portability mean every host runs every package? No. A host must support the implementation and the required public interfaces. SDK availability and host support are separate. Missing support must stay visible rather than silently substituting a different method. ## Must a method use an Agent or graph library? No. Ordinary code can implement a Flow. Agents, Skills, tools, libraries, and graphs are choices inside the method, not mandatory categories in the standard. ## Must I define a capability contract? No. Formal contracts earn their place when independently maintained consumers need an exact interface. A sophisticated procedure does not automatically need every optional mechanism. Start from the [authoring path](https://flow.jig.md/guide/start.md). ## Do methods bring their own permissions? A method can describe the interfaces it needs. The receiving host supplies local authority; a declaration does not grant credentials, access, or provider selection rights. ## Does reuse guarantee improvement? No. Evaluation establishes whether reuse, adaptation, or combination adds usable capability. A portable method can still be wrong or unsuitable for a new setting. Compounding is the ambition, not an automatic property of copying. --- url: https://flow.jig.md/guide/index.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # FLOW specifications FLOW lets you build applications from reusable methods that combine Agent instructions and executable code. Package useful work as a **Flow**, call it from code, and combine it with other Flows to accomplish more. An executable Flow gives a method a defined input and result, so a program can invoke it without an Agent first interpreting its instructions. The method can use ordinary code, Agent judgment, or both. The specifications below define the small package and process boundaries that make those pieces work together. ![A Flow package contains descriptive files and an executable method. Operator choices configure a host, such as Jig, which invokes the method through Run/1.](/static/svg/flow-boundary.a8af8de518.svg) Every package has `FLOW.md`; implementation, schemas, and capability contracts are optional. The Run SDK helps implement the exchange. FLOW defines these portable boundaries, while each host decides which implementations it supports. New to FLOW? [Start building a method](https://flow.jig.md/guide/start.md) or read [why this boundary exists](https://flow.jig.md/guide/understand.md) before exploring the exact contracts. ## Author a Flow Both SDKs implement the same portable Run SDK/1 contract: | Language | Package | Start here | | ---------- | --------------- | -------------------------------------------------------------------------- | | TypeScript | `@jigging/flow` | [SDK quickstart](https://github.com/jiggy/jig/tree/main/packages/flow-sdk) | | Python | `jiggy-flow` | [Python guide](https://flow.jig.md/guide/python.md) | A host's language support is separate from FLOW's SDK availability. ## Specifications - [JSON/1](https://flow.jig.md/spec/json-values.md) defines bounded portable values. - [Schema/1](https://flow.jig.md/spec/schema-files.md) defines conventional `input.schema.json`, `settings.schema.json`, and `result.schema.json` files. - [Package/1](https://flow.jig.md/spec/package-format.md) defines the portable package. - [Run/1](https://flow.jig.md/spec/run-protocol.md) defines one finite process exchange. - [Run SDK/1](https://flow.jig.md/spec/run-sdk.md) defines TypeScript and Python SDK behavior. - [Capability Contract/1](https://flow.jig.md/spec/capability-contracts.md) defines optional machine-verifiable capability descriptors. - [Channel Contract/1](https://flow.jig.md/spec/channel-contracts.md) defines optional direct communication ports and named message meaning. The exact machine-readable FLOW files are published under [`/schemas/`](https://flow.jig.md/schemas/schema-1.json). The Run/1 conformance corpus remains in the [source repository](https://github.com/jiggy/jig/tree/main/conformance/run-1). FLOW does not specify project admission, permissions, sandboxing, persistence, agent policy, or semantic routing. Those are host responsibilities. The [Jig site](https://jig.md/) documents one FLOW host. ## Status and stewardship These prerelease specifications are published directly from the source repository. FLOW is openly implementable; its decision process is described in [the governance document](https://github.com/jiggy/jig/blob/main/Governance.md). --- url: https://flow.jig.md/guide/python.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # Write a Flow in Python `jiggy-flow` lets a Python method receive one FLOW Run/1 invocation, call host-supplied child Flows and capabilities, and return an outcome with data. It has no runtime dependencies and requires Python 3.11 or newer. FLOW is independent of Jig; choose a host that supports Python execution. ## Install Install the latest published prerelease from [PyPI](https://pypi.org/project/jiggy-flow/) into your application's virtual environment: ```sh python -m venv .venv ``` Activate with `source .venv/bin/activate` on Linux/macOS or `.venv\Scripts\Activate.ps1` in Windows PowerShell, then: ```sh python -m pip install --upgrade --pre jiggy-flow ``` To test an unpublished candidate, pass the path to its built `.whl` file to `python -m pip install`. An installed package needs neither this repository nor a development task runner. ## Write a method Save this as `flow.py`: ```python from jiggy.flow import RunContext, RunResult, handle async def run(context: RunContext) -> RunResult: print("Preparing a greeting") # Diagnostics go to stderr after handle starts. return {"outcome": "done", "output": {"greeting": "Hello", "received": context.input}} handle(run) ``` `handle` is a synchronous entrypoint that owns the event loop and serves one Run. Starting `python flow.py` alone waits for a protocol invocation on stdin. Do not call it inside an already-running asyncio loop. ## Exercise the protocol locally For a trusted local example on Linux or macOS, use the repository's existing [independent Python test peer](https://github.com/jiggy/jig/tree/main/conformance/run-1/python-peer). Save its `run1_peer.py` next to `flow.py`, then save the following as `try_flow.py`: ```python import sys import tempfile import time from run1_peer import HostPeer, flow_run_request with tempfile.TemporaryDirectory() as scratch: request = flow_run_request("host:1", {"name": "Ada"}) request["params"]["scratch"] = scratch request["params"]["deadlineUnixMs"] = int(time.time() * 1000) + 10_000 with HostPeer([sys.executable, "flow.py"]) as peer: peer.send(request) response = peer.receive() print(response) peer.finish() ``` Run `python try_flow.py`. The response contains `result.outcome: "done"` and `result.output` with the greeting and supplied input. The test peer is a bounded protocol exercise, not a sandbox or production host; run only your own trusted code with it. Its pipe polling is POSIX-specific. Installed SDK runtime tests also run in the Windows CI job independently of this peer. To distribute a complete Flow package, add `FLOW.md` and describe the Python entrypoint as specified by [Package/1](https://flow.jig.md/spec/package-format.md). A host owns launching the interpreter, dependency preparation, authority, limits and cleanup. ## Call a child Flow or capability Inside your handler, use slots configured by the invoking host: ```python child = await context.run_child_flow( operation_id="summarize-1", slot="summarizer", input=context.input, ) value = await context.call_capability( operation_id="lookup-1", slot="catalogue", method="lookup", input=child["output"], ) ``` A child returns a complete `{ outcome, output }` result; a successful capability call returns its value. Naming a slot never grants authority or creates a provider. Operation IDs are supplied by your method and follow Run/1's identity rules. Await owned calls before returning the root result. `context.settings`, `context.attachments`, `context.scratch` and `context.deadline_unix_ms` contain host-supplied invocation data. Attachments map names to paths and access modes; the SDK does not discover local files or choose permissions. ## Handle failure and cancellation Catch `CapabilityError` for a declared capability application error; inspect `error_name` and `data`. `OperationError` carries an operational `code` and optional `details`, such as `UNAVAILABLE` or `INVALID_INPUT`. Do not turn every exception into a successful method outcome. Root cancellation cancels the handler using `asyncio.CancelledError`. Cancelling an asyncio task awaiting a child or capability call also cancels that local wait and sends protocol cancellation if dispatched. Use `try/finally` for local cleanup and propagate cancellation. A deadline is supplied context, not a timer created by the SDK. The host owns enforcement and settlement. Return JSON/1 values only: bounded JSON with safe integral numbers and valid Unicode. Invalid handler results become `INVALID_RESULT`. Keep protocol stdout free of raw writes and output from imports before `handle`; ordinary `print` after entry goes to stderr. See [Run SDK/1](https://flow.jig.md/spec/run-sdk.md) for exact limits, result validation, logging and cancellation semantics shared with TypeScript. --- url: https://flow.jig.md/guide/start.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # Start building a Flow A Flow packages one reusable method so another consumer can understand and invoke it. Start with a small procedure whose input and useful result you can explain. The same boundary can later contain code, Agent judgment, or both; starting with code makes that boundary easy to see. ## Choose a concrete result For a first method, use an echo or greeting to verify the exchange. Then replace its body with a useful operation, such as validating a supplied record or summarizing supplied text. Keep the operation's own checks in ordinary code. The package contains `FLOW.md` and, for executable work, its implementation. Add resources and schemas when the method needs them. See [Package/1](https://flow.jig.md/spec/package-format.md) for the exact metadata and layout. A description-only package can share readable guidance and resources. Without an implementation, it cannot itself execute a Run/1 invocation. ## Pick your authoring path | Your environment | Follow this path | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | TypeScript, running under Jig | [Create your first Flow](https://jig.md/guide/). Jig initializes the package and pins the SDK revision paired with its build. | | TypeScript, another compatible host | [TypeScript SDK instructions](https://github.com/jiggy/jig/tree/main/packages/flow-sdk). Check that the documented candidate version is published before installing it. | | Python | [Python SDK tutorial](https://flow.jig.md/guide/python.md), including a local protocol exercise and expected output. | SDK availability and host language support are separate. A Python SDK does not establish that Jig can execute Python packages. The specifications and SDKs are prerelease; use the exact revision qualified by your chosen host. ## Recognize the exchange A handler receives the invocation and returns an outcome with data. Choose your language to see the same method: **TypeScript** ```ts title="flow.ts" import { handle } from "@jigging/flow"; await handle(async (run) => { return { outcome: "done", output: { received: run.input } }; }); ``` **Python** ```python title="flow.py" from jiggy.flow import RunContext, RunResult, handle async def run(context: RunContext) -> RunResult: return {"outcome": "done", "output": {"received": context.input}} handle(run) ``` With input `{"name":"Ada"}`, the method's result is `{"outcome":"done","output":{"received":{"name":"Ada"}}}`. The host may wrap that result with its own execution status and diagnostics. These snippets define handlers; they are not standalone shell invocations. `handle` expects a Run/1 host exchange on stdin and stdout. Use the complete setup in your selected tutorial. Keep raw writes off protocol stdout; the SDK routes ordinary console diagnostics to stderr after `handle` starts. ## Make it yours After the echo works, change the output to include a fixed greeting alongside `received`. Run it again and check both values. With Jig, review the source change before running the new revision. Then choose the smallest useful method you want someone else to inherit. Document its purpose, inputs, outcomes, and limits in the package. Validate inputs before domain work and return a result whose meaning a consumer can check. A malformed input should have a deliberate, documented response. ## Build further - [See one caller with three implementations](https://jig.md/guide/request-triage) in a Jig example. - [Understand the design](https://flow.jig.md/guide/understand.md) before choosing a host boundary. - [Add schemas](https://flow.jig.md/spec/schema-files.md) when consumers need exact value validation. - [Use the Run SDK](https://flow.jig.md/spec/run-sdk.md) for host-supplied children and capabilities. - [Explore the specifications](https://flow.jig.md/guide/index.md) when implementing a host or SDK. --- url: https://flow.jig.md/guide/understand.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # Code and Agents. One way to compose them. Skills give Agents instructions and resources for useful work. You can use that work as part of your software, alongside ordinary code: one method interprets a request, another checks a record, another prepares a response. FLOW gives you a way to call and combine these methods through the same interface, whether their implementation uses intelligence, code, or both. **FLOW gives code and Agent work one method boundary.** A **Flow** packages a reusable procedure. When executable, it accepts input and returns a defined outcome and result. Its implementation can use code, Agent judgment, or both. The caller works with the method's contract. ## One caller, three implementations Consider an application that needs a suggested support queue for an incoming request. Its classifier accepts a message and returns `billing`, `technical`, or `manual`. The caller does not need a separate orchestration model for an intelligent implementation. | Inside the classifier | How it works | What the caller receives | | --------------------- | ------------------------------------------------------------------ | ------------------------------- | | Code | Recognize explicit labels; otherwise suggest manual classification | An outcome and queue suggestion | | Agent | Ask an Agent to interpret the message | The same result shape | | Code and Agent | Recognize labels directly; ask an Agent for other messages | The same result shape | All three can contribute at the same compositional level. The mixed method adds interpretation without asking its caller to coordinate a conversation. The code method runs without an Agent interpreting instructions first. The [complete example on Jig](https://jig.md/guide/request-triage) contains the same caller with three configured implementations. That is a concrete host example; Jig's configuration and Agent capability are not FLOW requirements. Shared result shape does not guarantee matching answers, costs, latency, or required powers. Those remain part of choosing and evaluating a method. ## From a Skill to an executable method A Skill is a useful place to capture how an Agent should work. FLOW brings that readable guidance together with a defined execution boundary, so the method can also become a building block in software. An instructions-only Flow can hold guidance and resources. Add an executable entrypoint such as `flow.ts` or `flow.py`, and a compatible host can invoke it directly. The program decides where ordinary procedure is sufficient and where to request interpretation. It can use existing Skills during Agent work. Skills can bundle scripts, and code can invoke those scripts directly too. FLOW's contribution is a common package and invocation contract around the complete method. A Markdown rename alone does not create an executable Flow; [Package/1](https://flow.jig.md/spec/package-format.md) defines valid metadata and implementations. ## Let capability build on capability The classifier can become one part of a larger intake method. Another method might look up supplied account records or prepare a draft response. Each has an input, an outcome, and a result the next method can use. The application owns their ordering, checks, and consequences. This is **capability compounding**: useful methods become the means to do more together. A method can gain checks, replace a reasoning step with code, or combine further specialists while preserving its caller-facing contract. Consumers can build on that work without adopting its internal framework. Changing an implementation still requires the receiving host's applicable review and authorization. Composition makes improvement possible; evaluation establishes whether it helped. A faster classifier may make worse decisions. A more capable method may require broader powers. Reuse carries assumptions as well as capability. ## A small boundary, room for the method FLOW specifies what a package means and how finite work crosses its boundary. It leaves the method's internal control to its implementation. ![A Flow package exposes readable meaning, input, and outcomes. Its runtime owns internal execution; the host supplies authorized capabilities.](/static/svg/flow-boundary.a8af8de518.svg) | Layer | Owns | | ------------- | ----------------------------------------------------------------------------------------------- | | Flow method | Procedure, prompts, selected Skills, validation, and internal control | | FLOW standard | Package meaning, portable values, invocation, outcomes, and optional interoperability contracts | | Host | Supported execution, local powers, providers, credentials, and policy | | Application | Purpose, domain checks, and what happens with the result | This separation is why FLOW is independent of Jig, Agent vendors, and any one runtime. A method can use a plain program or a graph library without making that internal model its caller's responsibility. Different hosts may support different implementations; portability does not imply universal support. For us, **AI-native composition** means code and Agent judgment can participate through the same executable method boundary. The work inside can evolve while the rest of the system keeps a coherent way to build with it. [Build your first Flow](https://flow.jig.md/guide/start.md), or open the [specification map](https://flow.jig.md/guide/index.md) to implement the boundary itself. --- url: https://flow.jig.md/index.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # FLOW Build with Agents as naturally as you build with code. > Start with readable instructions in FLOW.md. Add flow. to make the method executable. Compose code and Agent work through the same interface. [Build your first Flow](/guide/start) | [Explore a Flow](#showcase) ## A small package. A whole capability. Familiar if you use SKILL.md. Built to become a callable part of your software. One Flow package: greet Required: FLOW.md Optional implementation ### Describe: Start with one readable file. FLOW.md holds the method’s description and instructions. Guidance and resources can live beside it. With no implementation, it is an instructions-only Flow. ### Run: Give the method an executable form. Add one flow. entrypoint. A compatible host can invoke it directly, without an Agent first interpreting the Markdown. Switch languages to see the same greeting. ### Compose: Call the capability. Choose its implementation. Another Flow calls the configured greeter slot with an input and receives its result. The same calling boundary works whether the method uses code, Agent work, or both. ### FLOW.md ```markdown --- name: greet description: Greet someone by name. --- Greet the supplied name. Return a short greeting message. ``` ### flow.ts ```typescript import { handle } from "@jigging/flow"; await handle(async (run) => { const name = typeof run.input === "string" ? run.input : "world"; return { outcome: "done", output: { message: `Hello, ${name}!` }, }; }); ``` ### flow.py ```python from jiggy.flow import RunContext, RunResult, handle async def greet(run: RunContext) -> RunResult: name = run.input if isinstance(run.input, str) else "world" return { "outcome": "done", "output": {"message": f"Hello, {name}!"}, } handle(greet) ``` FLOW does not prescribe a language or runtime. The host must support the implementation and its FLOW protocol. TypeScript and Python SDKs exist; their availability does not imply that Jig supports both. Where do Agents fit? These greeting implementations use code. An executable method can also request Agent work through capabilities supplied by its host, using Skills and prompts inside the method. The caller still uses the method’s interface; required powers and result quality can differ. ### Inside another Flow · TypeScript ```typescript return run.runChildFlow({ operationId: "say-hello", slot: "greeter", input: "Ada", }); ``` Input: "Ada" Outcome and output: done → { "message": "Hello, Ada!" } ### Add precision when you need it - [input.schema.json](/spec/package-format#4-conventional-schemas): Optionally define which inputs the method accepts. - [result.schema.json](/spec/package-format#4-conventional-schemas): Optionally define the shape of outcomes and outputs. - [Capability contracts](/spec/capability-contracts): Add an explicit interface for a capability dependency when interoperability needs one. Ordinary child Flow calls need no capability contract. Package walkthrough and SDK excerpts, not a live run. A host supplies the runtime and configures the child slot. Follow the authoring guide for complete setup. [Choose an SDK or host](/guide/start)

From guidance to execution

The familiarity of a Skill.
The composition of code.

SKILL.md captures instructions and resources for an Agent. FLOW.md gives a method readable meaning; an optional flow.<ext> gives it a defined execution boundary.

Use code for known steps, Agent judgment for interpretation, or combine both. Your application calls the method through the same interface, without taking on its internal orchestration.

See one caller use code, an Agent, or both ↗

Progressive disclosure, built into the package

Describe the method
Start with the required FLOW.md. Keep its purpose and instructions readable.
Make it executable
Add one flow.<ext> using a runtime supported by the host.
Add the precision you need
Schemas and capability contracts stay optional until the work calls for them.

A common connection

Different internals.
Made to fit together.

A caller supplies input and handles an outcome. Behind that boundary, a method can use a different language, a library, or an Agent. You can build on the capability without adopting its author’s whole stack.

This is the modularity FLOW is built for: independently reusable methods that become parts of larger applications. The receiving host supplies supported execution and powers; matching interfaces and suitable behavior make the pieces fit.

Jig puts Flows to work locally. FLOW remains independent of Jig, Agent vendors, and any one runtime.

How capability compounds ↗

Build around the method’s interface

  1. Supply an inputYour caller names the configured method slot.
  2. Run the chosen implementationCode, Agent work, or both live inside the method.
  3. Build on the resultYour application checks the outcome and chooses what comes next.

A shared interface does not guarantee identical judgment, cost, latency, or required powers.

Start small.
Build something more capable.

Choose an SDK or a compatible host and build your first executable Flow.

Build your first Flow

The standard and SDKs are prerelease.

What to know before building

Skills can also bundle executable scripts. FLOW adds its own package and invocation contract; a Markdown rename does not establish universal format compatibility or executable behavior. Hosts support specific implementations and supply their own powers and policy. Progressive disclosure here describes the package’s optional layers, not a guarantee about how an Agent loads context.

--- url: https://flow.jig.md/spec/capability-contracts.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # FLOW Capability Contract/1 > _Status: prerelease specification candidate. The machine schema is published > as [`capability-contract-1.schema.json`](https://flow.jig.md/schemas/capability-contract-1.schema.json)._ Most Flows need no formal contract. Generic `flow/run-child` already means “perform this bounded piece of work and return one outcome.” A Capability Contract is for a stable machine interface which must be called repeatedly or precisely. The division is: ```text flow/run-child bounded work through an admitted child slot; no capability contract required capability/call a named method on a bound capability; local opaque or exact public contract ``` Child intent is optional guidance within the existing slot; it does not grant discovery or widen authority. Both operations remain owned by their invoking Run. The distinction is the interface called, not whether its implementation has side effects. Complexity alone never requires a contract. A one-method contract is valid when it is a stable reusable seam, such as the session-store example below, rather than an ordinary child Flow disguised as an API. ## 1. Descriptor One self-contained JSON descriptor is the only public interface format. The sole normative session-store example is the parseable [`session-store.capability.json`](https://github.com/jiggy/jig/blob/main/docs/flow/spec/examples/capability-contracts/session-store.capability.json), whose Capability Contract/1 digest is `sha256:95717125236427f83f401e2f942bb4df46e95867ac34828d719cc9795e5b3e98`. This document does not duplicate the same public URI/version descriptor in a Markdown block. The descriptor is the authority for method names, wire value shapes, and named errors. A contract's normative companion specification and conformance tests may additionally define state machines, cross-field rules, ordering, and atomicity which JSON Schema cannot express. They use the same owner-controlled ID and exact version; changing those semantics requires a new version. The descriptor digest prevents wire-shape equivocation, not behavioral fraud, so publisher provenance and conformance evidence remain separate. The descriptor defines only: ```text owner-controlled absolute identity URI exact SemVer interface version closed named methods input and output Schema/1 values closed named application errors and their Schema/1 data optional per-method directional channel requirements local definitions ``` `true` may replace any method/error schema, meaning any bounded FLOW JSON/1 value. This is the progressive starting point for an interface whose method vocabulary is known before every value shape is. Replacing `true` with a strict schema is an interface change and therefore needs a new exact version and digest. There is no loose/strict mode, second IDL, OpenRPC layer, external `$ref`, inheritance, ranges, subtyping, callback, provider, graph, GUI, or selection declaration. The root is a JSON object with exactly these members: ```text $schema required exact URI flowCapabilityContract required integer 1 id required Contract ID version required Contract Version methods required non-empty method map $defs optional local schema-definition map ``` `$schema` is exactly `https://flow.jig.md/schemas/capability-contract-1.schema.json`. `methods` has 1–256 `LocalName` keys, where `LocalName` is a 1–64 character lower-ASCII slug matching `[a-z0-9]+(?:-[a-z0-9]+)*`. Each method object requires `input`, `output`, and `errors`, with optional `channels`; the first two are Schema/1 schemas and `errors` is a 0–128 member map from `LocalName` to Schema/1 error-data schema. `$defs`, when present, has at most 1,024 keys matching `[A-Za-z][A-Za-z0-9]{0,63}`, each containing one Schema/1 schema. Definitions are referenced only as `#/$defs/`. `channels` uses [Channel Contract/1 declarations](https://flow.jig.md/spec/channel-contracts.md#1-declarations). These requirements do not grant endpoint access or introduce a new capability result wrapper. Calls explicitly map unused held endpoints; selected provider support and every mapping must validate before dispatch. The UTF-8 descriptor is at most 262,144 bytes and the complete embedded schema graph must satisfy Schema/1's depth, node, reference, and keyword limits. Unknown fields reject at every descriptor and method level. ### 1.1 Contract identity and version syntax `id` is one canonical owner-controlled HTTPS URI. V1 admits only lower-ASCII URIs of this form: ```text https:///[/...] ``` The DNS name has at least two dot-separated labels. Each label matches `[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?`. Each non-empty path segment matches `[a-z0-9._~-]+`. User information, ports, IP literals, empty segments, `.` or `..` segments, percent encoding, query strings, and fragments are forbidden. Internationalized names use their lower-ASCII DNS form before publication. Contract IDs compare as exact strings. Hosts perform no case folding, URI normalization, redirect, DNS lookup, or network dereference when matching them. The restricted spelling deliberately turns equivalent-looking URI spellings into one obvious representation while retaining decentralized owner namespaces. `version` is the stable SemVer core form: ```text (0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*) ``` It has exactly three decimal components, no leading zeroes except the single digit `0`, no `v` prefix, and no prerelease or build suffix. Versions compare as exact strings in Capability Contract/1; v1 defines no range or precedence matching. Keep prose, examples, and conformance fixtures beside the descriptor rather than inside it. Capability Contract/1 is closed: an unknown descriptor or method field is invalid. Only Schema/1's explicitly admitted annotations may appear inside embedded schemas, and they remain part of the exact descriptor identity. ## 2. Values and errors Embedded schemas use the same closed, bounded Schema/1 dialect as `*.schema.json`, without repeating the file-root `$schema`. Only local `#/$defs/...` references are allowed. Method success and named application failure are tagged values: ```json { "value": { "sessionId": "s-1" } } ``` ```json { "error": { "name": "not-found", "data": { "sessionId": "s-1" } } } ``` JSON-RPC errors remain reserved for protocol, validation, authority, cancellation, capacity, and provider-loss failures. ## 3. Exact identity and digest Public compatibility in v1 is exact: ```text contract URI + exact SemVer + exact descriptor digest ``` The descriptor never contains its own digest. The digest string is `sha256:<64-lowercase-hex>` and is calculated over the complete parsed descriptor: ```text SHA-256( UTF8("FLOW-Capability-Contract/1\0") || RFC8785(full descriptor) ) ``` The domain separator prevents another SHA-256-bearing object from being substituted. A package, lock, source, prepared-tree, runtime, or internal CAS digest cannot satisfy the contract digest. Any descriptor change—including `$schema` or Schema/1 annotations—changes the digest. This avoids a second normative “semantic projection” algorithm and makes equivocation detection exact. Documentation which should not change interface identity belongs outside the descriptor. Two descriptors claiming the same URI/version but deriving different digests are incompatible. A publisher, trusted index, or source revision claiming both is reported as equivocation in that authority domain. The mere presence of an untrusted conflicting package does not globally quarantine an otherwise exact trusted match; that would make namespace squatting a denial-of-service tool. The digest proves descriptor identity, not publisher authority, provider behavior, quality, or semantic substitutability. Provenance and trust are recorded separately. V1 has no compatible version ranges. Versions 1 and 3 do not satisfy a request for version 2. A provider may expose several exact descriptors; an adapter which consumes one exact version and provides another is an ordinary explicit provider. ## 4. Package declarations and loading A portable consumer carries the complete descriptor it expects inside its own package and references those bytes from `FLOW.md`: ```yaml uses: sessions: contract: ./contracts/session-store.capability.json ``` The local name `sessions` is a consumer slot, not a global identity. The path uses the Package/1 author-reference grammar: exact `./` prefix followed by canonical logical path segments. The prefix is stripped once without any other normalization, decoding, or case folding. The reference is confined to the same immutable staged package and resolves by exact case to one regular JSON file. The host validates it before code loads and derives the contract URI, exact version, and canonical descriptor digest. Authors never copy a hash, URI, or version into the reference. A project-local nonportable effect may use an explicit `local: true` declaration instead; missing a descriptor never means “weak public contract.” Carrying the descriptor is intentional progressive disclosure. Most Flows carry none. A Flow which depends on a strict capability seam pays for one small, self-contained, offline-inspectable interface. Internal content-addressed storage may deduplicate identical copies; v1 does not add a contract package manager, ambient contract catalogue, or network fetch to save those bytes. Capability Contract/1 does not define provider declaration, discovery, registration, lifecycle, or locking. Before dispatch, a host which binds the slot must independently establish a trusted provider and match the provider's exact contract URI, version, and descriptor digest. The identity URI is never an instruction to fetch from the network. ## 5. Required conformance cases 1. An ordinary child Flow runs without a contract. 2. A local opaque effect has no portability claim and cannot masquerade as a public contract. 3. `true` schemas accept bounded JSON/1 while unknown methods/errors still reject. 4. Strict schemas validate inputs, successes, and named error data. 5. Changing any descriptor value under one URI/version makes the descriptors incompatible and reports equivocation only for a source or authority which claims both. 6. Versions 1 and 3 do not satisfy exact version 2. 7. Independent TypeScript and Python implementations compute the same domain-separated full-descriptor digest. 8. Another SHA-256-bearing object cannot substitute for the contract digest. 9. A consumer resolves its exact descriptor offline without dereferencing the identity URI; a trusted provider claim matches the same exact triple. 10. An untrusted conflicting claimant cannot globally quarantine an exact trusted consumer/provider match. 11. Descriptor validation occurs before provider execution. --- url: https://flow.jig.md/spec/channel-contracts.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # FLOW Channel Contract/1 > _Status: prerelease specification candidate for JSON channels._ A named channel contract lets independently authored participants agree on message meaning, not merely shape. Two `{text: string}` messages might mean append a fragment or replace a snapshot. Equal schemas do not establish equal protocols. Generic channels need no named contract. ## 1. Declarations `FLOW.md` and Capability Contract/1 method objects may declare a `channels` map. Each key is a `LocalName`; each value is a closed object: | Field | Meaning | | ----------- | --------------------------------------------------------------------------- | | `direction` | Required `send` or `receive` | | `required` | Boolean, default `true`; optional unwired ports are absent | | `schema` | Optional inline Schema/1 item schema; omission means generic JSON/1 | | `contract` | Optional package-local `./` descriptor reference, exclusive with `schema` | | `delivery` | Optional `direct` or `broadcast`; omission accepts either supported profile | | `start` | Receive-only `beginning` or `suffix`, default `beginning` | Direct receivers start at sequence one. A broadcast subscription starts at the next accepted source sequence; a `beginning` receiver requires that sequence to be one. `suffix` accepts a later start but grants neither subscription authority nor replay. Declarations describe requirements, not grants. A required port must be connected before invocation; this does not guarantee its peer will succeed. Channel names are local slots, not globally discoverable addresses. References obey Package/1's canonical package-local reference rules. Hosts resolve them within the exact calling/receiving package; no URI is fetched. ## 2. Descriptor and identity A self-contained UTF-8 JSON/1 descriptor has exactly these fields: | Field | Meaning | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `$schema` | `https://flow.jig.md/schemas/channel-contract-1.schema.json` | | `id` | Canonical [Capability Contract/1 identity URI](https://flow.jig.md/spec/capability-contracts.md#11-contract-identity-and-version-syntax) | | `version` | Exact three-component SemVer core, using the same syntax | | `semantics` | Normative nonempty description, at most 16,384 UTF-8 bytes | | `item` | One Schema/1 schema; `true` accepts any JSON/1 value | | `$defs` | Optional local definitions under Schema/1's restrictions | The complete descriptor is at most 262,144 UTF-8 bytes. Embedded schemas retain Schema/1 graph, keyword and reference limits. Unknown fields reject. Machine schema validation does not replace byte bounds or the schema-graph checks. Examples and executable behavioral checks can live beside the descriptor. Derive the digest over the complete descriptor, including `semantics`: ```text sha256(UTF8("FLOW-Channel-Contract/1\0") || RFC8785(descriptor)) ``` The result is `sha256:` plus 64 lowercase hexadecimal digits. Authors supply the package-local reference, never a handwritten digest. Exact compatibility compares `id`, `version` and digest. Descriptor identity is an agreement, not proof that an implementation obeys it. ## 3. Matching and interpretation A source becomes named only at creation. A later writer, reader or message cannot silently rename it. For a named source, a transferred writer's port must declare that exact contract. Named readers require the same exact source identity. An unnamed source cannot satisfy a named reader even when schemas match. Named sources may supply generic or schema-only readers. Two nontrivial item schemas must have equal canonical content; omitted/`true` is generic. Hosts do not infer subtyping or execute semantic prose as a validator. Direct late binding validates the queued prefix before adding a reader constraint, and later values satisfy the combined constraints. Broadcast readers instead add per-subscription constraints: a queued prefix is checked before transfer, and a later invalid value fails only that subscription. A new writer declaration must be shape-compatible with live bound subscribers, just as a new reader must match the admitted writer. This is an admission check, not source-wide receiver validation; disposed, ended or failed subscribers do not veto a later writer. Writer/source constraints remain source-wide. Invalid mapping rejects before any right moves or new participant dispatches. Forwarding an unused receive endpoint preserves its actual source identity. Reading values and copying them into a new generic source does not. A converter or named relay owns its own output meaning and validation. Hosts enforce identity, item shapes, granted rights, delivery and start position. Applications enforce correlation, ordering across messages, deltas versus snapshots and domain completeness. Sequence one identifies this source's origin, not complete upstream history. A clean channel end cannot establish a successful Agent result, accepted application outcome or durable processing. The exchange and lifecycle rules are in [Run/1](https://flow.jig.md/spec/run-protocol.md#51-channels). This contract does not grant network access, session mutation or arbitrary transports. Binary payloads require separately specified support. --- url: https://flow.jig.md/spec/json-values.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # FLOW JSON/1 value model FLOW uses JSON as a bounded cross-language value format. JSON/1 fixes the parts which ordinary host parsers otherwise disagree about; it is not a new serialization syntax. It applies to protocol frames, settings, inputs, results, schemas, and descriptors unless a particular protocol or value seam states a lower limit. ## Syntax and strings JSON/1 is [RFC 8259](https://www.rfc-editor.org/rfc/rfc8259) JSON encoded as UTF-8, with no BOM. Parsers reject duplicate object member names before constructing an object. Strings and member names contain Unicode scalar values only; literal or escaped lone surrogates are invalid. Host-language object prototypes and magic property names have no semantic role. ## Numbers A number is a finite IEEE 754 binary64 value obtained by correctly rounded decimal conversion using round-to-nearest, ties-to-even. NaN and infinities are not values. A number whose resulting value is mathematically integral must be within `[-9007199254740991, 9007199254740991]`; larger integral quantities use strings. Negative zero is admitted but equals and canonicalizes as zero. Consequently `1` and `1.0` are the same JSON/1 value, while the integer token `9007199254740993` is invalid instead of being preserved by Python and rounded by JavaScript. ## Equality and canonical bytes Equality is recursive: - null, booleans, and strings compare by exact value; - numbers compare by their binary64 numeric value, with `-0` equal to `0`; - arrays compare by length and corresponding values in order; - objects compare by the same exact member-name set and equal corresponding values, independent of source member order. [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) serializes an already valid JSON/1 value for digests and operation identities. Canonicalization does not repair duplicate keys, invalid Unicode, an out-of-domain number, or an oversized value; those fail first. ## Absolute value limits One encoded JSON/1 document or protocol frame has these inclusive maxima: ```text UTF-8 encoded bytes 16,777,216 value depth (root is 1) 128 total value nodes, including root 262,144 members in one object 65,536 items in one array 65,536 UTF-8 bytes in one string value 8,388,608 UTF-8 bytes in one member name 1,024 bytes in one number token 128 ``` Every array item and object member value is one child value node; member names are not additional nodes. A narrower protocol, contract, or schema may impose a smaller bound only when that bound is fixed and known before the value is produced or accepted. It may never accept a value above the JSON/1 absolute maxima while claiming JSON/1 conformance. JSON/1 does not define a universal wire error name. The protocol or value seam reports the applicable failure—for example a fatal framing error, JSON-RPC invalid params, `INVALID_INPUT`, `INVALID_RESULT`, or `SCHEMA_INVALID_JSON`. --- url: https://flow.jig.md/spec/package-format.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # FLOW Package/1 > _Status: prerelease specification candidate._ A FLOW package is one immutable logical file tree. Its only required file is an exact-case root `FLOW.md`. A package may add zero or one obvious root implementation named `flow.`. Package identity depends only on the tree's canonical paths and exact file bytes. It does not depend on Git, a package registry, the source directory, or the runtime chosen by a host. ## 1. Package tree A source adapter selects one component subtree and stages every descendant regular file. `FLOW.md` must be at the staged root. Package/1 has no ignore file, package-manager filtering, dependency exclusion, or generated-file exception: a regular file inside the selected tree is package content. Empty directories have no Package/1 meaning. Symlinks and other special files are invalid. A filesystem adapter rejects a multiply linked source inode unless it can prove that every alias is inside the same selected, nonprotected tree. Accepted hardlinks become independent path-and-content records; inode identity is not package data. Inspection and execution use the same privately staged bytes. They never reopen visible source after its package digest has been computed. A mutable directory adapter uses descriptor-relative reads and retries detected changes; it must not claim atomic source-snapshot provenance when its source mechanism does not provide it. Two source mechanisms produce the same Package/1 digest exactly when they present the same canonical logical file map. ## 2. `FLOW.md` Metadata/1 `FLOW.md` is valid UTF-8 without a BOM and contains Unicode scalar values only. It begins with an exact `---` delimiter line and ends its frontmatter at the next exact `---` delimiter line. Delimiters may use LF or CRLF independently. The bytes through the closing delimiter are limited to 262,144 bytes. The remaining bytes are the Markdown body and retain their exact line endings. The minimum document is: ```yaml --- name: resize-image description: Resize an image to the requested dimensions. --- ``` `name` is a `LocalName`: 1–64 lower-ASCII characters matching: ```text [a-z0-9]+(?:-[a-z0-9]+)* ``` It is a friendly package-local label, not global identity. `description` is 1–16,384 Unicode scalars of human-readable text. Metadata/1 has this closed unnamespaced vocabulary: ```text name required LocalName description required non-empty string uses optional map of capability slots outcomes optional map of custom outcome descriptions attachments optional map of attachment access modes channels optional map of directional channel requirements ``` Unknown unnamespaced fields reject. An extension key is exactly `x-` followed by a `LocalName`. Its bounded JSON-shaped value is inert package metadata and can never acquire core Metadata/1 meaning. Frontmatter uses the YAML 1.2 JSON schema with string mapping keys. Exact plain `null`, `true`, `false`, and JSON-number scalars become their JSON values; other admitted scalars become strings. Duplicate keys, explicit tags, anchors, aliases, merge keys, non-string mapping keys, and implementation-specific scalar types reject. The parsed value must satisfy FLOW JSON/1 and these additional bounds: ```text nesting depth 16 total nodes 4,096 entries per map 256 items per sequence 256 ``` ### Capability uses Each `uses` key is a `LocalName`. A slot has exactly one of these forms: ```yaml uses: index: contract: ./contracts/index.capability.json scratch: local: true ``` `contract` names one package-local Capability Contract/1 descriptor. An author reference begins with exact `./`, then uses one or more canonical package path segments. It cannot contain an empty, `.`, `..`, backslash, absolute, encoded, or escaping form. The referenced regular file must exist in the staged package. A host derives contract identity, version, and digest from the descriptor. Those values are not copied into `FLOW.md`. The `local: true` form deliberately names a nonportable local seam. The two forms are mutually exclusive. ### Outcomes and attachments Each `outcomes` key is a `LocalName` with a nonempty description. `done`, `failed`, `cancelled`, and `error` are reserved and cannot be declared as custom outcomes. Each `attachments` key is a `LocalName`. Its value is exactly `read` or `read-write`. Metadata declares required attachment names and maximum access; attachment-source mapping is host policy outside Package/1. A host may expose that mapping through an explicit configuration mechanism. ### Channels Each `channels` key is a `LocalName` and declares a directional communication port, not permission to run another participant. Its fields and exact named agreement are defined by [Channel Contract/1](https://flow.jig.md/spec/channel-contracts.md). Host invocation grants supply the actual endpoints. Optional unwired ports are absent; ordinary one-shot packages need no channel declaration. ### Format evolution Metadata/1 has no `flow` or `format` field. A future core vocabulary must use a new discriminator or entrypoint convention and is invalid under Metadata/1. The Markdown body remains the package's public procedure and description; a host cannot infer that prose and an implementation are equivalent. ## 3. Implementation entrypoint A package may contain at most one root regular file whose name matches: ```text flow. ``` `suffix` is 1–16 lowercase ASCII letters or digits. Nested files and names with more than one suffix are ordinary resources. If the implementation begins with `#!`, its first line must be exactly: ```text #!/usr/bin/env ``` The line may end in LF or CRLF. `selector` is 1–64 characters matching `[A-Za-z0-9][A-Za-z0-9._+-]*`. Arguments, `env -S`, absolute interpreter paths, interpolation, and shell commands are not part of this selector. The selector identifies implementation semantics; it does not prescribe how a host installs or invokes a runtime. No entrypoint means the package contains instructions only. Package validity does not imply that a particular host can execute it. ## 4. Conventional schemas A Run package may contain these exact optional root files: | File | Value validated | | ---------------------- | --------------------------------------------- | | `input.schema.json` | Invocation input | | `settings.schema.json` | The complete immutable Run settings object | | `result.schema.json` | The complete `{ "outcome", "output" }` result | Each present file must compile as FLOW Schema/1 during inert package inspection. Without an input schema, any JSON/1 input is valid. Without a settings schema, only `{}` is valid settings. Without a result schema, the Run/1 envelope and declared-outcome rules still apply. The result schema covers the complete result so it can correlate each outcome with its output shape. ## 5. Canonical paths and limits Each logical path: - is a Unicode 15.1 NFC string; - is relative and uses `/` as its only separator; - has one or more nonempty segments; - contains no NUL, backslash, empty, `.` or `..` segment; - has at most 64 segments; - is at most 1,024 UTF-8 bytes; and - has segments of at most 255 UTF-8 bytes. The complete tree rejects NFC duplicates and collisions under Unicode 15.1 full default case folding. Case remains significant. Records are ordered by unsigned UTF-8 path bytes; host locale and filesystem enumeration order have no role. An admitted package has these absolute validity ceilings: ```text regular files 65,536 bytes in one file 1,073,741,824 sum of file contents 4,294,967,296 ``` Counters do not wrap. A host lacking capacity may report `RESOURCE_EXHAUSTED`, but must not calculate a partial identity or silently change these validity limits. ## 6. Package digest For every file, let `P` be its canonical UTF-8 path bytes and `C` its exact content bytes. Sort files by `P`, then compute: ```text SHA-256( ASCII("FLOW-Package/1\0") || u64be(fileCount) || for each file ( 0x01 || u32be(byteLength(P)) || P || u64be(byteLength(C)) || C ) ) ``` Integers are unsigned, big-endian, fixed-width values. The public rendering is `sha256:` followed by 64 lowercase hexadecimal digits. The digest excludes source location, directories, timestamps, ownership, mode bits, inode identity, runtime selection, host policy, and sandbox state. Those are provenance or admission evidence, not package identity. ## 7. Required conformance Conforming implementations must prove at least: 1. The minimum Metadata/1 document passes and every unknown unnamespaced field rejects. 2. BOM, invalid UTF-8, malformed delimiters, unsafe YAML features, invalid JSON/1 values, and every metadata bound plus one reject. 3. Zero or one root implementation passes; two reject; selector spelling is exact. 4. Every present conventional schema is compiled during inert inspection. 5. Missing, escaping, or case-mismatched author references reject. 6. Enumeration order, directories, modes, ownership, and timestamps do not change identity; path, content, or extra-file changes do. 7. Traversal, absolute, backslash, NUL, non-NFC, case-fold collision, symlink, and unproved hardlink cases reject consistently. 8. Independent streaming digest implementations produce identical results. --- url: https://flow.jig.md/spec/run-protocol.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # FLOW Run/1 > _Status: prerelease specification candidate._ Run/1 is the finite executable boundary for a FLOW package. It deliberately does not expose a host's durable records, resolver, authority evidence, graph model, provider identities, or application ontology. Invocation and cancellation use: ```text host -> component flow/run component -> host flow/run-child component -> host capability/call either direction request/cancel ``` The channel extension adds component-to-host `channel/create`, `channel/subscribe`, `channel/send`, `channel/next`, `channel/close`, and `channel/release`. Channels carry bounded JSON values during work; they do not start work, authorize control operations, or replace invocation results. `flow/run` supplies the complete context to an already selected component. `flow/run-child` asks the host to resolve an admitted child slot and construct another invocation. They have distinct parameter shapes, not an overloaded method selected by direction. The method name grants no authority; the host enforces the caller's existing invocation authority before child dispatch. The end-to-end operation is Flow A running Flow B through the host: ```text host -> Flow A: flow/run (context for A) Flow A -> host: flow/run-child (slot, operation identity, input) host -> Flow B: flow/run (context for B) ``` A child is an invocation relationship, not a different kind of Flow package. Every component receives the same `flow/run` entry request. ## 1. Process and framing One component process handles exactly one root `flow/run` request. The channel is full-duplex JSON-RPC 2.0 over stdio. Here "root" means the owner request on this component's channel, including when the host launched it as a child: ```text stdin protocol frames only stdout protocol frames only stderr unstructured diagnostics only ``` Each stdin or stdout frame is one JSON object encoded as UTF-8 and terminated by one LF byte. The LF is not part of the frame payload. A frame has at most 16,777,216 bytes before LF. Empty frames, JSON-RPC batches, a BOM, invalid UTF-8, duplicate object members, lone Unicode surrogates, and values outside [`FLOW JSON/1`](https://flow.jig.md/spec/json-values.md) are invalid. EOF after nonempty bytes without LF is an incomplete frame. Both peers keep their reader active while awaiting responses. Writes are serialized and flushed. A component may have at most 64 component-originated requests awaiting responses at once; an SDK may queue additional calls rather than put them on the wire. A host sends exactly one `flow/run` request. Request IDs are 1–128 byte ASCII tokens matching: ```text [A-Za-z0-9][A-Za-z0-9._:/-]* ``` Each sender chooses its own ID namespace and never reuses an ID during one channel incarnation, including after the request settles. Opposite directions may coincidentally use the same spelling. IDs correlate wire messages only; they are not durable Run or operation identities. Numeric and `null` request IDs are invalid in Run/1. Each sender may originate at most 65,536 request-form messages during one channel incarnation. Settled and cancelled requests still consume this lifetime budget; responses and notifications do not. A request with a fresh, structurally valid ID consumes the budget before method dispatch, including when its method is unknown or its method params are invalid. A conforming SDK rejects an attempted 65,537th outbound request locally with `RESOURCE_EXHAUSTED` and emits no frame, so its root handler may recover. A receiver which observes a 65,537th request records a fatal `PROTOCOL_ERROR` and closes without relying on another response. This fixed lifetime bound is separate from the limit of 64 unresolved component requests on the wire. Channel implementations reserve at least one simultaneous request slot and enough remaining request IDs to close/release already allocated endpoints and pending allocations. Ordinary admission stops before consuming these reserves. Repeated failed settlement attempts cannot consume unlimited reserved capacity. Cancellation, host revocation and fencing never depend on a free ordinary slot. Whenever a request or notification includes `params`, that member must be a JSON object or array. `null` and scalar `params` make the complete JSON-RPC envelope invalid; they are not method-level invalid parameters. An unknown well-formed request method receives JSON-RPC `-32601`. A recognized request with invalid params receives `-32602`. Unknown notifications are ignored. The direction restrictions in this specification are part of method validity. For a complete, bounded, LF-terminated, valid-UTF-8 frame which fails JSON/1, a peer should best-effort send `-32700` with `id: null`, flush, and close. This includes JSON syntax errors, a BOM, duplicate members, lone surrogates, and out-of-domain JSON/1 numbers or value bounds. For a parsed batch, non-object, or invalid JSON-RPC envelope it should best-effort send `-32600` with `id: null` or an unambiguous valid ID, then close. Invalid UTF-8, an oversized or incomplete frame, an unknown/duplicate response, reuse of a request ID, and a failed write close the channel without relying on another frame. Every fatal close stops admission, cancels owned work, and fails local pending calls under this exact split: - `PROTOCOL_ERROR` means a detected framing, JSON/1, envelope, correlation, or protocol-state violation by the peer; - `CHANNEL_LOST` means EOF, read/write failure, or transport loss without a detected peer-protocol violation. A valid standard JSON-RPC error received in response to a correctly emitted Run/1 request proves that the peers disagree about the fixed protocol. It is a fatal `PROTOCOL_ERROR`, not a per-call `OperationError`. A peer must never rely on a best-effort error frame arriving over a corrupted channel. State-dependent request handling is fixed as follows: | Input | Receiver behavior | | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Request-form method unavailable in this direction, including request-form `request/cancel` | Respond `-32601`; the channel may continue. | | Notification other than a valid `request/cancel`, including notification-form request methods | Ignore it. | | Recognized method with invalid params | Respond `-32602`. An invalid root request then ends the one-Run component process. | | Second `flow/run` | Best-effort `-32600`, then fatal `PROTOCOL_ERROR`. | | `flow/run-child` or `capability/call` without a pending root owner | Respond `OWNER_CLOSED`; do not dispatch. | | 65th simultaneously pending component request | Respond `RESOURCE_EXHAUSTED` to that request; do not dispatch it. | | 65,537th request originated by one peer during the channel lifetime | Fatal `PROTOCOL_ERROR`; do not dispatch it or rely on a response. | | Component frame after its root terminal response | Fatal `PROTOCOL_ERROR`; any buffered success is invalidated. | After sending its root terminal response, a component may immediately stop reading and exit. A host which sends later traffic is itself nonconforming and cannot require a response. ## 2. Shared names and values `slot`, `method`, declared effect-error `name`, and Run `outcome` use the Package/1 `LocalName` grammar: ```text [a-z0-9]+(?:-[a-z0-9]+)* ``` Their length is 1–64 ASCII characters. Whether an outcome is `done` or one of the package's declared custom outcomes is checked against the exact package, not inferred from this wire grammar. `operationId` uses the request-ID token grammar and limit. It is stable semantic identity chosen by component code, not a transport ID generated by an SDK. All `input`, `output`, settings, effect values, error data, and error details are bounded JSON/1 values. `settings` is always an object. Output and declared effect-error data are required; use `null` when no more meaningful value exists. `deadlineUnixMs` is a nonnegative JSON/1 safe integer containing the host-authoritative UTC Unix epoch deadline in milliseconds. It is advisory to component cleanup; the host remains responsible for cancellation and enforced termination. Child Flow and effect calls inherit the remaining root deadline and cannot supply or widen one. ## 3. `flow/run` The host's only request is: ```json { "jsonrpc": "2.0", "id": "host:1", "method": "flow/run", "params": { "protocol": "run/1", "input": {}, "settings": {}, "attachments": { "source": { "path": "/workspace/source", "access": "read" } }, "scratch": "/workspace/scratch", "deadlineUnixMs": 1787558400000 } } ``` `params` has these required fields and optional `channels`: | Field | Meaning | | ---------------- | -------------------------------------------------------------------------- | | `protocol` | Exact literal `run/1`; a cheap activation-mismatch guard, not negotiation. | | `input` | Actual invocation input. | | `settings` | Complete invocation-stable configured settings object. | | `attachments` | Map from `LocalName` to one sandbox-local root and access mode. | | `scratch` | Nonempty sandbox-local private read-write root path. | | `deadlineUnixMs` | Finite host-enforced root deadline. | `channels`, when present, maps at most 256 `LocalName` entries to distinct host-granted endpoints defined below. Absence means an empty map. Each attachment has exactly: ```json { "path": "/sandbox/path", "access": "read" } ``` `path` is a nonempty runtime-native path inside the already prepared sandbox. `access` is exactly `read` or `read-write`. The outer execution envelope, not an SDK path helper, enforces the view. One Run has at most 256 named attachments. There is no Run ID, parent ID, trigger field, correlation field, provider identity, slot inventory, enforcement receipt, or negotiated limit object in Run/1. A triggering fact belongs in `input`; lifecycle and authority evidence remain host-side. A successful result is exactly: ```json { "outcome": "done", "output": null } ``` Protocol, execution, cancellation, deadline, provider, or uncertainty failures are JSON-RPC failures. They never masquerade as package outcomes. ## 4. `flow/run-child` While its root request is pending, the component may issue child Flow requests: ```json { "jsonrpc": "2.0", "id": "component:1", "method": "flow/run-child", "params": { "operationId": "research:1", "slot": "research", "intent": "Find and justify a suitable comparison target.", "input": {} } } ``` `operationId`, `slot`, and `input` are required. Here `slot` is the consumer-local name of one child-Flow resolution slot admitted by the host. It is not a capability slot and is not declared in `FLOW.md` `uses`; child-Flow targets and candidate sets are host configuration outside Package/1. `intent` is an optional 1–16,384 scalar string. It describes the requested work for that already admitted slot; it does not name candidates, configure a resolver, grant catalogue access, or widen authority. Exact host binding still wins, and an absent intent never means catalogue-wide discovery. It is not delivered to the child unless component code also includes that information in `input`. Optional `channels` maps the callee's declared channel names to held endpoint reference strings. Success returns the same complete `{ outcome, output }` value as `flow/run`. Run/1 carries bounded JSON/1 `input` and explicitly mapped channel rights into the requested operation and returns its complete JSON/1 Run result. It does not define how a host selects or configures the admitted child. Nested execution is a host operation, not graph merging or implicit settings, attachment, or authority inheritance. Hosts may impose smaller concurrency or retained-result budgets and report `RESOURCE_EXHAUSTED`; the Run/1 identity, replay, cancellation, and lifetime rules still apply. ## 5. `capability/call` While its root request is pending, the component may call one method through a bound capability slot. Unlike a `flow/run-child` slot, this slot is a capability dependency declared by the package's `FLOW.md` `uses` entry: ```json { "jsonrpc": "2.0", "id": "component:2", "method": "capability/call", "params": { "operationId": "artifact-write:1", "slot": "artifacts", "method": "write", "input": {} } } ``` The params have the four shown fields and optional `channels`, using the same endpoint-reference mapping as child calls. A successful capability method returns exactly one of: ```json { "value": null } ``` ```json { "error": { "name": "not-found", "data": {} } } ``` The second form is an application error declared by the exact capability contract, not a JSON-RPC error. Both `value` and `error`, neither member, an unknown member, or missing error `data` is invalid. ## 5.1 Channels This extension supports finite, single-writer JSON/1 sequences. A direct source has one receiver; a broadcast source has independently bounded subscriptions. Arbitrary byte transports, persistent streams and continuing Agent control are not part of this extension. An endpoint grant is a closed object: | Field | Meaning | | --------------- | -------------------------------------------------------------------------------------------------------------------------- | | `endpoint` | Opaque reference using the request-ID token grammar, bound to the receiving invocation | | `direction` | `send` or `receive` | | `delivery` | `direct` or `broadcast` | | `contract` | Optional exact `{id, version, digest}` [Channel Contract/1](https://flow.jig.md/spec/channel-contracts.md) source identity | | `startSequence` | Required positive JSON/1 integer for receivers; `1` for direct delivery; absent for senders | Endpoint possession grants only the indicated communication right. It is not portable JSON authority: a string copied into ordinary input grants nothing. The creating Run owns the source's lifetime. An unused endpoint may transfer through an exact child or capability call; its cleanup owner does not change. First local use claims a right. Inspecting metadata does not claim it. Broadcast creation also grants subscription authority to the creating invocation only. Its opaque source reference uses the endpoint token grammar but is not an endpoint and cannot be transferred in a call map. Moving a writer does not move subscription authority. Each successful subscription allocates a distinct reader starting at the next accepted sequence. Late subscriptions receive no history; preallocated readers may lag even before their first read. Source sealing or failure prevents new subscriptions. All allocations count over the owner's lifetime; releasing one does not replenish that allowance. Before dispatch, the host atomically validates all mapped rights, declaration requirements, contracts, provider support, delivery, queued-prefix constraints and capacity, then moves the rights and constructs the callee's grants. Rejected admission changes no rights. Exact operation joins precede moved-right rejection; changed mappings conflict. An SDK offer or failed final result does not establish whether rights moved. The host retains authoritative ownership. No cross-root connection, ambient lookup or late injection into an ungranted running participant is implied. Disposing a receiver does not revoke its peer's unused send right. While the source owner remains live and the source is neither failed nor sealed, that sender remains transferable under the same checks, even after receiver disposal. For direct delivery, its sends and close still fail `DISCONNECTED`; transfer cannot revive delivery. Broadcast delivery continues for other readers and permits later subscriptions while the source remains open. A disposed receiver is not transferable. Required ports require a grant, not a guarantee that the peer remains available. This keeps observation disposal independent of whether producer admission wins or loses that race. | Request | Closed parameters | Successful result | | ------------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `channel/create` | Optional `delivery: "direct"` or `"broadcast"`; optional `schema` or package-local `contract`, mutually exclusive | Direct: `{send: grant, receive: grant}`; broadcast: `{send: grant, source: opaqueReference}` | | `channel/subscribe` | `{source: opaqueReference}` | One broadcast receive grant owned by the creating invocation | | `channel/send` | `{endpoint, value}` | `null`: validated, snapshotted host acceptance, not processing or durability | | `channel/next` | `{endpoint}` | `{item: {sequence, value}}` or `{end: {lastSequence}}` | | `channel/close` | `{endpoint}` | `null`: writer sealed | | `channel/release` | `{endpoint}` | `{status: "released"}`, `{status: "ended", lastSequence}`, or `{status: "failed", code, details?}` | Creation defaults to direct delivery and generic JSON/1. Schemas use Schema/1; named references resolve only against the invoking package. Unsupported valid requests fail operationally before allocation or dispatch. Invalid RPC shapes retain the ordinary incompatibility rules. The host assigns accepted sequences starting at one. Receivers validate a contiguous interval from their immutable `startSequence`; a clean end equals the last delivered sequence (`startSequence - 1` for an empty interval). There is one outstanding read and no SDK prefetch. Direct full queues exert bounded sender pressure; receiver disposal rejects pending and future sends with `DISCONNECTED`. Broadcast acceptance does not wait for readers. A reader exceeding its capacity fails with sticky `LAGGED`, independently of the writer and other readers. A reader-schema failure likewise affects only that subscription. With no readers, accepted values advance sequence and source byte budget but are not retained. Source-invalid or oversized values instead fail the entire source before acceptance. Source/writer constraints apply before per-reader validation; adding a reader never adds its validator to a broadcast source. Connection admission still rejects known conflicting nontrivial writer/reader schemas in either binding order, including all staged mappings and existing live subscribers. The host bounds queues, retained/in-flight payload, pending sends, parser/writer buffers and lifetime allocations. A committed but unread response consumes receiver capacity until the next read releases its credit. Writer close rejects while sends remain unaccepted; otherwise it seals without waiting for the receiver or execution result. Accepted data can drain while the source owner lives. Failure before sealing aborts the source. Previously explicitly sealed intervals remain sealed despite subsequent producer failure; neither sealing nor receiver EOF establishes execution success. Failure discards only uncommitted queued values. A committed read response may still arrive; the next read or release reports the sticky cause. Revocation and clean-end commitment are atomically ordered by the host. Local read cancellation may win before SDK end exposure. Root cancellation independently prevents execution success. Cancelling an active read disposes its receiver. The SDK retains and drains the original read response and disposal response; it cannot restart iteration after silently skipping a committed item. Cancelling a send retracts only an unaccepted pending value, never an accepted value or permission to replay it. Cancelling a close/release wait retains its settlement action. Cancelling a creation or subscription wait does not prove that no allocation occurred. The SDK retains the wire response and disposes any late grants before completion; it cannot lose or replay an uncertain allocation. A newly allocated broadcast receiver is active owned work even if never read. Its holder must finish or dispose it; finalization cannot silently turn abandonment into success. Uncancelled receiver disposal waits for its response and all prior reads. It exposes a previously unexposed terminal failure even if a read waiter was cancelled before that failure arrived. An internally known cause is not exposed until a public operation rejects/raises with it. Repeated disposal joins the same action and does not rethrow an already exposed cause. Disposal that wins before an earlier source failure means deliberate incomplete delivery, not a new failure. Observation cancellation does not cancel producer execution. Independent readers/writers must remain serviced while operations are pending. A blocked transport cannot prevent other participants' progress or host fencing. Control has priority only between complete frames; no delivery is promised over a broken or persistently blocked pipe. ## 6. Operation identity `operationId` is scoped to the one root Run. For observable Run/1 behavior: ```text same operationId + same canonical method/params joins or returns the same operation result same operationId + different canonical method/params OPERATION_CONFLICT before another dispatch ``` The canonical comparison includes `flow/run-child` or `capability/call`, slot, method when present, intent and channel mappings when present, and input. Transport IDs and wait timing are not semantic input. The host chooses its persistence strategy; Run/1 does not standardize a ledger schema, activation digest, internal lifetime IDs, or recovery database. Each transport request is one waiter even when several requests join the same operation. Cancelling one request settles or removes only that waiter; it does not cancel another waiter. The receiver may request cancellation of shared underlying work only after no live waiter remains. A terminal operation result which won the receiver's atomic race may still settle a concurrent cancellation; otherwise the cancelled waiter receives `CANCELLED` while other waiters remain attached. An operation is never automatically replayed after unprovable dispatch. Uncertain completion is returned as `UNCERTAIN`. A deliberate new attempt uses a new `operationId`. ## 7. Errors Run/1 uses standard JSON-RPC `-32700`, `-32600`, `-32601`, `-32602`, and `-32603` with their standard meanings. Operational failures use numeric code `-32000` and this exact data shape: ```json { "code": "UNAVAILABLE", "details": null } ``` `details` is optional bounded JSON/1 diagnostic data. `message` is required by JSON-RPC, contains 1–1,024 Unicode scalar values, and is non-normative human text. The closed wire-visible code set is: | Code | Normative meaning | | -------------------- | ------------------------------------------------------------------------------------------------------------------ | | `CANCELLED` | Cooperative cancellation won the terminal race. | | `DEADLINE_EXCEEDED` | The host-authoritative deadline won the terminal race. | | `OWNER_CLOSED` | The root stopped admitting owned work before this operation could be admitted or settled. | | `OPERATION_CONFLICT` | The operation ID was already used with different canonical method or params. | | `UNAVAILABLE` | No admitted target or provider was callable for this operation. | | `PERMISSION_DENIED` | Host authority policy refused the requested operation. | | `RESOURCE_EXHAUSTED` | A declared protocol or host capacity bound prevented admission or completion. | | `INVALID_INPUT` | Application input failed the selected Flow or capability's declared validation after the Run/1 envelope was valid. | | `INVALID_RESULT` | A component, child Flow, or capability produced an invalid declared result. | | `UNCERTAIN` | Dispatch may have occurred, but a trustworthy terminal result cannot be proved. | | `EXECUTION_FAILED` | Admitted application work failed and no narrower code above applies. | | `LAGGED` | Channel delivery failed because its bounded observation capacity was exceeded. | | `DISCONNECTED` | The connected receiver was disposed or its communication right ended. | JSON-RPC `-32602` means the Run/1 method params themselves are invalid; `INVALID_INPUT` is downstream application validation. JSON-RPC `-32603` means the peer failed while processing the protocol; `EXECUTION_FAILED` means the admitted application work failed. Every request has one atomic terminal decision. A committed result or error wins a later cancellation or deadline observation. Otherwise the first recorded cancellation/deadline terminal condition determines `CANCELLED` or `DEADLINE_EXCEEDED`; no peer sends two terminal responses. That decision fixes the request-level result or error. Overall Run acceptance also requires the clean process completion described in Section 9. Host-internal distinctions such as which host binding, provider, runtime selection, or execution envelope was unavailable belong in durable host diagnostics, not the portable error taxonomy. Activation failures which occur before a channel exists are not Run/1 errors. `PROTOCOL_ERROR` and `CHANNEL_LOST` are local terminal classifications, not `-32000` values sent over a channel which can no longer be trusted. The machine-readable registry is [`run-1-errors.json`](https://flow.jig.md/schemas/run-1-errors.json). ## 8. Cancellation The originator of a pending request may send this notification: ```json { "jsonrpc": "2.0", "method": "request/cancel", "params": { "requestId": "component:1" } } ``` It has no `id` and receives no response. A valid target must have been originated by the notifier on this channel. Duplicate, stale, already-terminal, unknown, and opposite-direction targets are harmless no-ops because response and cancellation may race. A malformed cancellation notification is fatal: the sender cannot safely assume it requested cancellation. Cancellation closes new work for that request and asks the receiver to stop. It does not prove that dispatched external work was undone. The original request still receives at most one eventual result or error if the channel survives. Root cancellation applies to the complete component-owned subtree. An SDK must propagate it to the handler and cancel its pending outbound waits. The host enforces the deadline and terminates an uncooperative process after its private grace policy. ## 9. Completion and exit The component admits outbound calls only while `flow/run` is pending. It must settle every outbound request before returning the root result or error. Calls cannot be detached, reparented, or transferred. A normal root response with outstanding owned work cannot become success. Recoverable settled failures use ordinary language recovery; there is no error acknowledgement or global settled-failure ledger. An ignored, already-settled recoverable failure may escape detection. Fatal loss of this invocation's transport, root cancellation, unresolved ownership or failed cleanup still prevent success. A child that is conclusively fenced and cleaned may instead return a recoverable parent-local operational error; fatality follows the affected owner, not a word in an error message. Before implicit writer sealing, account for handler error or invalid result, fatal/cancellation state, abandoned live work and active unfinished receivers. Capture abandonment at handler settlement; cleanup does not erase it. Resolve retained cancellation/disposal accounting and recheck eligibility. The host, which knows actual endpoint transfers and source state, seals only healthy held writers after eligibility; a known disqualifier aborts unsealed sources. A wholly unused unconnected pair can be released automatically. An activated receiver must reach end, terminal failure or explicit disposal. Recheck fatal and cancellation state before accepting the execution terminal. The host must keep the component's stdin open while the root request is pending. After receiving and validating the complete root terminal response, it may close that sending half immediately. The component must tolerate this causally later half-close. Component stdin and stdout are independent byte streams; Run/1 infers no physical wall-clock ordering between them. The component locally linearizes a terminal response by claiming publication immediately before its serialized transport write begins. An stdin EOF, framing failure, or fatal protocol condition prevents the root response only when the component's protocol state machine atomically claims it before that publication claim. Once publication has started, later stdin state cannot overturn it: the complete write and runtime-buffer flush wins, while a write failure is `CHANNEL_LOST`. Flush is not `fsync`, a peer acknowledgement, or proof that the host received the frame. Kernel or runtime buffering does not establish cross-pipe order; bytes merely read or queued by the transport have no priority. After publication starts, the component may stop reading; unread or subsequently observed host input has no response guarantee. After receiving a root response, the host continues draining component stdout and awaits process termination. It accepts the Run only when the response is valid and correlated, no component-originated request remains pending, stdout ends on a frame boundary with no trailing bytes or frames, and the process exits zero. The supervising host classifies premature termination deterministically: - a host-enforced kill after a recorded cancellation or deadline is `CANCELLED` or `DEADLINE_EXCEEDED` respectively, unless a terminal response had already won; - a detected peer-protocol violation is `PROTOCOL_ERROR`; - a nonzero process exit without either condition is `EXECUTION_FAILED`; - component-stdout EOF, transport loss, or a zero exit before a complete response is `CHANNEL_LOST`. A response fixes the request decision but does not by itself satisfy those acceptance conditions. A nonzero exit, pending owned work, invalid output, trailing component bytes or frames, or a post-response shutdown kill prevents overall success. A post-response shutdown kill is an `EXECUTION_FAILED` lifecycle failure rather than a later cancellation or request deadline winning the already-fixed request decision. Cleanup failure must surface through process exit; there is no hidden cleanup-acknowledgement protocol. ## 10. Machine interface and SDK projection The closed message schema is [`run-1.json`](https://flow.jig.md/schemas/run-1.json). Direction and response correlation remain protocol state and cannot be proven by a context-free JSON Schema document. Framing and JSON/1 validation happen before schema validation. The TypeScript package `@jigging/flow` and Python distribution/import `jiggy-flow`/`jiggy.flow` expose the same semantic surface: ```text handle(handler) RunContext RunResult runChildFlow / run_child_flow callCapability / call_capability channel / channels and directional endpoint types OperationError CapabilityError JSON value types ``` They do not expose JSON-RPC envelopes, transport IDs, pending tables, resolution, host bindings, providers, schema loading, sandboxes, administration controls, Services, Agents, or graph types. `operationId` remains caller-supplied. The exact language projections and their cancellation/error behavior are closed in [`Run SDK/1`](https://flow.jig.md/spec/run-sdk.md). The corpus under `conformance/run-1/` provides executable evidence for this candidate through separately implemented TypeScript and Python participants. Passing that repository corpus is not a certification of a third-party implementation. --- url: https://flow.jig.md/spec/run-sdk.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # FLOW Run SDK/1 > _Status: prerelease SDK projection of [`FLOW Run/1`](https://flow.jig.md/spec/run-protocol.md)._ 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 `jiggy-flow`, imported as `jiggy.flow`. These root modules expose Run SDK/1 only. Both expose only: ```text handle RunContext RunResult OperationError CapabilityError JSON value types attachment types handler types ChannelSender / ChannelReceiver / ChannelEndpoint / ChannelPair ChannelContractIdentity ``` The TypeScript projection additionally names `ChildFlowRequest`, `CapabilityCall`, `CallOptions`, and `ChannelOptions`; Python expresses the same values as keyword-only method arguments and uses ordinary task cancellation. `handle` receives this Flow's invocation. `runChildFlow` / `run_child_flow` requests a Flow through an admitted child slot and returns its complete Run result. `callCapability` / `call_capability` calls a named capability method. These map to distinct `flow/run`, `flow/run-child`, and `capability/call` wire requests; authors do not construct the host-supplied invocation context. `handle` owns protocol stdin and stdout for the process and handles exactly one root Run. The TypeScript SDK captures its transport first, then replaces the global console with one backed by stderr; an imported library that reads the current global console during the handler is therefore safe. The Python SDK routes ordinary `print()` and `sys.stdout` output to stderr. Redirection remains installed after the one-shot call so later application output cannot become trailing protocol bytes. Output from modules evaluated before `handle`, a console/stdout reference cached before `handle`, raw writes to stdout or file descriptor 1, and a child process inheriting stdout remain invalid protocol output. Bare Run/1 implementations are responsible for keeping protocol stdout clean. The SDK never treats malformed protocol output as a log. A TypeScript entrypoint may import only `handle` statically and dynamically import its application module from the handler. In that form the SDK installs console redirection before evaluating the later application graph, including its top-level console calls and any console methods it then caches. This is an authoring pattern, not a second SDK operation, and it does not make raw stdout or inherited child stdout valid. Calling other protocol, resolver, host-configuration, provider, sandbox, administration, Agent, or graph APIs through this SDK is impossible because none are exposed. ## 2. TypeScript ```ts type JsonValue = | null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue }; type RunResult = { readonly outcome: string; readonly output: JsonValue; }; interface ChildFlowRequest { readonly operationId: string; readonly slot: string; readonly intent?: string; readonly input: JsonValue; readonly channels?: Readonly>; } interface CapabilityCall { readonly operationId: string; readonly slot: string; readonly method: string; readonly input: JsonValue; readonly channels?: Readonly>; } interface RunContext { readonly input: JsonValue; readonly settings: Readonly>; readonly attachments: Readonly>; readonly channels: Readonly>; readonly scratch: string; readonly deadlineUnixMs: number; readonly signal: AbortSignal; runChildFlow(call: ChildFlowRequest, options?: { signal?: AbortSignal }): Promise; callCapability(call: CapabilityCall, options?: { signal?: AbortSignal }): Promise; channel(options: ChannelOptions & { delivery: 'broadcast' }, callOptions?: CallOptions): Promise; channel(options?: ChannelOptions & { delivery?: 'direct' }, callOptions?: CallOptions): Promise; channel(options: ChannelOptions, callOptions?: CallOptions): Promise; } type RunHandler = (context: RunContext) => Promise; declare function handle(handler: RunHandler): Promise; ``` `run.signal` reports root cancellation. If a call-specific signal is already aborted when `runChildFlow` or `callCapability` 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: ```ts try { await run.callCapability(call, { signal }); } catch (error) { if (!(error instanceof OperationError) || error.code !== "CANCELLED") { throw error; } } ``` 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 ```python class RunResult(TypedDict): outcome: str output: JsonValue class RunContext(Protocol): @property def input(self) -> JsonValue: ... @property def settings(self) -> Mapping[str, JsonValue]: ... @property def attachments(self) -> Mapping[str, Attachment]: ... @property def channels(self) -> Mapping[str, ChannelEndpoint]: ... @property def scratch(self) -> str: ... @property def deadline_unix_ms(self) -> int: ... async def run_child_flow( self, *, operation_id: str, slot: str, input: JsonValue, intent: str | None = None, channels: Mapping[str, ChannelEndpoint] | None = None, ) -> RunResult: ... async def call_capability( self, *, operation_id: str, slot: str, method: str, input: JsonValue, channels: Mapping[str, ChannelEndpoint] | None = None, ) -> JsonValue: ... async def channel( self, *, delivery: Literal["direct", "broadcast"] = "direct", schema: JsonValue = ..., contract: str | None = None, ) -> ChannelPair | ChannelBroadcast: ... RunHandler = Callable[[RunContext], Awaitable[RunResult]] def handle(handler: RunHandler) -> None: ... ``` `handle` 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 `run_child_flow` or `call_capability` 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`](https://flow.jig.md/spec/json-values.md). 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 `runChildFlow` and `run_child_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. `callCapability` and `call_capability` unwrap a successful effect `{ value }`. A declared capability error raises `CapabilityError`, 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: ```text OperationError(code, message?, details?) ``` Authors inspect `code`, `message`, and `details`; they do not branch on the human message. In Python, omitted details are represented by `None`. The thirteen 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. With channels, ordinary admission reserves at least one live slot and enough remaining lifetime request IDs for every allocated or pending-allocation endpoint's settlement. Settlement stays inside the same total wire ceilings. 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. Normal `try/catch`, `try/except`, `allSettled` and `gather` remain sufficient for settled recoverable failures. There is no acknowledgement operation or global settled-failure ledger. An ignored already-settled failure can escape detection; that does not waive live ownership, root cancellation, fatal transport loss, uncertain dispatch or failed cleanup. A conclusively cleaned child failure can remain recoverable in its healthy parent. Handler settlement and invalid result checks precede implicit writer sealing. An activated unfinished receiver is abandonment unless it reached terminal failure or explicit disposal. Previously explicitly sealed intervals are not retracted by later producer failure. The host alone knows actual transfer and source state and performs eligible implicit sealing; SDKs never infer moved rights from final call outcomes. Retained read/disposal settlements finish before submitting the invocation terminal. The deadline is exposed as context, not implemented as an SDK timer. The host is responsible for enforcing it and terminating an uncooperative process. ## 7. Minimal root examples TypeScript: ```ts import { handle } from "@jigging/flow"; await handle(async (run) => ({ outcome: "done", output: run.input })); ``` Python: ```python from jiggy.flow import RunContext, RunResult, handle async def run(context: RunContext) -> RunResult: return {"outcome": "done", "output": context.input} handle(run) ``` These examples require only the root `flow/run` operation. Availability of child-Flow and effect slots is host configuration; calling an unavailable slot returns `UNAVAILABLE`. ## 8. Child-call examples A child-Flow slot may execute the following TypeScript: ```ts import { handle } from "@jigging/flow"; await handle(async (run) => { const child = await run.runChildFlow({ operationId: "research:1", slot: "research", input: run.input, }); return { outcome: "done", output: child.output }; }); ``` The equivalent Python is: ```python from jiggy.flow import RunContext, RunResult, handle async def run(context: RunContext) -> RunResult: child = await context.run_child_flow( operation_id="research:1", slot="research", input=context.input, ) return {"outcome": "done", "output": child["output"]} handle(run) ``` When a host binds `research`, JSON/1 `input` and any explicitly mapped channel rights cross through this SDK operation; the complete JSON/1 `RunResult` returns separately. Child context, authorization, and resource policy belong to the host; the SDK creates no implicit inheritance. The caller-supplied `operationId` retains all Run/1 join, conflict, cancellation, and uncertainty semantics. Uncertain dispatch is not automatically replayed. The SDK creates no separately addressable child history, administration, scheduler, catalogue, resolver, or Agent-specific surface. A host may impose a lower child-concurrency limit and report `RESOURCE_EXHAUSTED`. The Run/1 request-lifetime limit still applies. The implementations live under `packages/flow-sdk/` and `packages/jiggy-flow/`. The shared executable seed is [`conformance/run-1/`](https://github.com/jiggy/jig/tree/main/conformance/run-1). ## 9. Channel projection TypeScript names the following interfaces; Python exposes corresponding protocols, with `start_sequence`, `aclose()`, `__anext__()` and async context management on receivers. Python channel options are keyword-only arguments. ```ts interface ChannelContractIdentity { readonly id: string; readonly version: string; readonly digest: string; } type ChannelOptions = { readonly schema?: JsonValue; readonly contract?: string; } & ({ readonly delivery?: 'direct' } | { readonly delivery: 'broadcast' }); interface ChannelSender { readonly direction: 'send'; readonly delivery: 'direct' | 'broadcast'; readonly contract?: ChannelContractIdentity; send(value: JsonValue, options?: CallOptions): Promise; close(options?: CallOptions): Promise; } interface ChannelReceiver extends AsyncIterableIterator { readonly direction: 'receive'; readonly delivery: 'direct' | 'broadcast'; readonly contract?: ChannelContractIdentity; readonly startSequence: number; next(options?: CallOptions): Promise>; close(options?: CallOptions): Promise; } type ChannelEndpoint = ChannelSender | ChannelReceiver; interface ChannelPair { readonly send: ChannelSender; readonly receive: ChannelReceiver; } interface ChannelBroadcast { readonly send: ChannelSender; subscribe(options?: CallOptions): Promise; } ``` Python absent contract identity is `None`. `schema` and `contract` are mutually exclusive, including explicit `schema: true`. Endpoint references remain private SDK/host machinery, never ordinary input data. Literal direct creation returns `ChannelPair`; literal broadcast returns `ChannelBroadcast` in both SDKs. Python supplies matching typing overloads and `await source.subscribe()`. Only the creator retains `subscribe`; transferring `source.send` leaves that authority with its creator. Each subscription gets a separate reader with an immutable starting sequence. Subscribe before producer dispatch when a consumer requires the beginning. Late readers receive only a suffix, not retained history. An allocated subscription is active work even before its first read and must finish or be disposed. Cancelling creation or subscription retains settlement and disposes late grants; failed cleanup cannot be recovered as success. Broadcast sends do not wait for readers. A slow reader fails locally with `LAGGED`; a receiver-schema failure is likewise local. Both are distinct from a source/writer error, which aborts unsealed output for all readers. The caller still awaits execution independently and handles recoverable channel errors with ordinary language constructs. There is one iterator and one pending read per receiver, without prefetch. TypeScript iterator `return()` disposes on early loop exit. Python early exit requires `async with receiver` or explicit `aclose()` in `finally`; a bare `async for` break is insufficient. Fully exhausting either iterator is enough. Cancelling an active read starts retained receiver disposal. An uncancelled close joins that disposal and every prior read/tombstone, exposing the first previously unexposed terminal cause. A cause received internally after its public read was cancelled has not yet been exposed. Repeated close suppresses that same cause only after a public operation raised/rejected with it. Cancelling a close waiter does not cancel settlement or lose its later cause. Ordinary language recovery applies; no acknowledgement or query operation is needed. Local failure to start a read is an operation failure, not proof that its endpoint ended. A fatal current-connection error remains fatal even when caught. Sender acceptance, receiver end and the separate execution result retain their different meanings under [Run/1](https://flow.jig.md/spec/run-protocol.md#51-channels). An application must start producer work before awaiting its first message. In Python, assigning a coroutine alone does not start it. `gather` starts both coroutines below and retains both outcomes: ```python updates = await run.channel(contract="./contracts/public-updates.json") async def invoke(): try: return await run.call_capability( operation_id="answer", slot="agent", method="run", input=run.input, channels={"events": updates.send}, ) except (Exception, asyncio.CancelledError): # Rejected admission may leave no producer to end the stream. try: await updates.receive.aclose() except (Exception, asyncio.CancelledError): pass # Preserve the execution failure. raise async def observe(): try: async with updates.receive: async for value in updates.receive: print(value, flush=True) except OperationError as error: if error.code not in {"LAGGED", "DISCONNECTED"}: raise print("Progress delivery was incomplete.", flush=True) execution, observed = await asyncio.gather(invoke(), observe(), return_exceptions=True) if isinstance(execution, BaseException): raise execution if isinstance(observed, BaseException): raise observed answer = execution ``` This excerpt assumes an admitted Agent-like capability and matching package-local channel contract. Application code interprets/filter values and the actual final answer. Neither that capability's meaning nor a logging sink is part of FLOW. --- url: https://flow.jig.md/spec/schema-files.md --- > For AI agents: the complete documentation index is available at https://flow.jig.md/llms.txt, the full documentation bundle is available at https://flow.jig.md/llms-full.txt. # FLOW Schema/1 files > _Status: prerelease specification candidate. The machine meta-schema is > published as [`schema-1.json`](https://flow.jig.md/schemas/schema-1.json)._ FLOW packages may expose three fixed, inert JSON Schema files. They describe values; they are never runtime mailboxes, configuration stores, templates, or code. | File | Exact value validated | | ---------------------- | ---------------------------------------------------- | | `input.schema.json` | `flow/run.params.input` | | `settings.schema.json` | `flow/run.params.settings` | | `result.schema.json` | The complete normal `{ "outcome", "output" }` result | The third name is `result`, not `output`, because a package may declare several outcomes whose legal output shapes differ. Validating the complete value lets a schema express that correlation; an output-only schema could not. ## 1. Absence has exact semantics - Run `settings` is always a JSON object, whether or not a schema exists. Arrays, scalars, and `null` are never settings values. - Without `input.schema.json`, any value admitted by the bounded FLOW JSON data model is valid input. - Without `settings.schema.json`, the only valid settings value is `{}`. A package must contain a settings schema to expose a configurable seam. - Without `result.schema.json`, any result satisfying the Run/1 base envelope and declared-outcome rules is valid. Absence never asks a host to infer a schema from TypeScript, Markdown, environment variables, defaults, examples, or an earlier invocation. ## 2. Validation points The host parses and validates every present schema while creating the inert package snapshot, before package code or instructions can run. Settings are first required to be one complete JSON object and are then validated against `settings.schema.json` before runtime selection or execution. There is no inheritance, merge, per-Run overlay, environment fallback, or default insertion. A value chosen once for a configured use, such as `maxRetries`, is a setting. A value expected to vary from one invocation to another is Run input. Durable working data belongs in an attachment or bound capability. These three seams replace variable interpolation rather than hiding it elsewhere. Input is validated against the actual call value before a Run process starts. A normal component result first passes the Run/1 envelope checks: it has one declared domain outcome and an `output` value, and it is not a protocol, execution, cancellation, provider-loss, or uncertainty failure disguised as a domain outcome. After all owner work has quiesced, the host validates the complete result against `result.schema.json`; only then may owner success commit. Validation failure is `INVALID_RESULT`. ## 3. Schema/1 dialect Each file is a JSON object whose root contains this exact declaration: ```json "$schema": "https://flow.jig.md/schemas/schema-1.json" ``` Schema/1 is a closed, resource-bounded dialect of JSON Schema 2020-12. Boolean schemas are allowed below the root. A keyword is valid only in the locations and with the value shapes assigned to it by JSON Schema 2020-12. Both schemas and instances first satisfy the [`FLOW JSON/1 value model`](https://flow.jig.md/spec/json-values.md). The complete v1 keyword allowlist is: ```text $schema $defs $ref $comment title description examples type enum const allOf anyOf oneOf not if then else properties required additionalProperties minProperties maxProperties dependentRequired dependentSchemas prefixItems items contains minContains maxContains minItems maxItems minLength maxLength minimum exclusiveMinimum maximum exclusiveMaximum ``` Every other keyword is a schema error at every depth. In particular, v1 rejects: ```text $id $anchor $dynamicAnchor $dynamicRef $vocabulary default format pattern patternProperties contentEncoding contentMediaType contentSchema unevaluatedItems unevaluatedProperties multipleOf uniqueItems propertyNames ``` `format` is rejected rather than treated differently by different validator defaults. `pattern` is deferred to avoid regex-engine and resource-consumption differences. `multipleOf` is deferred to avoid cross-language numeric disagreement. `uniqueItems` is deferred because portable deep-uniqueness work is difficult to bound. `propertyNames` is deferred to avoid inventing a virtual instance-pointer identity for object keys. Values in `const` and `enum` are limited to JSON/1 scalars; structural alternatives use schema applicators. `description` and `examples` remain inert annotations. A Capability Contract descriptor which embeds them still digests the complete descriptor; “inert for validation” does not mean “excluded from interface identity.” `$defs` is allowed only on the root object. Definition names match `[A-Za-z][A-Za-z0-9]{0,63}`. `$ref` accepts only an acyclic same-document reference spelled exactly `#/$defs/` with one such definition name; percent encoding and JSON Pointer `~` escapes are not supported. Remote, relative, anchor, recursive, and dynamic resolution are invalid. Referenced definitions use the same closed dialect. For a Capability Contract/1 embedded schema graph, the descriptor's `$defs` map is the sole root definition map. Method, error, and definition schemas do not declare their own `$defs`. All embedded roots and shared definitions are compiled together so the graph-wide node, depth, and reference rules cannot be evaded by splitting an interface into many methods. Validation is pure. A conforming evaluator never coerces a value, inserts a default, removes a property, resolves a URI, executes code, or changes the instance. Number handling and equality use JSON/1 rather than host-language integer or decimal extensions. The exact same Schema/1 keyword and evaluation dialect is used by embedded input, output, and error-data schemas in Capability Contract/1; embedded schemas do not repeat the file-root `$schema` declaration. FLOW does not maintain two subtly different schema languages. ## 4. Required limits A conforming host rejects before evaluation when any one schema exceeds: ```text encoded schema file 256 KiB schema nesting depth 64 schema nodes 4,096 ``` A **schema node** is the root or an object/boolean in a schema-valued position: each `$defs` or `properties` value; each `allOf`/`anyOf`/`oneOf` or `prefixItems` item; each `not`/`if`/`then`/`else`, `additionalProperties`, `dependentSchemas`, `items`, or `contains` schema. Data under `const`, `enum`, `examples`, and annotations is not a schema node. Schema depth is the longest schema-child path with the root at depth 1. A `$ref` does not duplicate its target for structural counting. One validation has a deterministic budget of 1,000,000 **work units**. The meter is an abstract function of the parsed schema and instance, never a count of implementation actions. Each distinct `(schema JSON Pointer, instance JSON Pointer)` pair evaluated costs 1 and is memoized. Each present, semantically applicable keyword adds exactly: | Keyword | Additional units and child evaluations | | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | | `$ref` | 1, then evaluate the target against the same instance. | | `type` | 1 per allowed type name. | | `const` | 1 scalar comparison. | | `enum` | 1 scalar comparison per member, including members after a match. | | `allOf`, `anyOf`, `oneOf` | 1 per listed branch and evaluate every branch. | | `not` | 1 and evaluate its child. | | `if` | 1 and evaluate the condition; if present, charge 1 and evaluate only the selected `then` or `else`. | | `properties` | 1 per declared property; evaluate each declared property present in the instance. | | `required` | 1 per listed name. | | `additionalProperties` | 1 per instance member to classify; evaluate its schema for every member not declared by `properties`. | | `minProperties`, `maxProperties` | 1 each. | | `dependentRequired` | 1 per declared trigger plus 1 per required name for each trigger present in the instance. | | `dependentSchemas` | 1 per declared trigger and evaluate each child whose trigger is present. | | `prefixItems` | 1 per listed prefix schema and evaluate each position which exists. | | `items` | 1 and evaluate every array item after the `prefixItems` range. | | `contains` | 1 per array item and evaluate its child against every item. | | `minContains`, `maxContains`, `minItems`, `maxItems` | 1 each. | | `minLength`, `maxLength` | If either is present, charge the instance's Unicode-scalar length once, plus 1 per present bound. | | `minimum`, `exclusiveMinimum`, `maximum`, `exclusiveMaximum` | 1 numeric comparison each. | | annotations and `$defs` | 0 during instance evaluation. | Object-key collections use RFC 8785 member order and arrays use index order. All applicable branches and comparisons named above are charged even if an implementation can logically short-circuit. An optimized validator may skip physical work only if it computes the same abstract charge. Crossing the limit is `SCHEMA_LIMIT_EXCEEDED`, never a false validation result. Implementations may impose lower limits only in a separately named nonconforming local mode. They must compute the normative meter before treating an instance as merely invalid; exhaustion takes precedence. Once within the budget, they may report only the first validation error. They must not quietly skip an unsupported keyword or external reference. ## 5. Errors and inspection Schema compilation errors use stable codes: ```text SCHEMA_INVALID_JSON SCHEMA_INVALID SCHEMA_KEYWORD_UNSUPPORTED SCHEMA_REFERENCE_INVALID SCHEMA_LIMIT_EXCEEDED ``` `SCHEMA_INVALID_JSON` is reserved for invalid UTF-8, JSON syntax, duplicate members, and other JSON/1 failures. `SCHEMA_INVALID` means the parsed value violates Schema/1's root, keyword-location, or keyword-value-shape rules. Keeping those cases distinct lets a host report malformed data without misclassifying a well-formed but invalid schema as an unsupported extension. Instance rejection uses `INVALID_INPUT`, `INVALID_SETTINGS`, or `INVALID_RESULT`. Each diagnostic contains: ```text code instancePointer RFC 6901; empty when no instance location applies schemaPointer RFC 6901 keyword when applicable ``` Human wording and multi-error ordering are non-normative. Schema/1 defines no standalone public schema digest. A package schema is identified by its containing Package/1 digest and canonical logical path; an embedded schema by its containing Capability Contract/1 digest and JSON Pointer. A host may use a private cache fingerprint, but that value is not a portable identity, compatibility token, lock input, or author-facing requirement. A host may report those containing identities and locations, plus schema compilation and example-fixture failures, without evaluating package code. ## 6. Examples The companion examples are: - [`input.schema.json`](https://github.com/jiggy/jig/blob/main/docs/flow/spec/examples/schema-files/input.schema.json) - [`settings.schema.json`](https://github.com/jiggy/jig/blob/main/docs/flow/spec/examples/schema-files/settings.schema.json) - [`result.schema.json`](https://github.com/jiggy/jig/blob/main/docs/flow/spec/examples/schema-files/result.schema.json) They demonstrate shape validation and outcome/output correlation. They are examples, not implicit schemas for packages which omit the files.