ifURI examples
← all examples

25 — a multi-step URI flow across tellmesh packs (executed, not just resolved)

Example 24 adopted ~20 tellmesh libraries into one URI registry. This one shows the payoff: chaining several of those URIs into one flow and running it in action, where each step's real output feeds the next.

kvm://{host}/monitor/command/capture  ──image_id──►  ocr://{host}/image/query/text
                                                              │ text
                                                              ▼
                                          llm://{host}/chat/command/complete ──► summary

Three schemes — kvm (capture), ocr (read), llm (summarize) — live in one registry. The flow runner calls urirun.run(uri, registry, mode="execute") for each step under a policy that allows only those three schemes, and threads the result of each step into the next step's payload.

Run it

python3 flow.py
pytest test_flow.py -q
== one registry, 3 routes across 3 adopted packs (kvm, ocr, llm) ==

  [1] kvm://host1/monitor/command/capture
      -> {"image_id": "shot-mon0", "monitor": 0, "width": 1920, "height": 1080}
  [2] ocr://host1/image/query/text
      -> {"image_id": "shot-mon0", "text": "INVOICE  Acme Corp  TOTAL DUE: 42.00 USD  due 2026-07-01", ...}
  [3] llm://host1/chat/command/complete
      -> {"model": "mock-llm", "summary": "Invoice for 42.00 USD, due 2026-07-01.", ...}

flow result: 'Invoice for 42.00 USD, due 2026-07-01.'
end-to-end data threaded correctly: True

image_id from step 1 is the input to step 2; the text from step 2 is the prompt for step 3. Change the capture (monitor=1) and a *different* scanned document flows all the way to a *different* summary — the data really moves through the URIs.

Run it from the CLI (no Python runner)

The same chain, driven entirely by the urirun CLI from bash — jq threads each step's result.value into the next step's payload:

./flow_cli.sh
  [1] kvm capture            -> image_id=shot-mon0
  [2] ocr image=shot-mon0    -> text="INVOICE  Acme Corp  TOTAL DUE: 42.00 USD  due 2026-07-01"
  [3] llm complete           -> summary="Invoice for 42.00 USD, due 2026-07-01."
end-to-end (kvm->ocr->llm) threaded correctly via the CLI: ok

This works because urirun adopt-pack emits a re-importable handler descriptor, so an adopted route executes from a plain file registryurirun run <uri> <registry> --execute — with no Python orchestration. The per-step --allow <scheme>://** is the policy gate.

Run it over a network transport (a served node)

The same chain, but the registry is served by urirun node serve (HTTP) and each step is a POST /run to the node — the URIs interoperate *remotely*:

./flow_node.sh
node healthy: {"name":"flownode","execute":true,"routeCount":3}
  [1] POST kvm capture       -> image_id=shot-mon0
  [2] POST ocr image=shot-mon0 -> text="INVOICE  Acme Corp  TOTAL DUE: 42.00 USD  due 2026-07-01"
  [3] POST llm complete      -> summary="Invoice for 42.00 USD, due 2026-07-01."
end-to-end (kvm->ocr->llm) threaded correctly over HTTP: ok

The node's --allow kvm/ocr/llm globs are its security boundary — a POST /run for any other URI is denied at the node. Same three URIs, same data flow, now across a socket: in-process (flow.py), local CLI (flow_cli.sh) and remote node (flow_node.sh) are three transports over one registry.

What's real and what's a stand-in

contracts), the adoption (adopt-pack manifest → bindings), compiling them into one registry, in-process execution of each route via the local-function adapter, the policy gate around the chain, and the data threaded between steps.

need the whole monorepo installed (uriocr imports uri_control.edge, …), so this example ships small deterministic handlers that honour the same URI contracts. Swap in the real packages (install tellmesh, point the manifests at uriocr.handlers:…) and the same flow runs against them unchanged — that is the point of adoption.

How a route becomes executable

adopt-pack emits a ref string ("flow_ocr.handlers:extract_text"). To execute (not just dry-run), flow.py turns that into a re-importable descriptor (python: {module, export}) so urirun hydrates and calls the handler in-process. Each handler receives the step's payload as keyword arguments and returns a dict, which urirun wraps as result.value — that value is what the next step consumes.

Files

CLI and node flows end to end.

Files

.gitignoreREADME.mdflow.pyflow_cli.shflow_node.shtest_flow.py.benchmarks/.ruff_cache/packs/

View on GitHub →