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, and this page is available as Markdown at https://flow.jig.md/guide/platforms.md.
DocumentationGuidePrerelease

#Host and platform integration

View Markdown

A FLOW host lets your application invoke a reusable method with input and receive an outcome with data. The method can use code, Agent judgment, or other methods. Your host selects its implementation and supplies its local powers and execution context.

This guide is for maintainers adding that ability to a coding agent or platform. For writing a method instead, start with the SDK tutorials.

#Component entry and dependency invocation

Run/0 has two requests because the caller and host supply different information:

RequestSender → receiverResponsibility
flow/runHost → componentStart the selected component with its complete invocation context.
flow/callComponent → hostRequest an invocation through a declared dependency slot, supplying input and an operation identity.

The host resolves the slot, checks its grants, and constructs the selected implementation's context. The caller does not choose its credentials, settings, attachments, scratch directory, or deadline. A child has its own context and inherits the remaining deadline; it does not implicitly inherit other powers.

Host → Flow A: flow/run
Flow A → Host: flow/call (slot: greeter)
Host → Flow B: flow/run
Flow B → Host: result
Host → Flow A: result of the call
Flow A → Host: result of its invocation

Every launched component receives flow/run, including Flow B. A child is an invocation relationship, not a separate package type. A slot can also select a host-provided implementation without launching another component process; its input, result, authority and owned-work obligations still apply.

For an author, the SDK exposes handle(handler) to receive an invocation and context.call(...) to invoke a dependency. See Run/0 for the wire contract and Run SDK/0 for that authoring API.

#Place the host alongside ACP

The Agent Client Protocol (ACP) connects a client, such as an editor, to a coding agent. FLOW connects a host to callable method implementations. These roles are independent; ACP support does not supply a Run/0 host.

PlacementWhat your integration owns
Inside an ACP AgentThe coding agent hosts Flows as part of its work, launching components and consuming their results.
Inside an ACP Client or platformThe application hosts Flows and can supply dependencies backed by one or more ACP Agents.

In either placement, implement the same FLOW boundary. When supplying an ACP-backed dependency, an adapter must translate the dependency's declared input into the Agent's session/prompt API and validate the completed answer against its result contract. ACP updates are observations; they are not a FLOW terminal result. Own the session through completion, failure and cancellation, and distinguish a settled failure from uncertain dispatch. Use separate Agent contexts when the method requires that isolation.

The application chooses the Agent route and its grants. An arbitrary slot name or model response must not create a new route. FLOW does not define a universal ACP adapter, provider configuration, or host admission UI. Consult the ACP documentation for its session protocol; use the dependency's actual invocation contract for its data.

#Walkthrough: Servicing a declared slot with an ACP Agent

Consider a caller Flow that declares a dependency slot summarizer in its FLOW.meta.json:

{"uses":{"summarizer":{}}}

When running in an ACP Client orchestrator, the host routes calls for slot: "summarizer" to an admitted ACP Agent session. The integration follows these explicit boundaries:

  1. Declared route validation: The caller component issues a child call:
    {"jsonrpc":"2.0","id":"c:1","method":"flow/call","params":{"slot":"summarizer","operationId":"sum:1","input":{"text":"..."}}}
    The host validates that slot: "summarizer" exists in its static, operator-authorized route table. A model-generated slot name or ungranted target is refused immediately with a JSON-RPC error.
  2. Translate input to an ACP prompt: The adapter formats the declared input into the ACP Agent's prompt or session work. It supplies bounded context and instructions, preserving any attachments or scratch directories authorized for that slot.
  3. ACP updates are observations, not terminal results: During execution, the ACP Agent emits streaming notifications (text tokens, tool calls, thought updates). These are streaming observations. If the caller component declared an incoming progress channel, the host may forward updates; otherwise, they serve as host diagnostics. They do not fulfill the pending flow/call.
  4. Contract validation of the final answer: When the ACP Agent signals turn completion, the adapter extracts the final response, validates that it satisfies the slot's contract (e.g., verifying { summary: string }), and returns the complete correlated result to the caller component:
    {"jsonrpc":"2.0","id":"c:1","result":{"outcome":"done","output":{"summary":"..."}}}
  5. Cancellation and failure ownership: If the caller cancels or hits a deadline, the host sends request/cancel and the adapter terminates or cancels the ACP session (session/cancel). If the ACP Agent process crashes, disconnects, or emits malformed output, the adapter returns a settled JSON-RPC operational error. If side effects cannot be verified, the host records execution as uncertain rather than retrying automatically.
ResponsibilityWho implements it
Transport, process launch & ACP session lifecycleThe maintainer / platform integration
Run/0 framing, slot validation, deadlines & child responsesThe FLOW host framework
Component execution, recipe selection & dependency callsThe Flow component (via SDK or Markdown interpreter)

#Implement the invocation lifecycle

  1. Inspect Package/0: exactly one root FLOW.<ext> entrypoint, its metadata, and any invocation contract. Code metadata lives in optional FLOW.meta.json; Markdown metadata lives in frontmatter. Check the selected runtime, dependency requirements and supported channels.
  2. Prepare the execution environment and grants before starting work. Supply only selected settings, attachments, scratch space and a finite deadline. Keep credentials with their responsible host implementation.
  3. Send one complete flow/run request with a string request ID. Run/0 uses bounded LF-terminated UTF-8 JSON objects over full-duplex stdio. Keep the reader active while awaiting responses and serialize writes. Stderr carries diagnostics, not protocol frames.
  4. Service flow/call only while its invocation owner is pending. Validate its declared slot, operation identity, input and any explicit channel transfer; resolve only an authorized implementation. Supply the callee's own context.
  5. Return and validate complete { outcome, output } results. Declared domain refusals are normal results; operational failures use the JSON-RPC error envelope. Neither progress nor channel EOF establishes successful execution.
  6. On cancellation, send request/cancel with the pending request's ID. Enforce deadlines and bounded termination even if the component ignores cancellation. Settle the complete owned subtree and resources; do not retry uncertain work automatically.
  7. After a terminal response, drain stdout and await process exit. Accept success only with a valid correlated result, no pending owned requests, no trailing bytes or frames, successful cleanup and a zero exit status. A shutdown kill after a result is an execution failure.

Cancellation has this exact notification shape; it carries no id and receives no response:

{"jsonrpc":"2.0","method":"request/cancel","params":{"requestId":"host:1"}}

Run/0 specifies validation, error codes, identity, cancellation and completion in more detail. These duties do not prescribe Jig's review, persistence or sandbox implementation.

#Exercise both directions locally

This trusted-code exercise runs a caller and a greeter using Node 24, npm and Python 3.11+ on Linux or macOS. It uses the public, standard-library-only Run/0 test peer. The peer checks framing and process completion. It inherits the launching process's environment and does not contain descendants; run it with the clean environment below and only code you trust. It is not a production host library.

Create an empty directory and install the published SDK:

npm init -y
npm pkg set type=module
npm install --save-exact @jigging/flow@alpha
mkdir greeter

Save this caller as FLOW.mjs:

import { handle } from "@jigging/flow";

await handle(async (context) => context.call({
  operationId: "greet:1",
  slot: "greeter",
  input: context.input,
}));

Declare its dependency in FLOW.meta.json:

{"uses":{"greeter":{}}}

Save this implementation as greeter/FLOW.mjs. Both packages use the installed SDK in this local exercise:

import { handle } from "@jigging/flow";

await handle(async (context) => ({
  outcome: "done",
  output: { message: `Hello, ${context.input}!` },
}));

Save the public peer's run0_peer.py in the exercise directory, then save this as host.py. It serves one exact dependency call for these packages. A general host additionally needs the other validation, authority, concurrency, channels and lifecycle duties described above.

import json
import tempfile
import time
from pathlib import Path
from run0_peer import HostPeer, flow_run_request, require_exact_object, success


def done_result(message, request_id):
    require_exact_object(message, {"jsonrpc", "id", "result"})
    if message["id"] != request_id:
        raise RuntimeError("Unexpected response ID")
    result = require_exact_object(message["result"], {"outcome", "output"})
    if result["outcome"] != "done":
        raise RuntimeError("These packages declare only the done outcome")
    return result


def remaining(deadline):
    seconds = (deadline - int(time.time() * 1000)) / 1000
    if seconds <= 0:
        raise TimeoutError("Invocation deadline expired")
    return seconds


with tempfile.TemporaryDirectory() as directory:
    scratch = Path(directory)
    (scratch / "caller").mkdir()
    (scratch / "greeter").mkdir()
    deadline = int(time.time() * 1000) + 5_000
    request = flow_run_request("host:1", "Ada")
    request["params"].update(scratch=str(scratch / "caller"), deadlineUnixMs=deadline)

    with HostPeer(["node", "FLOW.mjs"], timeout=1) as caller:
        caller.send(request)
        call = caller.validate_request(
            caller.receive(timeout=remaining(deadline)), "flow/call"
        )
        params = require_exact_object(call["params"], {"slot", "operationId", "input"})
        if params != {"slot": "greeter", "operationId": "greet:1", "input": "Ada"}:
            raise RuntimeError("The call does not match this exercise's granted route")

        child_request = flow_run_request("host:greeter:1", params["input"])
        child_request["params"].update(
            scratch=str(scratch / "greeter"), deadlineUnixMs=deadline
        )
        with HostPeer(["node", "greeter/FLOW.mjs"], timeout=1) as greeter:
            greeter.send(child_request)
            child_result = done_result(
                greeter.receive(timeout=remaining(deadline)), "host:greeter:1"
            )
            greeter.finish()

        caller.send(success(call["id"], child_result))
        result = done_result(caller.receive(timeout=remaining(deadline)), "host:1")
        caller.finish()

    if result != {"outcome": "done", "output": {"message": "Hello, Ada!"}}:
        raise RuntimeError("Unexpected greeting result")
    print(json.dumps(result))

Run the exercise without ambient credentials:

env -i PATH="$PATH" python3 host.py

The expected output is:

{"outcome":"done","output":{"message":"Hello, Ada!"}}

Change Hello, to Welcome, in the greeter and change the final expected message in host.py. Run again: the caller is unchanged and receives the new result. As a failure case, remove output from the greeter's return value. The SDK rejects it; the host does not print a successful result. A component which emits trailing stdout or exits nonzero after responding must also fail completion.

#Verify an implementation

Use the Run/0 conformance corpus and its README for executable component/host-peer checks. Start with ordinary invocation and slot calls, then test malformed frames, error responses, cancellation, pending work, nonzero exits and trailing output. Implement the channel extension when your host claims that support.

Protocol evidence does not establish containment. The host's threat model must cover filesystem and network authority, resource limits, credentials, descendant ownership and cleanup. Those controls belong to the host; a method declaration cannot grant them. Share implementation experience in GitHub Discussions.