# Static Python Sources Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add static config-driven Python workflow sources so local/project Python `NodeSpec`s can be exposed through `WorkflowServer`, `wf cap list`, `wf cap call`, and workflow runs without MCP. **Architecture:** `wf_authoring` continues to own `NodeSpec` and `@node`. New package `wf_sources_python` owns module/object loading and projection into `CapabilitySource`. `wf_config` gains `kind: "python"` source entries, and `wf_server.config` composes Python sources into the local/static `WorkflowServer` path. **Tech Stack:** Python 3.14, Pydantic v2 discriminated unions, `importlib`, `wf_authoring.NodeSpec`, `wf_platform.CapabilitySource`, pytest/pytest-asyncio, ruff, basedpyright. --- ## File Structure - Create `src/wf_sources_python/__init__.py` - Public exports for `PythonSourceConfigLike`, `load_python_source`, `python_capability_source`. - Create `src/wf_sources_python/loader.py` - Import `module`, resolve `registry`, normalize supported registry shapes. - Create `tests/wf_sources_python/test_loader.py` - Unit tests for loading mappings, sequences, callables, qualification, and errors. - Modify `src/wf_config/models.py` - Add `PythonSourceConfig` to `SourceConfig` union. - Modify `src/wf_config/__init__.py` - Export `PythonSourceConfig`. - Modify `src/wf_server/context.py` - Let `build_local_static_workflow_server(...)` accept extra capability sources. - Modify `src/wf_server/config.py` - Detect `kind: "python"` sources and pass loaded sources to local/static server construction. - Modify tests: - `tests/wf_config/test_config_models.py` - `tests/wf_server/test_config_composition.py` - Add or update an RPC/client smoke test proving `cap call` works. - Modify docs: - `docs/current_roadmap.md` - `docs/superpowers/specs/2026-06-11-python-source-provider.md` --- ## Task 1: Config Model **Files:** - Modify: `src/wf_config/models.py` - Modify: `src/wf_config/__init__.py` - Modify: `tests/wf_config/test_config_models.py` - [ ] **Step 1: Add failing config test** Add to `tests/wf_config/test_config_models.py`: ```python def test_workflow_config_parses_python_source() -> None: config = WorkflowConfigFile.model_validate( { "version": 1, "server": { "sources": [ { "kind": "python", "id": "local.ops", "module": "tests.fixtures.python_source_ops", "registry": "registry", } ] }, } ) source = config.server.sources[0] assert source.kind == "python" assert source.id == "local.ops" assert source.module == "tests.fixtures.python_source_ops" assert source.registry == "registry" ``` - [ ] **Step 2: Run failing test** Run: ```bash uv run pytest tests/wf_config/test_config_models.py::test_workflow_config_parses_python_source -q ``` Expected: fail because `PythonSourceConfig` is not in the source union. - [ ] **Step 3: Add `PythonSourceConfig`** In `src/wf_config/models.py`, add after `StdlibSourceConfig`: ```python class PythonSourceConfig(WorkflowConfigModel): """Static config for trusted in-process Python workflow sources.""" kind: Literal["python"] = "python" id: str enabled: bool = True module: str = Field(min_length=1) registry: str = Field(default="registry", min_length=1) @field_validator("id") @classmethod def validate_source_id(cls, value: str) -> str: if not re.fullmatch(SOURCE_ID_PATTERN, value): raise ValueError( "source id must start with alphanumeric or underscore and contain " "only [A-Za-z0-9_.-]" ) if "." not in value: raise ValueError("source id must look like '.'") return value ``` Update `SourceConfig`: ```python SourceConfig = Annotated[ StdlibSourceConfig | PythonSourceConfig | McpSourceConfig, Field(discriminator="kind"), ] ``` Update `src/wf_config/__init__.py` to export `PythonSourceConfig`. - [ ] **Step 4: Run config tests** Run: ```bash uv run pytest tests/wf_config/test_config_models.py -q ``` Expected: pass. - [ ] **Step 5: Commit config slice** Run: ```bash git add src/wf_config/models.py src/wf_config/__init__.py tests/wf_config/test_config_models.py git commit -m "feat: add python source config model" ``` --- ## Task 2: Python Source Loader **Files:** - Create: `src/wf_sources_python/__init__.py` - Create: `src/wf_sources_python/loader.py` - Create: `tests/wf_sources_python/test_loader.py` - Create: `tests/fixtures/python_source_ops.py` - [ ] **Step 1: Add fixture module** Create `tests/fixtures/python_source_ops.py`: ```python 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) @node(name="authoring.upper") def upper(payload: EchoInput) -> EchoOutput: return EchoOutput(echoed=payload.text.upper()) registry = [echo, upper] ``` - [ ] **Step 2: Add failing loader tests** Create `tests/wf_sources_python/test_loader.py`: ```python from __future__ import annotations import pytest from wf_sources_python import load_python_source def test_load_python_source_from_sequence_registry() -> None: source = load_python_source( source_id="local.ops", module="tests.fixtures.python_source_ops", registry="registry", ) assert source.id == "local.ops" assert source.kind == "python" assert set(source.capabilities.node_specs) == { "local.ops.echo", "local.ops.upper", } assert source.permissions.safe_for_workflow is True def test_load_python_source_rejects_missing_registry() -> None: with pytest.raises(ValueError, match="missing registry object"): load_python_source( source_id="local.ops", module="tests.fixtures.python_source_ops", registry="missing", ) def test_load_python_source_rejects_non_node_spec() -> None: with pytest.raises(TypeError, match="expected NodeSpec"): load_python_source( source_id="local.ops", module="math", registry="pi", ) ``` - [ ] **Step 3: Run failing tests** Run: ```bash uv run pytest tests/wf_sources_python/test_loader.py -q ``` Expected: fail because `wf_sources_python` does not exist. - [ ] **Step 4: Implement loader** Create `src/wf_sources_python/loader.py`: ```python from __future__ import annotations from collections.abc import Callable, Mapping, Sequence from importlib import import_module from typing import Any, Protocol from wf_authoring import NodeSpec, node from wf_platform import ( CapabilityBuckets, CapabilitySource, SourcePermissions, SourceVisibility, ) class PythonSourceConfigLike(Protocol): id: str module: str registry: str enabled: bool def load_python_source( *, source_id: str, module: str, registry: str = "registry", enabled: bool = True, ) -> CapabilitySource: """Load a trusted in-process Python source from a module registry object.""" module_obj = import_module(module) if not hasattr(module_obj, registry): raise ValueError(f"missing registry object {registry!r} in module {module!r}") raw_registry = getattr(module_obj, registry) if callable(raw_registry) and not isinstance(raw_registry, NodeSpec): raw_registry = raw_registry() specs = _coerce_specs(raw_registry) qualified = [_qualify_spec(source_id, spec) for spec in specs] names = [spec.name for spec in qualified] if len(names) != len(set(names)): raise ValueError(f"duplicate NodeSpec names in Python source {source_id!r}") return CapabilitySource( id=source_id, kind="python", enabled=enabled, capabilities=CapabilityBuckets( node_specs={spec.name: spec for spec in qualified}, ), visibility=SourceVisibility( planner=True, mcp_client=True, admin_dashboard=True, ), permissions=SourcePermissions(safe_for_workflow=True), description=f"Python source loaded from {module}:{registry}.", ) def python_capability_source(config: PythonSourceConfigLike) -> CapabilitySource: return load_python_source( source_id=config.id, module=config.module, registry=config.registry, enabled=config.enabled, ) def _coerce_specs(raw_registry: object) -> list[NodeSpec[Any, Any]]: if isinstance(raw_registry, Mapping): values = list(raw_registry.values()) elif isinstance(raw_registry, Sequence) and not isinstance(raw_registry, str): values = list(raw_registry) else: values = [raw_registry] specs: list[NodeSpec[Any, Any]] = [] for value in values: if not isinstance(value, NodeSpec): raise TypeError(f"expected NodeSpec in Python source registry, got {type(value).__name__}") specs.append(value) return specs def _qualify_spec(source_id: str, spec: NodeSpec[Any, Any]) -> NodeSpec[Any, Any]: local_name = spec.name.removeprefix("authoring.") if local_name.startswith(f"{source_id}."): return spec return node(spec, name=f"{source_id}.{local_name}") ``` Create `src/wf_sources_python/__init__.py`: ```python from __future__ import annotations from .loader import PythonSourceConfigLike, load_python_source, python_capability_source __all__ = [ "PythonSourceConfigLike", "load_python_source", "python_capability_source", ] ``` - [ ] **Step 5: Run loader tests** Run: ```bash uv run pytest tests/wf_sources_python/test_loader.py -q ``` Expected: pass. - [ ] **Step 6: Commit loader slice** Run: ```bash git add src/wf_sources_python tests/wf_sources_python tests/fixtures/python_source_ops.py git commit -m "feat: add python source loader" ``` --- ## Task 3: Compose Python Sources Into Local WorkflowServer **Files:** - Modify: `src/wf_server/context.py` - Modify: `src/wf_server/config.py` - Modify: `tests/wf_server/test_config_composition.py` - [ ] **Step 1: Add failing server composition test** Add to `tests/wf_server/test_config_composition.py`: ```python from wf_config import PythonSourceConfig def test_workflow_config_with_python_source_exposes_capability(tmp_path) -> None: config = WorkflowConfigFile.model_validate( { "version": 1, "server": { "store": {"kind": "filesystem", "root": str(tmp_path / "store")}, "sources": [ { "kind": "python", "id": "local.ops", "module": "tests.fixtures.python_source_ops", "registry": "registry", } ], }, } ) server = build_workflow_server_from_workflow_config(config) assert "local.ops" in server.specs.capability_sources assert ( "local.ops.echo" in server.specs.capability_sources["local.ops"].capabilities.node_specs ) ``` Use existing imports in that file; only add missing names. - [ ] **Step 2: Run failing test** Run: ```bash uv run pytest tests/wf_server/test_config_composition.py::test_workflow_config_with_python_source_exposes_capability -q ``` Expected: fail because Python sources are ignored or unsupported. - [ ] **Step 3: Extend `build_local_static_workflow_server`** In `src/wf_server/context.py`, update `build_local_static_workflow_server` to accept extra sources: ```python def build_local_static_workflow_server( store_root: Path, *, extra_sources: Mapping[str, CapabilitySource] | None = None, ) -> WorkflowServer: ``` Inside the function, merge sources after built-ins: ```python sources = builtin_sources() if extra_sources: overlap = set(sources) & set(extra_sources) if overlap: raise ValueError(f"duplicate workflow source ids: {sorted(overlap)}") sources.update(extra_sources) ``` Use `sources` where the function currently passes `builtin_sources()` into `StaticWorkflowSpecProvider`. - [ ] **Step 4: Wire Python sources in `wf_server.config`** In `src/wf_server/config.py`, import: ```python from wf_sources_python import python_capability_source ``` Add helper: ```python def _python_sources(config: WorkflowConfigFile) -> dict[str, CapabilitySource]: return { source.id: python_capability_source(source) for source in config.server.sources if getattr(source, "kind", None) == "python" } ``` Import `CapabilitySource` from `wf_platform` for the return annotation. In `build_workflow_server_from_workflow_config`, change: ```python return build_local_static_workflow_server(store.root) ``` to: ```python return build_local_static_workflow_server( store.root, extra_sources=_python_sources(config), ) ``` - [ ] **Step 5: Run server composition tests** Run: ```bash uv run pytest tests/wf_server/test_config_composition.py tests/wf_server/test_local_static_server.py -q ``` Expected: pass. - [ ] **Step 6: Commit composition slice** Run: ```bash git add src/wf_server/context.py src/wf_server/config.py tests/wf_server/test_config_composition.py git commit -m "feat: compose python sources in workflow server" ``` --- ## Task 4: Prove CLI/RPC Capability Call And Workflow Run **Files:** - Modify: `tests/wf_transport_rpc_http/test_app.py` or `tests/wf_server/test_config_composition.py` - [ ] **Step 1: Add RPC capability call test** Add to `tests/wf_transport_rpc_http/test_app.py`: ```python async def test_rpc_calls_python_source_capability(tmp_path) -> None: config = WorkflowConfigFile.model_validate( { "version": 1, "server": { "store": {"kind": "filesystem", "root": str(tmp_path / "store")}, "sources": [ { "kind": "python", "id": "local.ops", "module": "tests.fixtures.python_source_ops", "registry": "registry", } ], }, } ) server = build_workflow_server_from_workflow_config(config) app = create_rpc_app(server) transport = httpx.ASGITransport(app=app) async with httpx.AsyncClient(transport=transport, base_url="http://test") as client: listed = await _rpc( client, "workflow.capabilities.list", {"source_id": "local.ops", "limit": 10}, ) called = await _rpc( client, "workflow.capabilities.call", { "qualified_name": "local.ops.echo", "payload": {"text": "hello python"}, }, ) assert listed["result"]["total"] == 2 assert called["result"]["outcome"] == "ok" assert called["result"]["output"] == {"echoed": "hello python"} ``` Add imports if missing: ```python from wf_config import WorkflowConfigFile from wf_server.config import build_workflow_server_from_workflow_config ``` - [ ] **Step 2: Run RPC test** Run: ```bash uv run pytest tests/wf_transport_rpc_http/test_app.py::test_rpc_calls_python_source_capability -q ``` Expected: pass. - [ ] **Step 3: Add workflow run test if not covered by capability call** If the capability call test passes but does not exercise deployment execution, add a direct server/API run test in `tests/wf_server/test_config_composition.py`: ```python async def test_python_source_capability_runs_in_deployment(tmp_path) -> None: config = WorkflowConfigFile.model_validate( { "version": 1, "server": { "store": {"kind": "filesystem", "root": str(tmp_path / "store")}, "sources": [ { "kind": "python", "id": "local.ops", "module": "tests.fixtures.python_source_ops", } ], }, } ) server = build_workflow_server_from_workflow_config(config) await server.api.create_artifact_from_capability( artifact_id="python_echo", version=1, capability_name="local.ops.echo", title="Python Echo", input_map={"text": "input.text"}, output_map={"echoed": "state.echoed"}, source_bindings={"local.ops": "local.ops"}, outcomes=("ok",), ) await server.api.save_deployment( { "id": "python_echo.default", "artifact_id": "python_echo", "artifact_version": 1, "bindings": [{"logical_source": "local.ops", "concrete_source": "local.ops"}], } ) result = await server.api.run_deployment( deployment_id="python_echo.default", workflow_input={"text": "run me"}, ) assert result["status"] == "completed" assert result["output"]["echoed"] == "run me" ``` If `create_artifact_from_capability` has a different signature, follow the existing closest test in `tests/wf_api/test_capability_api.py` or `tests/wf_transport_rpc_http/test_app.py`. - [ ] **Step 4: Run focused integration tests** Run: ```bash uv run pytest tests/wf_sources_python tests/wf_config/test_config_models.py tests/wf_server/test_config_composition.py tests/wf_transport_rpc_http/test_app.py::test_rpc_calls_python_source_capability -q ``` Expected: pass. - [ ] **Step 5: Commit integration slice** Run: ```bash git add tests/wf_transport_rpc_http/test_app.py tests/wf_server/test_config_composition.py git commit -m "test: prove python source capabilities over rpc" ``` --- ## Task 5: Docs And Verification **Files:** - Modify: `docs/current_roadmap.md` - Modify: `docs/superpowers/specs/2026-06-11-python-source-provider.md` - Move: `docs/superpowers/plans/2026-06-11-static-python-sources.md` to `docs/historical/superpowers/plans/2026-06-11-static-python-sources.md` - [ ] **Step 1: Update roadmap** In `docs/current_roadmap.md`, under Priority 3, add: ```markdown - Completed: static config `kind: "python"` sources can load trusted local `NodeSpec` registries and expose them through WorkflowServer. Implementation: [`static Python sources`](historical/superpowers/plans/2026-06-11-static-python-sources.md). ``` - [ ] **Step 2: Update spec status** In `docs/superpowers/specs/2026-06-11-python-source-provider.md`, change: ```markdown Status: planned first non-MCP source family ``` to: ```markdown Status: first static config slice implemented ``` Add: ```markdown ## Implementation Status Implemented: - `wf_config` accepts `server.sources[]` entries with `kind: "python"`. - `wf_sources_python` loads trusted in-process `NodeSpec` registries from `module:registry`. - `wf_server.config` composes Python sources into local/static servers. - Capability listing/calling works over JSON-RPC. Still deferred: - mutable source registry/apply support - hot reload - reducer exports - sandboxing/untrusted code ``` - [ ] **Step 3: Move completed plan** Run: ```bash git mv docs/superpowers/plans/2026-06-11-static-python-sources.md docs/historical/superpowers/plans/2026-06-11-static-python-sources.md ``` - [ ] **Step 4: Run verification** Run: ```bash uv run pytest tests/wf_sources_python tests/wf_config/test_config_models.py tests/wf_server/test_config_composition.py tests/wf_server/test_local_static_server.py tests/wf_transport_rpc_http/test_app.py -q uv run ruff check src/wf_sources_python src/wf_config src/wf_server tests/wf_sources_python tests/wf_config/test_config_models.py tests/wf_server/test_config_composition.py tests/wf_transport_rpc_http/test_app.py uv run ruff format --check src/wf_sources_python src/wf_config src/wf_server tests/wf_sources_python tests/wf_config/test_config_models.py tests/wf_server/test_config_composition.py tests/wf_transport_rpc_http/test_app.py uv run basedpyright --level error src/wf_sources_python src/wf_config src/wf_server tests/wf_sources_python tests/wf_config/test_config_models.py tests/wf_server/test_config_composition.py tests/wf_transport_rpc_http/test_app.py git diff --check ``` Expected: - Tests pass. - Ruff passes. - Basedpyright reports 0 errors for changed files. - `git diff --check` reports no whitespace errors; CRLF warnings on Windows are acceptable. - [ ] **Step 5: Final commit** Run: ```bash git add src tests docs git commit -m "docs: record static python source support" ``` If earlier task commits were skipped, use one final commit instead: ```bash git add src tests docs git commit -m "feat: add static python sources" ``` --- ## Acceptance Criteria - `wf_config` parses `kind: "python"` source entries. - `wf_sources_python` imports a trusted module registry and projects `NodeSpec`s under the configured source id. - `wf_server.config` includes Python sources in local/static `WorkflowServer` construction. - Built-in `wf.std` and `wf.recipes` remain unchanged. - No `wf_mcp` dependency is introduced. - `workflow.capabilities.list` and `workflow.capabilities.call` work for Python source capabilities over JSON-RPC. - Docs make clear this is trusted in-process code and not hot-reloaded or registry-mutable yet.