the WorkflowApiBackend collapses

This commit is contained in:
lda
2026-06-02 11:48:55 +07:00 Verified
parent 09995d86d5
commit 55d45226f4
19 changed files with 984 additions and 1385 deletions
+6 -6
View File
@@ -40,9 +40,9 @@ implementation state.
6. **Workflow API seam**
- `wf_api.WorkflowApi` is now the process-local application-facing workflow
API used by both CLI commands and MCP workflow tools.
- `wf_api` imports no `wf_mcp` modules. The current adapter,
`WfMcpWorkflowApiBackend`, still wraps the existing MCP service stack while
later slices extract protocol-neutral logic behind that seam.
- `wf_api` imports no `wf_mcp` modules. `WorkflowApi` composes domain
services directly from `WorkflowOperationContext`; MCP owns only context
construction and tool schemas.
- Protocol-neutral helpers now live in `wf_api`: refs/constants, wrapper
hints, next actions, raw workflow plans, runtime dependencies, saved
subgraph preparation, and durable run lifecycle helpers. Old
@@ -115,9 +115,9 @@ implementation state.
Protocol-neutral operation context and domain services now exist behind
`wf_api`; MCP tool schemas and tool registration stay in `wf_mcp`.
- Workflow store ownership is explicit: entrypoints construct/inject `WorkflowStores`; `WfMcpService` no longer guesses stores from the MCP store root.
- The next useful slice is removing the remaining double-delegation path so
`WorkflowApi` composes domain services directly instead of routing through
`WfMcpWorkflowApiBackend` and `WorkflowSurfaceHandlers`.
- Double-delegation has been removed: CLI and MCP workflow tools construct
`WorkflowApi(context_from_service(service))` directly. `WorkflowSurfaceHandlers`
remains only as a temporary compatibility shim for older imports.
Frame stress points remaining for native subgraphs and future fork/gather:
@@ -4,6 +4,11 @@
**Goal:** Extract a protocol-neutral workflow application API from `wf_mcp` while preserving current process-local behavior and avoiding a large semantic rewrite.
> Current update: the original `WorkflowApiBackend` seam was useful for proving
> dependency direction, but has been collapsed. `WorkflowApi` now composes
> domain services directly from `WorkflowOperationContext`; MCP owns only
> context construction and tool schemas.
**Architecture:** `wf_api` becomes the long-lived in-process application service layer. `wf_mcp`, `wf_cli`, and future HTTP/UI adapters call `wf_api`; `wf_api` must not import `wf_mcp`.
**Current State:** Slice 1 introduced `wf_api.WorkflowApi`,
@@ -0,0 +1,754 @@
# wf_api Remove Double Delegation 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:** Collapse workflow calls from `WorkflowApi -> WfMcpWorkflowApiBackend -> WorkflowSurfaceHandlers -> domain services` to `WorkflowApi -> domain services`.
**Architecture:** `WorkflowApi` becomes the protocol-neutral application facade that composes `WorkflowCapabilityApi`, `WorkflowDraftApi`, `WorkflowArtifactApi`, `WorkflowDeploymentApi`, and `WorkflowRunApi` from a `WorkflowOperationContext`. MCP and CLI construct `WorkflowApi(context_from_service(service))` directly. `WorkflowSurfaceHandlers` remains only as a temporary compatibility shim for legacy imports/tests, and `WfMcpWorkflowApiBackend` / `WorkflowApiBackend` are removed.
**Tech Stack:** Python 3.14, `wf_api`, `wf_mcp`, dataclasses, pytest, ruff, basedpyright.
---
## Current Chain
```text
wf_mcp tools / wf_cli
-> WorkflowApi
-> WfMcpWorkflowApiBackend
-> WorkflowSurfaceHandlers
-> WorkflowCapabilityApi / WorkflowDraftApi / WorkflowArtifactApi / WorkflowDeploymentApi / WorkflowRunApi
```
This creates two mechanical delegation layers. Adding one workflow operation currently requires touching at least `WorkflowApi`, `WorkflowApiBackend`, `WfMcpWorkflowApiBackend`, and usually `WorkflowSurfaceHandlers`.
## Target Chain
```text
wf_mcp tools / wf_cli
-> WorkflowApi
-> WorkflowCapabilityApi / WorkflowDraftApi / WorkflowArtifactApi / WorkflowDeploymentApi / WorkflowRunApi
```
Legacy imports of `WorkflowSurfaceHandlers` may still work, but only as a thin wrapper around `WorkflowApi`.
## Files
- Modify: `src/wf_api/models.py`
- Modify: `src/wf_api/__init__.py`
- Modify: `src/wf_api/service.py`
- Delete: `src/wf_api/backend.py`
- Delete: `src/wf_mcp/broker/service/workflow_api_backend.py`
- Modify: `src/wf_cli/context.py`
- Modify: `src/wf_cli/commands/runs.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- Modify: `src/wf_mcp/workflow_surface/handlers.py`
- Modify: `tests/wf_cli/test_context.py`
- Modify: `tests/wf_api/test_cli_context_uses_api.py`
- Add: `tests/wf_api/test_direct_service.py`
- Add: `tests/wf_api/test_no_double_delegation.py`
- Modify docs: `docs/current_roadmap.md`, `docs/wf_mcp_architecture.md`, `docs/superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md`
---
### Task 1: Move TraceRange Out of `backend.py`
**Files:**
- Modify: `src/wf_api/models.py`
- Modify: `src/wf_api/__init__.py`
- Modify: `src/wf_cli/commands/runs.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- [ ] **Step 1: Write import regression test**
Add to `tests/wf_api/test_raw_workflow_plan_extraction.py`:
```python
def test_trace_range_exports_from_wf_api_models() -> None:
from wf_api import TraceRange
from wf_api.models import TraceRange as CanonicalTraceRange
assert TraceRange is CanonicalTraceRange
assert TraceRange(start=1, limit=2).start == 1
assert TraceRange(start=1, limit=2).limit == 2
```
- [ ] **Step 2: Run failing test**
Run:
```bash
uv run pytest tests\wf_api\test_raw_workflow_plan_extraction.py::test_trace_range_exports_from_wf_api_models -q
```
Expected: fail because `wf_api.models.TraceRange` does not exist yet.
- [ ] **Step 3: Add `TraceRange` to `wf_api.models`**
In `src/wf_api/models.py`, add imports:
```python
from dataclasses import dataclass
```
Then add before `RawWorkflowPlan`:
```python
@dataclass(frozen=True, slots=True)
class TraceRange:
"""Caller-bounded debug trace slice for durable deployment runs."""
start: int = 0
limit: int = 25
```
- [ ] **Step 4: Update `wf_api.__init__` export**
Change:
```python
from .backend import TraceRange, WorkflowApiBackend
```
to:
```python
from .models import RawWorkflowPlan, TraceRange
```
Remove `"WorkflowApiBackend"` from `__all__`. Keep `"TraceRange"`.
If `RawWorkflowPlan` was not exported before, include it only if already expected by tests; do not add new public API unless the existing file already imports it elsewhere.
- [ ] **Step 5: Update imports that referenced `wf_api.backend.TraceRange`**
In `src/wf_cli/commands/runs.py`, replace:
```python
from wf_api.backend import TraceRange
```
with:
```python
from wf_api import TraceRange
```
In `src/wf_mcp/workflow_surface/tools.py`, remove:
```python
from wf_api.backend import TraceRange as ApiTraceRange
```
Also remove `_to_api_trace_range()`. Later tasks pass MCP `TraceRange` directly because `WorkflowRunApi` validates trace ranges structurally through `TraceRangeLike`.
- [ ] **Step 6: Verify Task 1**
Run:
```bash
uv run pytest tests\wf_api\test_raw_workflow_plan_extraction.py::test_trace_range_exports_from_wf_api_models tests\wf_cli\test_run_deploy.py tests\wf_mcp\server\test_tools.py -q
uv run ruff check src\wf_api\models.py src\wf_api\__init__.py src\wf_cli\commands\runs.py src\wf_mcp\workflow_surface\tools.py tests\wf_api\test_raw_workflow_plan_extraction.py
uv run ruff format --check src\wf_api\models.py src\wf_api\__init__.py src\wf_cli\commands\runs.py src\wf_mcp\workflow_surface\tools.py tests\wf_api\test_raw_workflow_plan_extraction.py
```
Expected: tests pass, lint pass, format pass.
---
### Task 2: Make `WorkflowApi` Compose Domain Services Directly
**Files:**
- Modify: `src/wf_api/service.py`
- Add: `tests/wf_api/test_direct_service.py`
- [ ] **Step 1: Write direct-composition tests**
Create `tests/wf_api/test_direct_service.py`:
```python
from __future__ import annotations
import asyncio
from wf_artifacts import FileWorkflowArtifactStore
from wf_api import WorkflowApi
from wf_api.artifacts import WorkflowArtifactApi
from wf_api.capabilities import WorkflowCapabilityApi
from wf_api.deployments import WorkflowDeploymentApi
from wf_api.drafts import WorkflowDraftApi
from wf_api.runs import WorkflowRunApi
from wf_mcp.broker import WfMcpService
from wf_mcp.broker.service.workflow_operation_context import context_from_service
from wf_mcp.models import ConnectionConfig
from wf_mcp.storage import FileStore
from tests.wf_mcp.test_support import echo_tool, local_temp_root
def _api() -> WorkflowApi:
root = local_temp_root() / "wf_api_direct_composition"
service = WfMcpService(
store=FileStore(root / "mcp"),
artifact_store=FileWorkflowArtifactStore(root),
)
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_specs("demo.personal", echo_tool)
return WorkflowApi(context_from_service(service))
def test_workflow_api_composes_domain_services() -> None:
api = _api()
assert isinstance(api.capabilities, WorkflowCapabilityApi)
assert isinstance(api.drafts, WorkflowDraftApi)
assert isinstance(api.artifacts, WorkflowArtifactApi)
assert isinstance(api.deployments, WorkflowDeploymentApi)
assert isinstance(api.runs, WorkflowRunApi)
assert not hasattr(api, "backend")
def test_workflow_api_direct_capability_call() -> None:
api = _api()
result = asyncio.run(
api.call_capability(
qualified_name="demo.personal.echo_tool",
payload={"text": "hello"},
)
)
assert result["kind"] == "node_spec"
assert result["outcome"] == "ok"
assert result["output"] == {"echoed": "hello"}
```
- [ ] **Step 2: Run failing tests**
Run:
```bash
uv run pytest tests\wf_api\test_direct_service.py -q
```
Expected: fail because `WorkflowApi` still expects a `WorkflowApiBackend`.
- [ ] **Step 3: Rewrite `WorkflowApi.__init__`**
In `src/wf_api/service.py`, replace:
```python
from .backend import TraceRange, WorkflowApiBackend
```
with:
```python
from .artifacts import WorkflowArtifactApi
from .capabilities import WorkflowCapabilityApi
from .deployments import WorkflowDeploymentApi
from .drafts import WorkflowDraftApi
from .models import TraceRange
from .operation_context import WorkflowOperationContext
from .runs import TraceRangeLike, WorkflowRunApi
```
Replace the class docstring and constructor:
```python
class WorkflowApi:
"""Protocol-neutral workflow application facade.
This facade owns the stable application entry point. It composes the
domain APIs from a WorkflowOperationContext so MCP, CLI, and future HTTP
callers share one operation surface without importing wf_mcp.
"""
def __init__(self, context: WorkflowOperationContext) -> None:
self.context = context
self.capabilities = WorkflowCapabilityApi(context)
self.drafts = WorkflowDraftApi(context)
self.artifacts = WorkflowArtifactApi(context)
self.deployments = WorkflowDeploymentApi(context)
self.runs = WorkflowRunApi(context)
```
- [ ] **Step 4: Replace backend delegations with domain service delegations**
In `src/wf_api/service.py`, replace these groups:
Capabilities:
```python
self.backend.list_capabilities(...) -> self.capabilities.list_capabilities(...)
self.backend.inspect_capability(...) -> self.capabilities.inspect_capability(...)
self.backend.call_capability(...) -> self.capabilities.call_capability(...)
self.backend.create_draft_workspace_from_capability(...) -> self.capabilities.create_draft_workspace_from_capability(...)
```
Artifacts:
```python
self.backend.list_artifacts(...) -> self.artifacts.list_artifacts(...)
self.backend.inspect_artifact(...) -> self.artifacts.inspect_artifact(...)
self.backend.save_artifact(...) -> self.artifacts.save_artifact(...)
self.backend.create_artifact_from_plan(...) -> self.artifacts.create_artifact_from_plan(...)
self.backend.create_artifact_from_draft(...) -> self.artifacts.create_artifact_from_draft(...)
self.backend.create_artifact_from_workspace(...) -> self.artifacts.create_artifact_from_workspace(...)
self.backend.create_wrapper_from_workspace(...) -> self.artifacts.create_wrapper_from_workspace(...)
```
Drafts:
```python
self.backend.validate_draft(...) -> self.drafts.validate_draft(...)
self.backend.compile_draft(...) -> self.drafts.compile_draft(...)
self.backend.patch_draft(...) -> self.drafts.patch_draft(...)
self.backend.list_draft_workspaces() -> self.drafts.list_draft_workspaces()
self.backend.create_draft_workspace(...) -> self.drafts.create_draft_workspace(...)
self.backend.get_draft_workspace(...) -> self.drafts.get_draft_workspace(...)
self.backend.delete_draft_workspace(...) -> self.drafts.delete_draft_workspace(...)
self.backend.validate_draft_workspace(...) -> self.drafts.validate_draft_workspace(...)
self.backend.patch_draft_workspace(...) -> self.drafts.patch_draft_workspace(...)
self.backend.set_draft_name(...) -> self.drafts.set_draft_name(...)
self.backend.set_draft_route(...) -> self.drafts.set_draft_route(...)
self.backend.set_step_input_map(...) -> self.drafts.set_step_input_map(...)
self.backend.set_step_output_map(...) -> self.drafts.set_step_output_map(...)
self.backend.create_minimal_draft_workspace(...) -> self.drafts.create_minimal_draft_workspace(...)
```
Deployments:
```python
self.backend.list_deployments() -> self.deployments.list_deployments()
self.backend.inspect_deployment(...) -> self.deployments.inspect_deployment(...)
self.backend.save_deployment(...) -> self.deployments.save_deployment(...)
self.backend.delete_deployment(...) -> self.deployments.delete_deployment(...)
self.backend.validate_deployment(...) -> self.deployments.validate_deployment(...)
```
Runs:
```python
self.backend.run_deployment(...) -> self.runs.run_deployment(...)
self.backend.resume_run(...) -> self.runs.resume_run(...)
self.backend.inspect_run(...) -> self.runs.inspect_run(...)
self.backend.read_run_trace(...) -> self.runs.read_run_trace(...)
```
For run methods, change type hints from `TraceRange | None` to `TraceRangeLike | None` and `TraceRange` to `TraceRangeLike` so MCP Pydantic `TraceRange` and CLI dataclass `TraceRange` both remain accepted structurally.
- [ ] **Step 5: Verify Task 2**
Run:
```bash
uv run pytest tests\wf_api\test_direct_service.py tests\wf_api\test_capability_api.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 -q
uv run ruff check src\wf_api\service.py tests\wf_api\test_direct_service.py
uv run ruff format --check src\wf_api\service.py tests\wf_api\test_direct_service.py
```
Expected: direct service tests and domain API tests pass.
---
### Task 3: Update CLI and MCP Tool Construction
**Files:**
- Modify: `src/wf_cli/context.py`
- Modify: `tests/wf_cli/test_context.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- Add: `tests/wf_api/test_no_double_delegation.py`
- [ ] **Step 1: Write no-backend-chain test**
Create `tests/wf_api/test_no_double_delegation.py`:
```python
from __future__ import annotations
import ast
from pathlib import Path
def _imports_module(path: Path, module_name: str) -> bool:
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module == module_name:
return True
if isinstance(node, ast.Import):
if any(alias.name == module_name for alias in node.names):
return True
return False
def test_cli_and_mcp_tools_do_not_import_backend_adapter() -> None:
root = Path(__file__).resolve().parents[2]
assert not _imports_module(
root / "src" / "wf_cli" / "context.py",
"wf_mcp.broker.service.workflow_api_backend",
)
assert not _imports_module(
root / "src" / "wf_mcp" / "workflow_surface" / "tools.py",
"wf_mcp.broker.service.workflow_api_backend",
)
```
- [ ] **Step 2: Run failing no-backend-chain test**
Run:
```bash
uv run pytest tests\wf_api\test_no_double_delegation.py -q
```
Expected: fail because CLI context and MCP tools still import `WfMcpWorkflowApiBackend`.
- [ ] **Step 3: Update CLI context**
In `src/wf_cli/context.py`, remove:
```python
from wf_mcp.broker.service.workflow_api_backend import WfMcpWorkflowApiBackend
```
Add:
```python
from wf_mcp.broker.service.workflow_operation_context import context_from_service
```
Change `load_cli_context()`:
```python
handlers=WorkflowApi(context_from_service(service)),
```
- [ ] **Step 4: Update CLI context test**
In `tests/wf_cli/test_context.py`, replace the private backend-chain assertion:
```python
assert context.handlers.backend._handlers.service is context.service # type: ignore[attr-defined]
```
with:
```python
assert context.handlers.context.artifact_store is context.service.artifact_store
assert context.handlers.context.draft_workspace_store is context.service.draft_workspace_store
assert context.handlers.context.run_store is context.service.run_store
```
This tests the public context seam instead of the deleted backend chain.
- [ ] **Step 5: Update MCP workflow tools**
In `src/wf_mcp/workflow_surface/tools.py`, remove:
```python
from wf_mcp.broker.service.workflow_api_backend import WfMcpWorkflowApiBackend
```
Add:
```python
from wf_mcp.broker.service.workflow_operation_context import context_from_service
```
Change:
```python
handlers = WorkflowApi(WfMcpWorkflowApiBackend(service))
```
to:
```python
handlers = WorkflowApi(context_from_service(service))
```
For run tools, pass `request.trace_range` or `trace_range` directly to `handlers.*`. Remove conversions through `ApiTraceRange`.
- [ ] **Step 6: Verify Task 3**
Run:
```bash
uv run pytest tests\wf_api\test_no_double_delegation.py tests\wf_cli\test_context.py tests\wf_cli tests\wf_mcp\server\test_tools.py tests\wf_mcp\workflow_surface -q
uv run ruff check src\wf_cli\context.py src\wf_mcp\workflow_surface\tools.py tests\wf_cli\test_context.py tests\wf_api\test_no_double_delegation.py
uv run ruff format --check src\wf_cli\context.py src\wf_mcp\workflow_surface\tools.py tests\wf_cli\test_context.py tests\wf_api\test_no_double_delegation.py
```
Expected: CLI and MCP workflow tool tests pass.
---
### Task 4: Shrink `WorkflowSurfaceHandlers` to Compatibility Shim
**Files:**
- Modify: `src/wf_mcp/workflow_surface/handlers.py`
- Modify: `tests/wf_api/test_direct_service.py` or add a small handler shim test
- [ ] **Step 1: Add compatibility shim test**
Add to `tests/wf_api/test_direct_service.py`:
```python
def test_workflow_surface_handlers_is_compatibility_shim() -> None:
from wf_api import WorkflowApi
from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
root = local_temp_root() / "workflow_surface_handler_shim"
service = WfMcpService(
store=FileStore(root / "mcp"),
artifact_store=FileWorkflowArtifactStore(root),
)
handlers = WorkflowSurfaceHandlers(service)
assert isinstance(handlers, WorkflowApi)
assert handlers.service is service
assert handlers.context.artifact_store is service.artifact_store
```
- [ ] **Step 2: Run failing compatibility test**
Run:
```bash
uv run pytest tests\wf_api\test_direct_service.py::test_workflow_surface_handlers_is_compatibility_shim -q
```
Expected: fail because `WorkflowSurfaceHandlers` is not a `WorkflowApi` subclass yet.
- [ ] **Step 3: Replace `WorkflowSurfaceHandlers` implementation**
Replace `src/wf_mcp/workflow_surface/handlers.py` with:
```python
from __future__ import annotations
from wf_api import WorkflowApi
from ..broker.service.workflow_operation_context import context_from_service
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..broker.service import WfMcpService
class WorkflowSurfaceHandlers(WorkflowApi):
"""Compatibility wrapper for old wf_mcp.workflow_surface imports.
New code should construct `WorkflowApi(context_from_service(service))`
directly. This shim keeps tests and legacy broker artifact tools working
while the MCP surface is migrated.
"""
def __init__(self, service: WfMcpService) -> None:
self.service = service
super().__init__(context_from_service(service))
__all__ = ["WorkflowSurfaceHandlers"]
```
This file should no longer import domain services directly.
- [ ] **Step 4: Verify handler compatibility**
Run:
```bash
uv run pytest tests\wf_api\test_direct_service.py::test_workflow_surface_handlers_is_compatibility_shim tests\wf_mcp\workflow_surface tests\wf_mcp\test_saved_subgraphs.py tests\wf_mcp\broker -q
uv run ruff check src\wf_mcp\workflow_surface\handlers.py tests\wf_api\test_direct_service.py
uv run ruff format --check src\wf_mcp\workflow_surface\handlers.py tests\wf_api\test_direct_service.py
```
Expected: old handler tests pass through the shim.
---
### Task 5: Delete Backend Protocol and Adapter
**Files:**
- Delete: `src/wf_api/backend.py`
- Delete: `src/wf_mcp/broker/service/workflow_api_backend.py`
- Modify: `src/wf_api/__init__.py`
- Modify docs that describe the old backend chain
- [ ] **Step 1: Delete backend files**
Delete:
```text
src/wf_api/backend.py
src/wf_mcp/broker/service/workflow_api_backend.py
```
- [ ] **Step 2: Remove public backend export**
In `src/wf_api/__init__.py`, ensure there is no import or `__all__` entry for `WorkflowApiBackend`.
- [ ] **Step 3: Search for live backend references**
Run:
```bash
rg -n "WorkflowApiBackend|WfMcpWorkflowApiBackend|workflow_api_backend|\\.backend" src tests
```
Expected: no live source/test references.
Historical docs under `docs/superpowers/plans/2026-06-01-*` may still mention the old slice. Do not rewrite historical plans except the active roadmap files named in Task 6.
- [ ] **Step 4: Verify deletion**
Run:
```bash
uv run pytest tests\wf_api\test_import_direction.py tests\wf_api\test_no_double_delegation.py tests\wf_api\test_cli_context_uses_api.py -q
uv run ruff check src\wf_api src\wf_cli\context.py src\wf_mcp\workflow_surface src\wf_mcp\broker\service tests\wf_api
uv run ruff format --check src\wf_api src\wf_cli\context.py src\wf_mcp\workflow_surface src\wf_mcp\broker\service tests\wf_api
```
Expected: tests pass, lint pass, format pass.
---
### Task 6: Update Active Docs
**Files:**
- Modify: `docs/current_roadmap.md`
- Modify: `docs/wf_mcp_architecture.md`
- Modify: `docs/superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md`
- [ ] **Step 1: Update `docs/current_roadmap.md`**
Replace the bullet that says the next useful slice is removing double-delegation with:
```markdown
- Double-delegation has been removed: CLI and MCP workflow tools construct
`WorkflowApi(context_from_service(service))` directly. `WorkflowSurfaceHandlers`
remains only as a temporary compatibility shim for older imports.
```
- [ ] **Step 2: Update `docs/wf_mcp_architecture.md`**
Find the architecture text that contains:
```text
wf_api.WorkflowApi ───> WorkflowApiBackend
```
Replace that diagram/text with:
```text
wf_mcp.workflow_surface.tools
-> wf_api.WorkflowApi
-> wf_api domain services
-> WorkflowOperationContext
-> WfMcpService adapters/stores/runtime
```
Add:
```markdown
`WorkflowSurfaceHandlers` is a compatibility shim only. New entrypoints should
construct `WorkflowApi(context_from_service(service))` directly.
```
- [ ] **Step 3: Update active extraction roadmap**
In `docs/superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md`, add a current-state note near the top:
```markdown
> Current update: the original `WorkflowApiBackend` seam was useful for proving
> dependency direction, but has been collapsed. `WorkflowApi` now composes
> domain services directly from `WorkflowOperationContext`; MCP owns only
> context construction and tool schemas.
```
Do not rewrite the historical task bodies. They describe prior slices.
- [ ] **Step 4: Verify docs**
Run:
```bash
git diff --check -- docs\current_roadmap.md docs\wf_mcp_architecture.md docs\superpowers\plans\2026-06-01-wf-api-extraction-roadmap.md
```
Expected: no whitespace errors.
---
### Task 7: Final Verification
**Files:**
- All touched files.
- [ ] **Step 1: Run focused workflow API/MCP/CLI tests**
Run:
```bash
uv run pytest tests\wf_api tests\wf_cli tests\wf_mcp\workflow_surface tests\wf_mcp\server\test_tools.py tests\wf_mcp\test_saved_subgraphs.py -q
```
Expected: selected tests pass.
- [ ] **Step 2: Run full suite**
Run:
```bash
uv run pytest -q
```
Expected: full suite passes with known skip/xfail counts.
- [ ] **Step 3: Run lint and format checks**
Run:
```bash
uv run ruff check src\wf_api src\wf_cli src\wf_mcp tests\wf_api tests\wf_cli tests\wf_mcp
uv run ruff format --check src\wf_api src\wf_cli src\wf_mcp tests\wf_api tests\wf_cli tests\wf_mcp
```
Expected: all checks pass.
- [ ] **Step 4: Run typecheck**
Run:
```bash
uv run basedpyright --level error
```
Expected: `0 errors, 0 warnings, 0 notes`. If the command exits nonzero only because of the known workspace enumeration warning, report that exactly.
- [ ] **Step 5: Final reference check**
Run:
```bash
rg -n "WorkflowApiBackend|WfMcpWorkflowApiBackend|workflow_api_backend|\\.backend" src tests docs\current_roadmap.md docs\wf_mcp_architecture.md
```
Expected: no references in live source/tests/current docs.
---
## Self-Review
- Spec coverage: The plan removes `WorkflowApiBackend`, deletes `WfMcpWorkflowApiBackend`, updates CLI/MCP construction, keeps handler compatibility, moves `TraceRange`, and updates active docs.
- Placeholder scan: No `TODO`/`TBD` placeholders remain.
- Type consistency: `WorkflowApi` accepts `WorkflowOperationContext`; run trace methods accept `TraceRangeLike`; `TraceRange` is a convenience DTO exported from `wf_api.models`.
- Scope check: This does not remove all `WorkflowSurfaceHandlers` tests or legacy imports. It reduces handlers to a shim; deleting the shim is a later cleanup once `wf_mcp.broker.artifact_tools` and legacy tests stop importing it.
+13 -20
View File
@@ -41,19 +41,24 @@ relevant concern package directly.
Workflow lifecycle operations now have a protocol-neutral front door:
```text
wf_cli ─┐
├──> wf_api.WorkflowApi ───> WorkflowApiBackend
wf_mcp ─┘
wf_mcp.workflow_surface.tools
-> wf_api.WorkflowApi
-> wf_api domain services
-> WorkflowOperationContext
-> WfMcpService adapters/stores/runtime
```
The current backend is `wf_mcp.broker.service.WfMcpWorkflowApiBackend`, which
wraps the existing `wf_mcp.workflow_surface.WorkflowSurfaceHandlers`. That
handler class still contains most workflow-surface logic and still depends on
`WfMcpService`; it is kept for compatibility and incremental extraction.
`WorkflowSurfaceHandlers` is a compatibility shim only. New entrypoints should
construct `WorkflowApi(context_from_service(service))` directly.
The old backend-adapter/protocol layer has been removed. `WorkflowApi` composes
domain services (`WorkflowCapabilityApi`, `WorkflowDraftApi`,
`WorkflowArtifactApi`, `WorkflowDeploymentApi`, `WorkflowRunApi`) from a
`WorkflowOperationContext`.
New code should treat `wf_api.WorkflowApi` as the application-facing API. Do not
add new callers that import `WorkflowSurfaceHandlers` directly unless they are
inside the MCP backend adapter or compatibility tests.
compatibility tests.
This is a dependency-direction cleanup, not a full domain split. Most API
methods still mirror the old workflow-surface payloads and return
@@ -66,18 +71,6 @@ old `wf_mcp.workflow_surface.*` helper modules are compatibility shims. MCP
tool schemas and registration still live in `wf_mcp.workflow_surface.models` and
`wf_mcp.workflow_surface.tools`.
The remaining extraction work is the large operation implementation:
`WorkflowSurfaceHandlers` still owns most capability, draft, artifact,
deployment, and run methods. Split that implementation by domain only after the
current helper shims are stable.
Do not move those method bodies directly into `wf_api` while they still depend
on the whole `WfMcpService`. The next extraction step should define a
protocol-neutral operation context/protocol seam for stores, capability sources,
event recording, and live source calls. MCP-owned code can adapt `WfMcpService`
into that seam; domain services can then depend on the seam rather than on MCP
service internals.
## Broker Catalogs
The broker keeps two related catalog views: