Files
lda-wf/docs/runbooks/python-source.md
T

4.5 KiB

Python Source Runbook

This runbook shows how to expose project-local Python NodeSpec functions as workflow capabilities through wf-rpc-server.

Python sources are trusted in-process code. Importing the configured module can run top-level module code during wf config validate and server startup. Keep module top-level work small and side-effect free; put real work inside @node functions.

Prefer package-style source modules and package-relative imports. For example, use module: "my_source.ops" with from .helpers import ... inside my_source/ops.py. Bare local imports such as import helpers use Python's global import cache and are only best-effort when multiple configured sources reuse the same helper/module names.

1. Write ops.py

Create a module that exports one or more NodeSpec objects. The simplest path is to decorate typed functions with wf_authoring.node and put them in a registry list.

from __future__ import annotations

from pydantic import BaseModel

from wf_authoring import node


class EchoInput(BaseModel):
    text: str


class EchoOutput(BaseModel):
    echoed: str


@node(name="echo")
def echo(payload: EchoInput) -> EchoOutput:
    return EchoOutput(echoed=payload.text)


registry = [echo]

The registry can also be a mapping or a callable returning a sequence/mapping. Every exported value must be a NodeSpec.

2. Configure The Source

Add a kind: "python" entry under server.sources[]:

{
  "version": 1,
  "client": {
    "target": {
      "kind": "rpc_http",
      "url": "http://127.0.0.1:8766/rpc",
      "timeout_seconds": 30
    }
  },
  "server": {
    "store": {"kind": "filesystem", "root": ".wf_python_store"},
    "transports": [
      {"kind": "rpc_http", "host": "127.0.0.1", "port": 8766, "path": "/rpc"}
    ],
    "sources": [
      {
        "kind": "python",
        "id": "local.ops",
        "path": ".",
        "module": "ops",
        "registry": "registry"
      }
    ]
  }
}

path is resolved relative to the config file and added to sys.path before import. This makes the module discoverable whether you run through uv run python or an installed entrypoint.

The source id prefixes local names. A node named echo becomes local.ops.echo. If a node uses the authoring namespace, such as authoring.echo, that authoring prefix is replaced by the source id, producing local.ops.echo.

3. Validate And Start

Preflight the config:

uv run wf config validate wf.python.config.json

Then start the server:

uv run wf-rpc-server --config wf.python.config.json

If the config includes client.target, the CLI can use the config directly:

uv run wf --config wf.python.config.json status

You can also pass the URL explicitly:

uv run wf --url http://127.0.0.1:8766/rpc source list

4. Call A Capability

List and call the Python capability:

uv run wf --url http://127.0.0.1:8766/rpc cap list --source local.ops
uv run wf --url http://127.0.0.1:8766/rpc cap call local.ops.echo --input '{"text":"hello"}'

Expected output includes:

{
  "outcome": "ok",
  "output": {"echoed": "hello"}
}

5. Save And Run A Workflow

Create a draft from the Python capability:

uv run wf --url http://127.0.0.1:8766/rpc draft create `
  python_echo_ws --capability local.ops.echo --name python_echo

Save it as an artifact. Bind the configured Python source. Built-in platform sources such as wf.std can be used by generated scaffolds without self-binding.

uv run wf --url http://127.0.0.1:8766/rpc draft save python_echo_ws `
  --artifact python_echo `
  --version 1 `
  --title "Python Echo" `
  --outcome ok `
  --binding local.ops=local.ops

Save a deployment with the same bindings:

uv run wf --url http://127.0.0.1:8766/rpc deploy save python_echo.default `
  --artifact python_echo `
  --version 1 `
  --binding local.ops=local.ops

Run it:

uv run wf --url http://127.0.0.1:8766/rpc run start python_echo.default `
  --input '{"text":"hello workflow"}'

Expected output includes:

{
  "outcome": "ok",
  "output": {"echoed": "hello workflow"}
}

Current Limits

  • Python sources are static at server startup; there is no hot reload yet.
  • Python sources are trusted in-process code; there is no sandbox.
  • Source registry mutation/apply support for Python sources is deferred.
  • Reducer exports are deferred until a real source needs them.