# wf_api Slice 5A/5B Helper Consolidation 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:** Consolidate duplicated workflow API listing, artifact-plan, source-snapshot, and dependency-summary helpers into protocol-neutral `wf_api` modules without changing public payloads. **Architecture:** Slice 5A moves workflow list helpers out of MCP-named modules for workflow API callers. Slice 5B promotes duplicated private helpers from `wf_api.capabilities`, `wf_api.artifacts`, `wf_api.drafts`, `wf_api.runs`, and `wf_api.deployments` into focused `wf_api` helper modules. `wf_mcp.shared.pagination` stays untouched because proxy/admin code still uses it. **Tech Stack:** Python 3.14, Pydantic v2, `wf_api`, `wf_artifacts`, `wf_platform.page_items`, pytest, ruff, basedpyright. --- ## Scope ### In Scope - Create `src/wf_api/listing.py` for `matches_query` and `paged_list_payload`. - Create `src/wf_api/artifact_plans.py` for `raw_plan_from_artifact`, `plan_field`, and `plan_nodes`. - Create `src/wf_api/artifact_refs.py` for `artifact_capability_id`. - Create `src/wf_api/capability_requirements.py` for `required_capability_payloads`, `observed_node_specs`, and `required_capabilities_for_plan`. - Create `src/wf_api/source_snapshots.py` for deployment/run source snapshot helpers if currently duplicated in `deployments.py` and `runs.py`. - Update `src/wf_api/{capabilities,artifacts,drafts,runs,deployments}.py` to import the shared helpers. - Update `src/wf_mcp/workflow_surface/handlers.py` no-store `list_artifacts` fallback to use `wf_api.listing.paged_list_payload`. - Add focused `wf_api` tests for the promoted helpers. ### Out of Scope - Do not move or delete `wf_mcp.shared.pagination`; `src/wf_mcp/proxy/tools.py` still uses it. - Do not move event primitives in this slice. - Do not delete `wf_mcp.workflow_surface.*` compatibility shims. - Do not change MCP tool names, response payload shapes, pagination semantics, artifact IDs, or capability IDs. - Do not move request/response Pydantic models out of `wf_mcp.workflow_surface.models`. ## File Map | File | Responsibility | | --- | --- | | `src/wf_api/listing.py` | Workflow API list filtering and common paged response payloads. | | `src/wf_api/artifact_plans.py` | Safe extraction of `RawWorkflowPlan` and plan node dictionaries from saved artifacts. | | `src/wf_api/artifact_refs.py` | Stable workflow artifact capability IDs. | | `src/wf_api/capability_requirements.py` | Required-capability payloads and observed NodeSpec inventory projection. | | `src/wf_api/source_snapshots.py` | Serializable source snapshots used by deployment validation and run resume checks. | | `src/wf_api/__init__.py` | Optional re-exports for public helper symbols. | | `src/wf_api/capabilities.py` | Remove local duplicates; import shared helpers. | | `src/wf_api/artifacts.py` | Remove local duplicates; import shared helpers. | | `src/wf_api/drafts.py` | Remove local duplicates; import shared helpers. | | `src/wf_api/runs.py` | Remove local `raw_plan_from_artifact`; import shared helpers. | | `src/wf_api/deployments.py` | Import source snapshot helper if duplicated there. | | `src/wf_mcp/workflow_surface/handlers.py` | Stop importing workflow list helper from `wf_mcp.shared`. | | `tests/wf_api/test_listing.py` | Unit tests for query matching and paged payload shape. | | `tests/wf_api/test_artifact_helpers.py` | Unit tests for plan extraction, artifact refs, and dependency helper outputs. | | `tests/wf_api/test_import_direction.py` | Existing guard; must continue passing. | --- ## Task 1: Add `wf_api.listing` **Files:** - Create: `src/wf_api/listing.py` - Test: `tests/wf_api/test_listing.py` - Modify: `src/wf_api/__init__.py` - [ ] **Step 1: Write focused listing tests** Create `tests/wf_api/test_listing.py`: ```python from __future__ import annotations from wf_api.listing import matches_query, paged_list_payload def test_matches_query_accepts_empty_or_missing_query() -> None: assert matches_query("Alpha", query=None) is True assert matches_query("Alpha", query=" ") is True def test_matches_query_searches_non_none_values_case_insensitively() -> None: assert matches_query(None, "Demo Echo", query="echo") is True assert matches_query(None, "Demo Echo", query="missing") is False def test_paged_list_payload_preserves_common_shape() -> None: payload = paged_list_payload( "nodes", [{"name": "a"}, {"name": "b"}, {"name": "c"}], cursor=None, limit=2, ) assert payload["nodes"] == [{"name": "a"}, {"name": "b"}] assert payload["total"] == 3 assert payload["next_cursor"] is not None ``` - [ ] **Step 2: Run the tests and verify import failure** Run: ```bash uv run pytest tests/wf_api/test_listing.py -q ``` Expected: fail because `wf_api.listing` does not exist. - [ ] **Step 3: Add `src/wf_api/listing.py`** ```python from __future__ import annotations from collections.abc import Sequence from typing import Any, TypeVar from wf_platform import page_items T = TypeVar("T") def matches_query(*values: object, query: str | None) -> bool: """Return whether a compact discovery row matches a human search query.""" if query is None: return True needle = query.strip().casefold() if not needle: return True return any(needle in str(value).casefold() for value in values if value is not None) def paged_list_payload( key: str, items: Sequence[T], *, cursor: str | None, limit: int, ) -> dict[str, Any]: """Build the shared workflow API list response shape.""" page = page_items(items, cursor=cursor, limit=limit) return { key: list(page.items), "next_cursor": page.next_cursor, "total": page.total, } ``` - [ ] **Step 4: Export helpers from `src/wf_api/__init__.py`** Add imports: ```python from .listing import matches_query, paged_list_payload ``` Add to `__all__`: ```python "matches_query", "paged_list_payload", ``` - [ ] **Step 5: Run listing tests** Run: ```bash uv run pytest tests/wf_api/test_listing.py -q ``` Expected: pass. --- ## Task 2: Route Current Workflow API Listing Calls Through `wf_api.listing` **Files:** - Modify: `src/wf_api/capabilities.py` - Modify: `src/wf_api/artifacts.py` - Modify: `src/wf_mcp/workflow_surface/handlers.py` - Test: existing `tests/wf_api/test_capability_api.py`, `tests/wf_api/test_artifact_api.py` - [ ] **Step 1: Replace local listing helpers in `src/wf_api/capabilities.py`** Remove local `_matches_query` and `_paged_list_payload`. Add: ```python from .listing import matches_query, paged_list_payload ``` Replace calls: ```python _matches_query(...) ``` with: ```python matches_query(...) ``` Replace calls: ```python _paged_list_payload(...) ``` with: ```python paged_list_payload(...) ``` - [ ] **Step 2: Replace local listing helpers in `src/wf_api/artifacts.py`** Remove local `_matches_query`, `_paged_list_payload`, the local `TypeVar`, and now-unused `wf_platform.page_items` import. Add: ```python from .listing import matches_query, paged_list_payload ``` Replace local helper calls the same way as Task 2 Step 1. - [ ] **Step 3: Stop workflow handler fallback from importing `wf_mcp.shared` listing** In `src/wf_mcp/workflow_surface/handlers.py`, replace: ```python from ..shared import paged_list_payload ``` with: ```python from wf_api.listing import paged_list_payload ``` The no-store fallback must keep returning: ```python return paged_list_payload("nodes", [], cursor=cursor, limit=limit) ``` - [ ] **Step 4: Run focused tests** Run: ```bash uv run pytest tests/wf_api/test_listing.py tests/wf_api/test_capability_api.py tests/wf_api/test_artifact_api.py tests/wf_api/test_import_direction.py -q ``` Expected: pass. --- ## Task 3: Add Artifact Plan And Artifact Ref Helpers **Files:** - Create: `src/wf_api/artifact_plans.py` - Create: `src/wf_api/artifact_refs.py` - Test: `tests/wf_api/test_artifact_helpers.py` - [ ] **Step 1: Add failing tests for artifact helper behavior** Create `tests/wf_api/test_artifact_helpers.py` with the imports below. If a helper fixture for artifacts already exists in nearby tests, use it; otherwise construct the minimal `WorkflowArtifact` inline with valid fields copied from existing `tests/wf_api/test_artifact_api.py`. ```python from __future__ import annotations import pytest from wf_api.artifact_plans import plan_field, plan_nodes, raw_plan_from_artifact from wf_api.artifact_refs import artifact_capability_id def test_artifact_capability_id_uses_workflow_ref_shape(echo_artifact) -> None: assert artifact_capability_id(echo_artifact) == ( f"workflow.{echo_artifact.id}.v{echo_artifact.version}" ) def test_raw_plan_from_artifact_preserves_required_plan_fields(echo_artifact) -> None: plan = raw_plan_from_artifact(echo_artifact) assert plan.name == echo_artifact.plan["name"] assert plan.start == echo_artifact.plan["start"] assert len(plan.nodes) == len(echo_artifact.plan["nodes"]) def test_plan_field_reports_missing_field(echo_artifact) -> None: broken = echo_artifact.model_copy( update={"plan": {key: value for key, value in echo_artifact.plan.items() if key != "start"}} ) with pytest.raises(ValueError, match="missing plan field 'start'"): plan_field(broken, "start") def test_plan_nodes_returns_only_dict_nodes(echo_artifact) -> None: artifact = echo_artifact.model_copy( update={"plan": {**echo_artifact.plan, "nodes": [{"id": "a"}, "bad"]}} ) assert plan_nodes(artifact) == [{"id": "a"}] ``` If there is no reusable `echo_artifact` fixture, add a private helper in this test file instead of importing fixtures across packages. - [ ] **Step 2: Run the tests and verify import failure** Run: ```bash uv run pytest tests/wf_api/test_artifact_helpers.py -q ``` Expected: fail because modules do not exist. - [ ] **Step 3: Create `src/wf_api/artifact_refs.py`** ```python from __future__ import annotations from wf_artifacts import WorkflowArtifact, WorkflowCapabilityRef def artifact_capability_id(artifact: WorkflowArtifact) -> str: """Return the stable workflow capability name for a saved artifact.""" return str( WorkflowCapabilityRef( artifact_id=artifact.id, version=artifact.version, ) ) ``` - [ ] **Step 4: Create `src/wf_api/artifact_plans.py`** ```python from __future__ import annotations from typing import Any from wf_artifacts import WorkflowArtifact from .models import RawWorkflowPlan def raw_plan_from_artifact(artifact: WorkflowArtifact) -> RawWorkflowPlan: """Validate the stored raw workflow plan shape expected by runtime calls.""" return RawWorkflowPlan.model_validate( { "name": plan_field(artifact, "name"), "input_schema": plan_field(artifact, "input_schema"), "state_schema": plan_field(artifact, "state_schema"), "output_schema": plan_field(artifact, "output_schema"), "outcomes": artifact.plan.get("outcomes", ["ok"]), "output": artifact.plan.get("output", []), "start": plan_field(artifact, "start"), "nodes": plan_field(artifact, "nodes"), "edges": plan_field(artifact, "edges"), } ) def plan_field(artifact: WorkflowArtifact, field_name: str) -> Any: """Return one required raw-plan field with an artifact-specific error.""" try: return artifact.plan[field_name] except KeyError as exc: raise ValueError( f"workflow artifact {artifact.id}@{artifact.version} " f"is missing plan field {field_name!r}" ) from exc def plan_nodes(artifact: WorkflowArtifact) -> list[dict[str, Any]]: """Return only dict-shaped node entries from a saved raw plan.""" nodes = artifact.plan.get("nodes", []) return [node for node in nodes if isinstance(node, dict)] ``` - [ ] **Step 5: Export helper modules if desired** In `src/wf_api/__init__.py`, export only stable helper names if the package already re-exports helpers. If the file is intentionally selective, skip this step and keep imports module-qualified. - [ ] **Step 6: Run helper tests** Run: ```bash uv run pytest tests/wf_api/test_artifact_helpers.py -q ``` Expected: pass. --- ## Task 4: Replace Duplicate Artifact Plan/Ref Helpers In Domain APIs **Files:** - Modify: `src/wf_api/capabilities.py` - Modify: `src/wf_api/artifacts.py` - Modify: `src/wf_api/runs.py` - Test: existing capability/artifact/run API tests - [ ] **Step 1: Update `src/wf_api/capabilities.py` imports** Add: ```python from .artifact_plans import raw_plan_from_artifact from .artifact_refs import artifact_capability_id ``` Remove local `_raw_plan_from_artifact`, `_plan_field`, and `_artifact_capability_id`. Replace: ```python _raw_plan_from_artifact(...) _artifact_capability_id(...) ``` with: ```python raw_plan_from_artifact(...) artifact_capability_id(...) ``` - [ ] **Step 2: Update `src/wf_api/artifacts.py` imports** Add: ```python from .artifact_plans import plan_nodes from .artifact_refs import artifact_capability_id ``` Remove local `_plan_nodes` and `_artifact_capability_id`. Replace calls with `plan_nodes(...)` and `artifact_capability_id(...)`. - [ ] **Step 3: Update `src/wf_api/runs.py` imports** Add: ```python from .artifact_plans import raw_plan_from_artifact ``` Remove local `_raw_plan_from_artifact` and `_plan_field`. Replace calls with `raw_plan_from_artifact(...)`. - [ ] **Step 4: Run focused tests** Run: ```bash uv run pytest tests/wf_api/test_artifact_helpers.py tests/wf_api/test_capability_api.py tests/wf_api/test_artifact_api.py tests/wf_api/test_run_api.py tests/wf_api/test_import_direction.py -q ``` Expected: pass. --- ## Task 5: Add Shared Capability Requirement Helpers **Files:** - Create: `src/wf_api/capability_requirements.py` - Modify: `src/wf_api/drafts.py` - Modify: `src/wf_api/artifacts.py` - Modify: `src/wf_api/capabilities.py` - Test: `tests/wf_api/test_artifact_helpers.py` - [ ] **Step 1: Add tests for requirement payload and observed specs** Append to `tests/wf_api/test_artifact_helpers.py`: ```python from wf_api.capability_requirements import ( observed_node_specs, required_capability_payloads, ) def test_required_capability_payloads_sorts_by_name(required_capabilities) -> None: payload = required_capability_payloads(required_capabilities) assert list(payload) == sorted(required_capabilities) first = next(iter(payload.values())) assert "ref" in first assert "kind" in first def test_observed_node_specs_projects_enabled_context_specs(operation_context) -> None: observed = observed_node_specs(operation_context) assert isinstance(observed, dict) assert all(hasattr(detail, "name") for detail in observed.values()) ``` If `required_capabilities` or `operation_context` fixtures do not exist, create explicit local helpers by copying the smallest valid setup from `tests/wf_api/test_artifact_api.py` or `tests/wf_api/test_capability_api.py`. Do not import private helpers from production modules. - [ ] **Step 2: Create `src/wf_api/capability_requirements.py`** ```python from __future__ import annotations from typing import Any from wf_artifacts import ( RequiredCapability, WorkflowArtifact, create_workflow_artifact_from_plan as build_workflow_artifact_from_plan, ) from wf_platform import CapabilityRef, NodeSpecInventory from .artifact_plans import plan_nodes from .operation_context import WorkflowOperationContext def required_capability_payloads( requirements: dict[str, RequiredCapability], ) -> dict[str, dict[str, Any]]: """Return deterministic JSON payloads for required capabilities.""" return { name: capability.model_dump(mode="json") for name, capability in sorted(requirements.items()) } def observed_node_specs( context: WorkflowOperationContext, ) -> dict[str, NodeSpecInventory]: """Project current executable specs into serializable observed contracts.""" observed: dict[str, NodeSpecInventory] = {} for source in context.capability_sources.values(): inventory = source.as_inventory() observed.update( {detail.name: detail for detail in inventory.capabilities.node_spec_details} ) return observed def required_capabilities_for_plan( plan: dict[str, Any], *, source_bindings: dict[str, str] | None, context: WorkflowOperationContext, ) -> dict[str, RequiredCapability]: """Infer a draft dependency summary without persisting an artifact.""" artifact = build_workflow_artifact_from_plan( artifact_id="draft_preview", version=1, title="Draft Preview", plan=plan, outcomes=("completed",), source_bindings=source_bindings, observed_node_specs=observed_node_specs(context), ) requirements = artifact.required_capability_map() for node in plan_nodes(artifact): raw_ref = node.get("node") if not isinstance(raw_ref, str) or raw_ref in requirements: continue try: parsed = CapabilityRef.parse(raw_ref) except ValueError: continue requirements[raw_ref] = RequiredCapability( ref=parsed, kind="node_spec", ) return requirements ``` - [ ] **Step 3: Update `src/wf_api/drafts.py`** Import: ```python from .capability_requirements import ( observed_node_specs, required_capabilities_for_plan, required_capability_payloads, ) ``` Replace: ```python _required_capability_payloads(...) _required_capabilities_for_plan(...) _observed_node_specs(...) ``` with: ```python required_capability_payloads(...) required_capabilities_for_plan(...) observed_node_specs(...) ``` Remove local `_required_capabilities_for_plan`, `_required_capability_payloads`, `_observed_node_specs`, and `_plan_nodes` if no longer used. - [ ] **Step 4: Update `src/wf_api/artifacts.py`** Import: ```python from .capability_requirements import ( observed_node_specs, required_capability_payloads, ) ``` Replace local helper calls and remove local duplicate helper definitions. - [ ] **Step 5: Update `src/wf_api/capabilities.py`** Import: ```python from .capability_requirements import required_capability_payloads ``` Replace local helper calls and remove the local duplicate helper definition. - [ ] **Step 6: Run focused tests** Run: ```bash uv run pytest tests/wf_api/test_artifact_helpers.py tests/wf_api/test_drafts_service.py tests/wf_api/test_artifact_api.py tests/wf_api/test_capability_api.py tests/wf_api/test_import_direction.py -q ``` Expected: pass. --- ## Task 6: Add Source Snapshot Helper If Duplicated **Files:** - Create: `src/wf_api/source_snapshots.py` - Modify: `src/wf_api/deployments.py` - Modify: `src/wf_api/runs.py` - Test: existing deployment/run API tests - [ ] **Step 1: Inspect current source snapshot helper names** Run: ```bash rg -n "_available_sources|AvailableSource|AvailableCapability|capability_name" src/wf_api src/wf_mcp/workflow_surface ``` Expected: identify whether `_available_sources` still exists in `src/wf_api/deployments.py` and is imported by `src/wf_api/runs.py`. - [ ] **Step 2: Create `src/wf_api/source_snapshots.py` only if a helper exists** If `_available_sources` exists, move it as: ```python from __future__ import annotations from collections.abc import Mapping from wf_artifacts import AvailableCapability, AvailableSource from wf_platform import CapabilitySource def available_sources_from_capability_sources( sources: Mapping[str, CapabilitySource], ) -> dict[str, AvailableSource]: """Project live capability sources into pinned resume-validation snapshots.""" return { source_id: AvailableSource( id=source.id, capabilities={ name: AvailableCapability(name=name) for name in source.capabilities.node_specs }, ) for source_id, source in sources.items() } ``` If the existing helper carries more fields than `name`, preserve those fields exactly. Do not simplify the payload. - [ ] **Step 3: Update deployment/run imports** Replace duplicated or cross-domain imports with: ```python from .source_snapshots import available_sources_from_capability_sources ``` Use it wherever resume/deployment validation needs current source snapshots. - [ ] **Step 4: Run focused tests** Run: ```bash uv run pytest tests/wf_api/test_deployment_api.py tests/wf_api/test_run_api.py tests/wf_api/test_import_direction.py -q ``` Expected: pass. --- ## Task 7: Remove Duplicate Private Helpers And Guard Imports **Files:** - Modify: `src/wf_api/capabilities.py` - Modify: `src/wf_api/artifacts.py` - Modify: `src/wf_api/drafts.py` - Modify: `src/wf_api/runs.py` - Modify: `src/wf_api/deployments.py` - Test: import-direction guard - [ ] **Step 1: Search for leftover duplicated helpers** Run: ```bash rg -n "def _matches_query|def _paged_list_payload|def _raw_plan_from_artifact|def _plan_field|def _artifact_capability_id|def _required_capability_payloads|def _observed_node_specs|def _plan_nodes|def _available_sources" src/wf_api src/wf_mcp/workflow_surface ``` Expected: - No duplicate helper definitions in domain API modules. - `src/wf_mcp/shared/listing.py` may still define `matches_query` and `paged_list_payload`; leave it alone unless no MCP code imports it. - `src/wf_mcp/shared/pagination.py` must remain. - [ ] **Step 2: Search for forbidden workflow listing import** Run: ```bash rg -n "from \.\.shared import paged_list_payload|from wf_mcp.shared import paged_list_payload" src/wf_mcp/workflow_surface src/wf_api ``` Expected: no matches. - [ ] **Step 3: Verify `wf_api` still imports no `wf_mcp`** Run: ```bash uv run pytest tests/wf_api/test_import_direction.py -q ``` Expected: pass. --- ## Task 8: Final Verification **Files:** - All touched files. - [ ] **Step 1: Run focused wf_api workflow tests** Run: ```bash uv run pytest tests/wf_api/test_listing.py tests/wf_api/test_artifact_helpers.py tests/wf_api/test_drafts_service.py tests/wf_api/test_artifact_api.py tests/wf_api/test_deployment_api.py tests/wf_api/test_run_api.py tests/wf_api/test_capability_api.py tests/wf_api/test_import_direction.py -q ``` Expected: pass. - [ ] **Step 2: Run adapter-focused workflow surface tests** Run: ```bash uv run pytest tests/wf_mcp/workflow_surface tests/wf_mcp/test_server.py -q ``` Expected: pass. If `tests/wf_mcp/workflow_surface` does not exist in this checkout, run the nearest existing workflow-surface test files discovered with `rg -n "WorkflowSurfaceHandlers|register_workflow_tools" tests/wf_mcp`. - [ ] **Step 3: Run lint and type checks** Run: ```bash uv run ruff check src/wf_api src/wf_mcp/workflow_surface tests/wf_api uv run ruff format --check src/wf_api src/wf_mcp/workflow_surface tests/wf_api uv run basedpyright --level error ``` Expected: all pass with zero new diagnostics. - [ ] **Step 4: Optional full suite** Run: ```bash uv run pytest -q ``` Expected: existing suite status remains at least as good as before this slice. --- ## Handoff Report Requirements When done, report: - Files created. - Files modified. - Exact helpers moved and their new canonical module. - Any helpers intentionally left in place and why. - Verification commands and outputs. - Any deviations from this plan. ## Self-Review - Spec coverage: covers roadmap Slice 5 listing cleanup and post-Slice-4 helper promotion. Event primitives are explicitly deferred because their semantics are larger than this helper cleanup. - Placeholder scan: no `TBD`, no unspecified edge handling, no “write tests for above” without concrete examples. - Type consistency: helper names are stable and public names omit leading underscores; domain modules should import from `wf_api.*`, never `wf_mcp.*`.