# Remote CLI Lifecycle RPC Methods 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:** Make the `wf` draft/artifact/deploy commands work against `client.target.kind = "rpc_http"` instead of failing fast as local-only commands. **Architecture:** Extend the fixed JSON-RPC method set, then extend `RpcWorkflowApiClient` to structurally support every `WorkflowApi` method used by the CLI. Only after the client and server both support the methods should the CLI command modules switch from local-only context to target-aware context. Keep this as remote workflow lifecycle support, not source registry/auth/MCP hosting. **Tech Stack:** Python 3.14, Pydantic v2, fastapi-jsonrpc, httpx, Typer, pytest, ruff, basedpyright. --- ## Scope In scope: - Add missing JSON-RPC methods for draft workspace, artifact, and deployment operations used by CLI. - Add matching `RpcWorkflowApiClient` methods. - Route `wf draft`, `wf artifact`, and `wf deploy` through `load_cli_context_from_typer`. - Add remote CLI integration tests for create draft → validate → save artifact → save deployment → validate deployment. - Keep existing local CLI behavior and old `wf_mcp.config.json` compatibility. Out of scope: - Source registry. - MCP/OpenAPI source config. - `/mcp` hosting. - Auth. - SQL stores. - Remote docs/schema/explain commands. - Streaming/progress. --- ## Required Method Coverage The client must implement every method used by these command modules: ```text src/wf_cli/commands/drafts.py list_draft_workspaces get_draft_workspace create_draft_workspace_from_capability patch_draft_workspace validate_draft_workspace create_wrapper_from_workspace create_artifact_from_workspace src/wf_cli/commands/artifacts.py list_artifacts inspect_artifact src/wf_cli/commands/deployments.py validate_deployment list_deployments inspect_deployment save_deployment delete_deployment ``` Do not route a command module to target-aware context until the client has the methods that module calls. --- ## Task 1: Add Missing RPC DTOs and Server Methods for Artifacts/Deployments **Files:** - Modify: `src/wf_transport_rpc_http/models.py` - Modify: `src/wf_transport_rpc_http/app.py` - Modify: `src/wf_transport_rpc_http/__init__.py` - Modify: `tests/wf_transport_rpc_http/test_app.py` - [ ] **Step 1: Add focused RPC app tests** Append a test that seeds an artifact/deployment through `server.api`, then calls: ```text workflow.artifacts.list workflow.artifacts.inspect workflow.deployments.list workflow.deployments.inspect workflow.deployments.delete ``` Assertions: ```python assert listed_artifacts["result"]["nodes"] assert inspected_artifact["result"]["artifact_id"] == "rpc_lifecycle" assert listed_deployments["result"]["deployments"] assert inspected_deployment["result"]["deployment_id"] == "rpc_lifecycle.default" assert deleted["result"]["deployment_id"] == "rpc_lifecycle.default" ``` Use the existing `_constant_plan()` helper in `tests/wf_transport_rpc_http/test_app.py` if possible. Do not bypass the RPC app for the operations under test. - [ ] **Step 2: Run the new test and verify method-not-found failures** Run: ```bash uv run pytest tests/wf_transport_rpc_http/test_app.py::test_rpc_artifact_and_deployment_catalog_methods -q ``` Expected: fail with JSON-RPC method-not-found for the newly required methods. - [ ] **Step 3: Add DTOs** In `src/wf_transport_rpc_http/models.py`, add: ```python class ListArtifactsParams(RpcParamsModel): query: str | None = None kind: str | None = None cursor: str | None = None limit: int = Field(default=50, ge=1, le=100) class InspectArtifactParams(RpcParamsModel): artifact_id: str = Field(min_length=1) version: int = Field(ge=1) class ListDeploymentsParams(RpcParamsModel): pass class InspectDeploymentParams(RpcParamsModel): deployment_id: str = Field(min_length=1) class DeleteDeploymentParams(RpcParamsModel): deployment_id: str = Field(min_length=1) ``` Export these from `src/wf_transport_rpc_http/__init__.py`. - [ ] **Step 4: Register artifact/deployment methods** In `src/wf_transport_rpc_http/app.py`, import the new DTOs and register: ```text workflow.artifacts.list -> server.api.list_artifacts workflow.artifacts.inspect -> server.api.inspect_artifact workflow.deployments.list -> server.api.list_deployments workflow.deployments.inspect -> server.api.inspect_deployment workflow.deployments.delete -> server.api.delete_deployment ``` Use the same expected-error handling as existing methods: ```python except (ValueError, KeyError, LookupError, FileNotFoundError) as exc: raise_workflow_rpc_error(exc) ``` For `ListArtifactsParams.kind`, pass the string through as `kind=params.kind`. If basedpyright complains because `WorkflowApi.list_artifacts` expects `ArtifactKind | None`, narrow with: ```python kind = params.kind if params.kind in {"workflow", "wrapper"} else None ``` and reject invalid values in the DTO if needed. - [ ] **Step 5: Run focused transport tests** Run: ```bash uv run pytest tests/wf_transport_rpc_http/test_app.py -q uv run ruff check src/wf_transport_rpc_http tests/wf_transport_rpc_http uv run basedpyright --level error src/wf_transport_rpc_http tests/wf_transport_rpc_http ``` Expected: tests pass, ruff clean, basedpyright 0 errors. --- ## Task 2: Add Draft Workspace RPC Methods **Files:** - Modify: `src/wf_transport_rpc_http/models.py` - Modify: `src/wf_transport_rpc_http/app.py` - Modify: `src/wf_transport_rpc_http/__init__.py` - Modify: `tests/wf_transport_rpc_http/test_app.py` - [ ] **Step 1: Add focused draft workspace RPC test** Append a test that calls: ```text workflow.draft_workspaces.create_from_capability workflow.draft_workspaces.list workflow.draft_workspaces.get workflow.draft_workspaces.validate workflow.draft_workspaces.patch workflow.draft_workspaces.create_artifact workflow.draft_workspaces.create_wrapper ``` Use a small patch such as changing the draft name or title through the existing workspace patch format. Assert: ```python assert created["result"]["workspace_id"] == "remote_ws" assert listed["result"]["workspaces"] assert fetched["result"]["workspace_id"] == "remote_ws" assert validated["result"]["status"] in {"valid", "invalid"} assert patched["result"]["revision"] == created["result"]["revision"] + 1 assert artifact["result"]["artifact_id"] == "remote_artifact" assert wrapper["result"]["artifact_id"] == "remote_wrapper" ``` If wrapper creation requires an output-capable draft and the simple capability draft cannot satisfy it, keep `create_wrapper` covered by client method tests and document why the RPC app integration test only covers `create_artifact`. - [ ] **Step 2: Run the new test and verify method-not-found failures** Run: ```bash uv run pytest tests/wf_transport_rpc_http/test_app.py::test_rpc_draft_workspace_methods -q ``` Expected: fail with method-not-found for missing workspace methods. - [ ] **Step 3: Add DTOs** Add to `src/wf_transport_rpc_http/models.py`: ```python class ListDraftWorkspacesParams(RpcParamsModel): pass class GetDraftWorkspaceParams(RpcParamsModel): workspace_id: str = Field(min_length=1) include_draft: bool = False class PatchDraftWorkspaceParams(RpcParamsModel): workspace_id: str = Field(min_length=1) revision: int = Field(ge=1) patch: list[dict[str, Any]] class ValidateDraftWorkspaceParams(RpcParamsModel): workspace_id: str = Field(min_length=1) class CreateArtifactFromWorkspaceParams(RpcParamsModel): workspace_id: str = Field(min_length=1) artifact_id: str = Field(min_length=1) version: int = Field(ge=1) title: str = Field(min_length=1) outcomes: list[str] kind: str = "workflow" description: str | None = None required_capabilities: dict[str, dict[str, Any]] | None = None source_bindings: dict[str, str] | None = None created_from_catalog_version: str | None = None class CreateWrapperFromWorkspaceParams(RpcParamsModel): workspace_id: str = Field(min_length=1) artifact_id: str = Field(min_length=1) version: int = Field(ge=1) title: str = Field(min_length=1) outcomes: list[str] description: str | None = None required_capabilities: dict[str, dict[str, Any]] | None = None source_bindings: dict[str, str] | None = None created_from_catalog_version: str | None = None ``` Export these from `src/wf_transport_rpc_http/__init__.py`. - [ ] **Step 4: Register draft workspace methods** In `src/wf_transport_rpc_http/app.py`, register: ```text workflow.draft_workspaces.list workflow.draft_workspaces.get workflow.draft_workspaces.create_from_capability workflow.draft_workspaces.patch workflow.draft_workspaces.validate workflow.draft_workspaces.create_artifact workflow.draft_workspaces.create_wrapper ``` Map them to the matching `server.api` methods: ```python server.api.list_draft_workspaces() server.api.get_draft_workspace(...) server.api.create_draft_workspace_from_capability(...) server.api.patch_draft_workspace(...) server.api.validate_draft_workspace(...) server.api.create_artifact_from_workspace(...) server.api.create_wrapper_from_workspace(...) ``` The existing `workflow.drafts.create_from_capability` method may stay for backward compatibility. It can call the same API method as `workflow.draft_workspaces.create_from_capability`. - [ ] **Step 5: Run focused transport tests** Run: ```bash uv run pytest tests/wf_transport_rpc_http/test_app.py -q uv run ruff check src/wf_transport_rpc_http tests/wf_transport_rpc_http uv run basedpyright --level error src/wf_transport_rpc_http tests/wf_transport_rpc_http ``` Expected: tests pass, ruff clean, basedpyright 0 errors. --- ## Task 3: Extend RpcWorkflowApiClient **Files:** - Modify: `src/wf_transport_rpc_http/client.py` - Modify: `tests/wf_transport_rpc_http/test_client.py` - [ ] **Step 1: Add client tests for the newly exposed methods** Add tests covering: ```text list_artifacts / inspect_artifact list_deployments / inspect_deployment / validate_deployment / delete_deployment list_draft_workspaces / get_draft_workspace / validate_draft_workspace create_draft_workspace_from_capability patch_draft_workspace create_artifact_from_workspace create_wrapper_from_workspace if feasible ``` Use `httpx.ASGITransport(app=create_rpc_app(server))` like the existing client tests. Seed artifacts/deployments through `server.api` where the method under test is only read/list/inspect. For create/patch workspace tests, call through the client. - [ ] **Step 2: Run client tests and verify failures** Run: ```bash uv run pytest tests/wf_transport_rpc_http/test_client.py -q ``` Expected: fail because `RpcWorkflowApiClient` lacks the new methods. - [ ] **Step 3: Implement client methods** Add one client method for each CLI-used `WorkflowApi` method listed in "Required Method Coverage". Wire methods to the JSON-RPC names from Tasks 1-2. Examples: ```python async def list_artifacts(...): return await self._call("workflow.artifacts.list", {...}) async def get_draft_workspace(...): return await self._call("workflow.draft_workspaces.get", {...}) async def create_artifact_from_workspace(...): return await self._call("workflow.draft_workspaces.create_artifact", {...}) ``` Keep signatures close to `WorkflowApi` so basedpyright accepts CLI command calls without casts. - [ ] **Step 4: Run client and type checks** Run: ```bash uv run pytest tests/wf_transport_rpc_http/test_client.py -q uv run basedpyright --level error src/wf_transport_rpc_http src/wf_cli ``` Expected: client tests pass and basedpyright reports 0 errors. --- ## Task 4: Route Draft/Artifact/Deploy CLI Through Target-Aware Context **Files:** - Modify: `src/wf_cli/commands/drafts.py` - Modify: `src/wf_cli/commands/artifacts.py` - Modify: `src/wf_cli/commands/deployments.py` - Modify: `tests/wf_cli/test_remote_target.py` - [ ] **Step 1: Write remote CLI lifecycle test** Add a test that starts an in-process JSON-RPC app and invokes Typer commands with: ```text wf --config wf.json --url http://test/rpc draft create-from-capability ... wf --config wf.json --url http://test/rpc draft validate ... wf --config wf.json --url http://test/rpc draft save ... wf --config wf.json --url http://test/rpc artifact inspect ... wf --config wf.json --url http://test/rpc deploy save ... wf --config wf.json --url http://test/rpc deploy validate ... ``` Use the same `httpx.AsyncClient` monkeypatch pattern already present in `tests/wf_cli/test_remote_target.py`. Assertions: ```python assert created.exit_code == 0 assert validated.exit_code == 0 assert saved_artifact.exit_code == 0 assert inspected_artifact.exit_code == 0 assert saved_deployment.exit_code == 0 assert validated_deployment.exit_code == 0 ``` Also assert key output fragments: ```python assert '"workspace_id": "remote_ws"' in created.output assert '"status": "valid"' in validated.output assert '"artifact_id": "remote_artifact"' in saved_artifact.output assert '"deployment_id": "remote_artifact.default"' in saved_deployment.output assert '"status": "runnable"' in validated_deployment.output ``` - [ ] **Step 2: Run test and verify failure** Run: ```bash uv run pytest tests/wf_cli/test_remote_target.py::test_wf_remote_draft_artifact_deploy_lifecycle -q ``` Expected: fail because command modules still use local-only context. - [ ] **Step 3: Switch command imports** In `src/wf_cli/commands/drafts.py`, `artifacts.py`, and `deployments.py`, replace: ```python from wf_cli.context import load_local_cli_context_from_typer as load_cli_context ``` with: ```python from wf_cli.context import load_cli_context_from_typer as load_cli_context ``` Keep the local `load_cli_context` alias name so existing tests that monkeypatch `wf_cli.commands..load_cli_context` keep working. - [ ] **Step 4: Run CLI tests** Run: ```bash uv run pytest tests/wf_cli/test_remote_target.py tests/wf_cli/test_run_deploy.py tests/wf_cli/test_discovery_lifecycle.py -q uv run basedpyright --level error src/wf_cli src/wf_transport_rpc_http ``` Expected: tests pass and basedpyright reports 0 errors. --- ## Task 5: Documentation and Verification **Files:** - Modify: `docs/superpowers/specs/2026-06-03-workflow-config-targets-and-sources.md` - Modify: `docs/current_roadmap.md` - [ ] **Step 1: Update spec status** Update the implementation status to say: ```markdown - remote JSON-RPC client support now covers capability, draft workspace, artifact, deployment, and run CLI commands - draft/artifact/deploy commands no longer fail fast for `rpc_http` targets ``` Remove or update the older local-only/fail-fast status line. - [ ] **Step 2: Update roadmap** Update the roadmap note to say the basic remote CLI lifecycle is wired: ```markdown selected `wf` commands can target JSON-RPC HTTP ``` should become: ```markdown the basic `wf` lifecycle can target JSON-RPC HTTP: capability discovery, draft workspace authoring, artifact/deployment operations, run, inspect, and bounded trace. ``` - [ ] **Step 3: Run focused verification** Run: ```bash uv run pytest tests/wf_transport_rpc_http tests/wf_cli/test_remote_target.py tests/wf_cli/test_run_deploy.py tests/wf_cli/test_discovery_lifecycle.py -q uv run ruff check src/wf_transport_rpc_http src/wf_cli tests/wf_transport_rpc_http tests/wf_cli uv run ruff format --check src/wf_transport_rpc_http src/wf_cli tests/wf_transport_rpc_http tests/wf_cli uv run basedpyright --level error src/wf_transport_rpc_http src/wf_cli tests/wf_transport_rpc_http tests/wf_cli ``` Expected: pass, except basedpyright may still exit non-zero for the known workspace enumeration warning only when run at full workspace scope. For the focused command above, expect 0 errors. - [ ] **Step 4: Run full verification** Run: ```bash uv run pytest -q uv run ruff check uv run ruff format --check uv run basedpyright --level error ``` Expected: - pytest passes with current skip/xfail count - ruff passes - basedpyright reports `0 errors`; if it exits 1 only due to workspace enumeration warning, report that exactly --- ## Self-Review Notes This plan intentionally adds server RPC methods before client methods and client methods before CLI routing. That sequence prevents the previous half-migration problem where commands could receive a partial client. Do not remove old local tests or monkeypatch seams. The command modules can keep a local import alias named `load_cli_context` for compatibility with existing tests, but the imported helper should become target-aware after Task 4.