Files
lda-wf/docs/superpowers/specs/2026-06-03-long-lived-workflow-api-boundary.md
T

28 KiB

Long-Lived Workflow API Boundary

Date: 2026-06-03

Status: Slices 1-5 implemented. wf_server provides build_local_static_workflow_server; wf_mcp.broker.server can adapt MCP broker config/services into the neutral WorkflowServer; wf_transport_rpc_http provides JSON-RPC methods and client support; wf_cli has target-aware context; and wf_config owns neutral config models. WebSocket transport, auth, streaming/progress, database backend, and live source hot reload remain future work.

Related:

Purpose

Define the boundary for a long-lived workflow API process that clients can connect to and use for workflow authoring, deployment, run, inspect, trace, and resume operations.

The goal is not "HTTP for its own sake." The goal is a durable server process that can run workflows as well as the current local CLI/MCP process, while keeping workflow semantics in wf_api and transport/session details outside it.

Core Decision

Introduce a process-host layer before introducing HTTP details.

Recommended package shape:

wf_server
  long-lived process composition
  required store construction
  source/catalog/runtime/event implementation wiring
  durable WorkflowOperationContext construction

wf_transport_rpc_http
  JSON-RPC 2.0 over HTTP endpoint/controller
  request/response envelope translation
  auth/session/streaming transport policy

wf_api
  protocol-neutral workflow application operations
  no HTTP imports
  no MCP imports

wf_mcp
  MCP transport and upstream MCP integration

wf_mcp is intentionally treated as a combined compatibility package in this diagram, not as the desired final boundary. It currently contains two different roles:

  • MCP as a client transport: an MCP client connects to workflow/admin surfaces.
  • MCP as an upstream source provider: workflows discover and invoke external MCP servers as capability sources.

Those roles should separate over time. A future wf_transport_mcp can expose the same WorkflowServer / wf_api surfaces to MCP clients, while a future wf_sources_mcp can own upstream MCP discovery, sessions, tool invocation, resource/prompt access, and FastMCP-specific provider behavior.

MCP Source Provider Package Direction

wf_sources_mcp is the target package for MCP-as-upstream-source code. It should own behavior required to turn external MCP servers into workflow capability sources:

  • MCP auth interpretation for stdio/http transports
  • desired MCP source registry models and conversion into runtime connection records
  • source catalog snapshot cache stores
  • upstream discovery and tool/resource/prompt invocation adapters
  • stateful MCP runtime/session management
  • FastMCP-specific compatibility behavior

wf_sources_mcp must not own workflow lifecycle APIs, MCP frontend tool schemas, or old wf-mcp entrypoint behavior. Those belong to wf_api, transport packages, or compatibility shims.

First slices should move leaf modules only and leave wf_mcp re-export shims:

  1. Complete: MCP auth helpers and focused auth/catalog stores moved to wf_sources_mcp, with wf_mcp shims preserved.
  2. Complete: MCP source registry models/conversion moved to wf_sources_mcp.source_registry, with wf_mcp.source_registry retained as a shim.
  3. Complete: upstream MCP catalog/discovery DTOs moved to wf_sources_mcp.catalog, with wf_mcp.capabilities and wf_mcp.catalog.models retained as shims.
  4. Complete: upstream SDK protocol/result types moved to wf_sources_mcp.sdk, with wf_mcp.sdk and wf_mcp.runtime.protocols retained as shims.
  5. Complete: MCP SDK conversion helpers moved to wf_sources_mcp.sdk.converters, with wf_mcp.sdk.converters retained as a shim.
  6. Complete: shared MCP session opener in wf_sources_mcp.client. One-shot adapter (McpSdkAdapter) and persistent runtime (PersistentSessionFactory) both use open_mcp_session.
  7. Complete: persistent MCP runtime (PersistentMcpSession, PersistentSessionFactory, McpRuntimePool, connection_runtime_fingerprint) moved to wf_sources_mcp.runtime, with wf_mcp.runtime.* retained as compatibility shims. Runtime remains tool-call-only. Next slice is moving McpSdkAdapter to wf_sources_mcp.sdk.adapter.
  8. Complete: one-shot MCP SDK adapter moved to wf_sources_mcp.sdk.adapter, with wf_mcp.sdk.adapter retained as a compatibility shim. This does not expand persistent runtime; the next design slice should unify one-shot and persistent client operation handling behind a shared source-client facade.
  9. Complete: shared McpSourceClient facade introduced in wf_sources_mcp.client. McpSdkAdapter now delegates operation handling to this facade. Persistent runtime still exposes only call_tool; expanding it requires a separate owner-task request routing slice.
  10. Complete: persistent MCP runtime owner now routes explicit callables through a generic operation queue with request metadata. The runtime still exposes only call_tool; non-tool methods require a separate public-surface slice.
  11. Complete: persistent MCP runtime can route read_resource through the owner-task queue and McpSourceClient. Runtime still does not expose raw method invocation, notifications, or discovery list operations.
  12. Complete: persistent MCP runtime can route get_prompt through the owner-task queue and McpSourceClient. Runtime still does not expose raw method invocation, notifications, or discovery list operations.
  13. Complete: broker content access now prefers configured StatefulMcpRuntime for resource and prompt reads, falling back to the one-shot adapter when no stateful runtime is configured. Catalog refresh/discovery remains one-shot.
  14. Complete: stateful MCP runtime protocols split into tool/resource/prompt slices. Runtime can route list_resources and list_prompts through the owner task for session-scoped listings; catalog refresh remains one-shot.
  15. Complete: MCP source catalog aggregation helpers (CombinedCatalog and snapshot_from_specs) moved to wf_sources_mcp.catalog, with wf_mcp.broker.catalog retained as a compatibility shim.
  16. Complete: MCP upstream capability discovery moved to wf_sources_mcp.discovery, with wf_mcp.broker.discovery retaining compatibility re-exports. specs_from_discovered_tools remains in wf_mcp until the wrapper/event seam is neutralized.
  17. Complete: MCP JSON-schema-to-Pydantic model helper moved to wf_sources_mcp.schema_models. This removes the broker catalog hydration dependency on private wf_mcp.workflow.wrappers helpers; eventful tool wrapping remains in wf_mcp for the next seam.
  18. Complete: MCP tool wrapper event emission now uses neutral wf_sources_mcp.tool_events DTOs; wf_mcp.broker.discovery adapts them to broker-local McpEvent. wrap_discovered_tool remains in wf_mcp until the next move slice.
  19. Complete: MCP discovered-tool wrapper generation (wrap_discovered_tool) moved to wf_sources_mcp.tool_wrappers, with wf_mcp.workflow retained as a compatibility shim. specs_from_discovered_tools remains in wf_mcp as the broker event projection adapter.
  20. Complete: neutral specs_from_discovered_tools moved to wf_sources_mcp.discovery. wf_mcp.broker.discovery remains as the broker adapter for legacy ConnectionConfig input and McpEvent projection.
  21. Complete: upstream MCP adapter lookup (require_adapter) moved to wf_sources_mcp.adapters, with wf_mcp.broker.service.adapters retained as a compatibility shim.
  22. Complete: MCP source ID validation and reserved source IDs are canonical in wf_sources_mcp.ids; legacy wf_mcp.connections / wf_mcp.shared.names remain compatibility consumers.
  23. Complete: broker DTO construction removed from wf_sources_mcp. wf_sources_mcp accepts legacy-shaped inputs structurally, while wf_mcp.source_registry owns helpers that construct ConnectionConfig.
  24. Complete: McpRuntimePool implements the full MCP operation surface (list_tools, resources, prompts, tools, raw methods, notifications, and local metadata). Broker upstream operations prefer the persistent runtime when configured and fall back to one-shot adapters.
  25. Complete: deterministic JSON-RPC integration coverage proves MCP-backed workflow runs reuse one persistent runtime session for repeated operations against the same source.
  26. Upstream transport/discovery/session services.

Each slice should add import-direction tests so the new source-provider package does not depend on wf_mcp.workflow_surface, wf_mcp.admin_surface, wf_mcp.server, or wf_mcp.proxy. Temporary imports from broker model shims are acceptable only when documented in the slice plan; the direction is to replace them with protocol/DTO seams before moving runtime services.

wf_transport_rpc_http should call WorkflowApi through the server composition. It should not call WfMcpService.

HTTP is one transport adapter, not "the API." JSON-RPC over HTTP, JSON-RPC over WebSocket, and possibly MCP can become sibling transports around the same server/application boundary. Server composition should be driven by the wider neutral wf_config model, including source definitions; MCP broker config is a legacy/source-specific input to normalize, not a permanent peer config family.

wf_server may initially be small. Its role is to prove that a non-MCP process can construct the same application boundary with required stores and an explicit runtime/source implementation.

MCP UI/App Support Policy

MCP UI metadata is not workflow-server capability by itself. Upstream MCP tools may expose _meta.ui / ui://... resources, but rendering those resources is a host/frontend responsibility, not a workflow runtime responsibility.

Until a dedicated MCP frontend transport owns the full host behavior, do not advertise MCP Apps/UI support to clients. Supporting it requires more than passing metadata through:

  • serving ui:// resources and any dependent assets from a stable origin
  • sandboxed iframe hosting
  • a JSON-RPC postMessage / app bridge
  • app-only tool visibility and calls
  • CSP/domain policy
  • widget state and teardown semantics

wf_sources_mcp / current upstream MCP source code may preserve UI metadata as observed source metadata, but it must not imply that wf_server can render or host widgets. wf_transport_mcp may later implement a real host bridge. Raw MCP proxy/debug modes can expose upstream behavior explicitly, but that path is not the durable workflow surface.

Why Not Wrap WfMcpService

WfMcpService is still a compatibility facade for MCP broker concerns:

  • connection config/reload
  • upstream MCP adapter/session management
  • catalog refresh
  • content access
  • event recording
  • runtime execution
  • source inventory

It is being decomposed into focused services, but its public shape still carries MCP process assumptions. Building HTTP on top of it would recreate the coupling that wf_api extraction just removed.

The long-lived API should depend on the same lower-level concepts as MCP, not on the MCP facade itself.

First Slice

First slice should be lightweight and local/static.

It should prove:

  • a long-lived process can construct a durable WorkflowApi
  • artifact, draft, and run stores are required up front
  • a client can connect and call workflow operations
  • deployment run/inspect/trace/resume semantics match local behavior
  • no direct dependency on WfMcpService

Implementation status:

  • Slice 1 complete: wf_server.build_local_static_workflow_server() constructs a durable WorkflowApi with required file-backed stores, local wf.std/wf.recipes sources, and a local runtime runner.
  • Slice 2 complete: wf_transport_rpc_http provides JSON-RPC 2.0 over HTTP via create_rpc_app(server) and the wf-rpc-server CLI.
  • Slice 3 complete: wf_cli supports target-aware context with --local, --url, and --timeout overrides, and works with remote RPC targets for capability and run commands.
  • Slice 4 complete: wf_mcp.broker.server.build_workflow_server_from_config() returns a neutral WorkflowServer backed by MCP broker runtime services, including source registry admin and platform admin surfaces.
  • Slice 5 complete: wf-rpc-server --mcp-config <path> starts JSON-RPC over an MCP-backed WorkflowServer; --store-root remains local/static-only.

Model slice complete when wf_config.server.sources[] accepts kind: "mcp" entries. The next slice converts those neutral source entries into MCP broker runtime connections and server composition.

Runtime bridge complete when wf-rpc-server --config <path> can compose an MCP-backed server from neutral server.sources[] entries. --mcp-config remains a compatibility alias until existing users migrate.

First slice should not include:

  • live upstream MCP source management
  • OpenAPI dynamic source registration
  • auth beyond a stub or disabled mode
  • streaming/progress
  • transactional database backend
  • multi-worker concurrency guarantees
  • config hot reload

Acceptable first-slice source support:

  • broker-local workflow stdlib capabilities such as wf.std
  • saved workflow artifacts/deployments from the configured store
  • explicitly registered local NodeSpecs when the server process starts

This is enough to prove the server can run real workflows and durable resumes without dragging upstream source lifecycle into the first implementation.

Runtime and Store Boundary

The server process should construct:

WorkflowStores
  artifact_store
  draft_workspace_store
  run_store

WorkflowOperationContext
  artifact_store
  draft_workspace_store
  run_store
  events
  specs
  runtime
  live_sources=None or local-only checker

WorkflowApi

The context must pass require_workflow_stores() before being exposed through the long-lived API.

Current config exposes one default server.store root. The server then fans it out into workflow stores, auth records, source registry state, and catalog/cache state. That is acceptable for the first durable server path, but the boundary should not assume every persistence role always shares one backend.

Future config should support optional role-specific store overrides:

server.store              default for every missing role
server.stores.workflow    artifacts, deployments, drafts, runs, traces
server.stores.auth        auth records or secret-manager references
server.stores.sources     desired source registry entries
server.stores.catalog     source catalog/cache snapshots

The compatibility rule is: if a role store is absent, use server.store. First implementation should keep overrides filesystem-only; SQL, object storage, and secret-manager adapters are later backend implementations.

The first server runtime may reuse existing implementation classes when they do not require MCP-specific behavior. If reuse would require constructing WfMcpService, that is the wrong dependency direction.

Transport Contract

Every transport should follow the same shape:

transport request
  -> decode/validate transport envelope
  -> call WorkflowApi method
  -> encode transport response

Transport adapters own:

  • route names, method names, or JSON-RPC method names
  • request parsing
  • response serialization
  • auth/session headers or connection identity
  • streaming/progress mechanics

Transport adapters must not own:

  • deployment validation semantics
  • run/resume state transitions
  • trace paging semantics
  • wrapper hint policy
  • source dependency validation

Client Contract

Clients should be able to perform the same workflow lifecycle remotely that they can perform locally:

list/inspect capabilities
create/patch/validate draft workspace
save artifact
save/validate deployment
run deployment
inspect run
read bounded trace
resume interrupted run
delete deployment

Response payloads should preserve current wf_api semantics:

  • list operations stay compact
  • inspect operations return detail
  • run responses return compact status plus trace metadata
  • trace entries require explicit bounded range
  • interrupted runs expose resume next actions
  • blocked resume returns diagnostics without consuming input

Transport field names may differ only when transport conventions require it. They must not change workflow status meanings.

Error and Failure Semantics

The long-lived server must preserve the persisted run/resume contract:

  • invalid deployment dependencies before start return unrunnable
  • completed, failed, and interrupted stopped states are persisted
  • only interrupted runs can resume
  • broken pinned dependencies before resume return blocked
  • dead live tools/sources fail the run; they do not become implicit interrupts

First slice can ignore live source death because it does not include live upstream sources. Later slices must preserve the same no-implicit-pause rule.

Package Boundary Rules

Hard rules:

  • wf_api must not import transports, wf_server, or wf_mcp.
  • transport packages may import wf_api and wf_server.
  • wf_server may import wf_api, wf_artifacts, wf_platform, and selected reusable implementation services.
  • wf_server should not import wf_mcp.broker.WfMcpService.
  • wf_mcp may continue constructing WorkflowApi through its existing MCP-specific context adapter.
  • A future MCP transport mounted through wf_server must still keep upstream MCP source execution separate from transport request handling.
  • Do not add new generic server or transport code that depends on the combined wf_mcp facade. If it needs MCP-specific upstream behavior, isolate that as a source-provider adapter; if it needs to expose workflow operations to MCP clients, isolate that as a transport adapter.

If a reusable service currently lives under wf_mcp.broker.service but has no MCP dependency, later slices may move or duplicate a protocol-neutral version. Do not move large service sets in the first slice.

Later Slice Pointers

Slice 2: JSON-RPC HTTP Transport Adapter

Add the first transport package as JSON-RPC 2.0 over HTTP, likely wf_transport_rpc_http.

This is preferred over REST for the first remote CLI/server path because CLI and agent clients want stable operation names more than resource-shaped URLs. The method names should be dotted strings, matching the mental model already used by MCP/admin tool names without inheriting MCP's dynamic tool-list behavior.

Proposed initial method names:

workflow.capabilities.list
workflow.capabilities.inspect
workflow.drafts.create_from_capability
workflow.drafts.patch
workflow.drafts.validate
workflow.artifacts.save
workflow.deployments.save
workflow.deployments.validate
workflow.runs.start
workflow.runs.inspect
workflow.runs.trace
workflow.runs.resume

This slice should expose a small method set over the existing server composition:

  • health/status
  • list/inspect capabilities
  • run deployment
  • inspect run
  • read bounded trace
  • resume run

It should not implement source provider management yet.

Implementation status:

  • wf_transport_rpc_http.create_rpc_app(server) exposes a fixed JSON-RPC method set over an existing wf_server.WorkflowServer.
  • wf-rpc-server --store-root <path> and wf-rpc-server --config <path> start the local/static server over /rpc.
  • Remote wf CLI targeting is implemented through wf_config and target-aware context in wf_cli.
  • Auth, streaming/progress, and live upstream MCP source management remain future work.

Preferred implementation dependency:

uv add fastapi-jsonrpc uvicorn

fastapi-jsonrpc is the recommended server library for this slice because it keeps JSON-RPC 2.0 dispatch, errors, and docs near FastAPI/Pydantic instead of requiring a local hand-rolled dispatcher.

Client-side CLI code can use httpx directly for now. A typed client wrapper can be added when the method set stabilizes.

Guardrails:

  • Do not dynamically register saved workflows as JSON-RPC methods.
  • Do not add a JSON-RPC equivalent of MCP tools/list as the primary execution surface.
  • Do not require client session state to run or resume workflows.
  • Keep durable state addressed by explicit ids: artifact_id, deployment_id, run_id.
  • Keep trace reads bounded by explicit range.
  • Define request/response models explicitly with Pydantic; do not pass raw arbitrary dicts through the transport layer when a stable request shape is known.

Slice 3: CLI Remote Target

Allow wf_cli to target either:

  • local process stores/runtime, current behavior
  • remote long-lived API server

The CLI command surface should stay stable. Only context construction changes.

Slice 4: WebSocket Transport

If JSON-RPC over HTTP starts becoming awkward for streaming/progress, add a WebSocket transport sibling rather than changing WorkflowApi.

Possible shapes:

  • JSON-RPC over WebSocket
  • future MCP transport over the same server-owned WorkflowApi

Slice 5: Source Providers

Add explicit source-provider interfaces for long-lived server use.

Possible providers:

  • static local NodeSpecs
  • saved workflow capability sources
  • OpenAPI capability source catalogs
  • upstream MCP connection catalogs

This slice should avoid making "source" mean "MCP connection." MCP is one source provider, not the source model.

Future package direction:

wf_transport_mcp
  exposes WorkflowServer / wf_api operations to MCP clients

wf_sources_mcp
  consumes upstream MCP servers as workflow capability sources

wf_mcp
  compatibility package until old MCP entrypoints can shrink or retire

Current MCP-backed server status:

  • MCP-backed WorkflowServer construction is implemented.
  • wf-rpc-server --mcp-config <path> can serve JSON-RPC over that server.
  • Source registry read/mutation APIs are reachable remotely when the target exposes source_registry_admin.
  • Boundary caveat: the first --mcp-config hook intentionally proved the product path quickly, but it currently makes the transport CLI import wf_mcp.broker. That violates the original transport-package boundary and the existing import-direction guard. Treat this as a cleanup slice, not the desired final shape. The intended cleanup is to widen wf_config so MCP sources are configured through server.sources[], with wf_mcp.config.json handled as compatibility input.

Next implementation slices should be:

  1. Wider wf_config source model. Add an MCP source config variant under server.sources[] that can express the current broker connection shape: source id, provider/account/profile, locked / seed ownership, stdio/http transport, auth reference, enabled flag, and metadata. Keep legacy wf_mcp.config.json parsing as a compatibility adapter into the wider config, not as the future primary shape. McpSourceRegistryEntry already expresses most of this shape. The locked / seed policy currently lives on legacy ConnectionConfig.source_config_ownership; migrate that as a neutral source ownership/config policy field, not as a connection-specific name.
  2. Transport package boundary cleanup. Keep JSON-RPC method/app/client modules transport-only. After wf_config can describe MCP sources, wf-rpc-server --config ... should compose MCP-backed sources from neutral config and the --mcp-config path can become deprecated/legacy. Completed when tests/wf_transport_rpc_http/test_import_direction.py passes and the RPC transport CLI imports only wf_config, wf_server, and transport modules for server construction.
  3. Legacy MCP config migration. Provide an explicit converter from old wf_mcp.config.json into WorkflowConfigFile: store_root maps to server.store (StoreConfig is already a discriminated union; currently only kind: "filesystem" exists), and each legacy connection maps to a kind: "mcp" source. Preserve source_config_ownership as the neutral ownership field. Normalize old HTTP-like transport metadata (http, streamable-http, streamable_http, sse) into the neutral HTTP MCP source transport while preserving compatibility metadata where needed. sse remains legacy/deprecated, but conversion support is intentional because FastMCP can still expose it. Completed when wf config migrate-mcp <legacy> --output <workflow-config> writes a neutral config that can be used by wf-rpc-server --config.
  4. Manual product smoke with the real CLI/server commands. Record UX/runtime gaps before broadening architecture.
  5. Completed: source registry apply/reload semantics. Registry mutation updates desired persisted state, and explicit apply reconciles that state into the current server connection/source graph without mutating config files.
  6. Completed: persisted resume across server restart. The MCP-backed JSON-RPC regression rebuilds a neutral-config server from the same stores, inspects an interrupted run, and resumes it from the stored checkpoint and pinned dependency environment.

Slice 6: Auth and Tenancy

Define who can read/write artifacts, deployments, runs, and auth records.

Do not store upstream credentials as plain JSON in production mode. The local file store can remain a development backend.

Slice 7: Streaming and Progress

Expose long-running run progress through a protocol-native channel:

  • HTTP streaming/SSE/WebSocket for HTTP/RPC transports
  • MCP progress/tasks for MCP if practical

Do not bloat run_deployment responses with full traces or live logs.

Slice 8: Transactional Store Backend

Add SQLite/Postgres or another transactional backend for run records, checkpoints, artifacts, deployments, and possibly catalogs.

This slice should handle:

  • compare-and-swap resume
  • multi-process safety
  • atomic writes
  • retention policy

Slice 9: Live Upstream MCP Sources

Add upstream MCP source management to the long-lived server only after the local server path is proven.

This needs explicit policies for:

  • connection lifecycle
  • auth records
  • catalog refresh
  • source liveness
  • session failure
  • side-effectful tool calls

Open Questions

  1. Package name: wf_server is the recommended process-composition package, but the exact name can change before implementation.
  2. HTTP framework: likely FastAPI under fastapi-jsonrpc.
  3. RPC framework: use fastapi-jsonrpc for the first JSON-RPC-over-HTTP slice unless evaluation finds a blocking issue.
  4. Source-provider extraction: some reusable code currently lives in wf_mcp.broker.service; first slice should avoid moving it unless a small dependency-free helper is obviously needed.
  5. Storage backend: first slice can use file stores; production remote API needs a transactional backend later.

Non-Goals

  • Do not redesign WorkflowApi.
  • Do not move MCP upstream session management into wf_api.
  • Do not make transport routes call WorkflowSurfaceHandlers.
  • Do not require dynamic tools to represent saved workflows.
  • Do not implement retry/timeout policy.
  • Do not treat external source failure as a pause.
  • Do not implement fork/gather or new workflow primitives in this server slice.

Success Criteria

First implementation plan should be considered successful when:

  • a process-local server composition can construct WorkflowApi without WfMcpService
  • required stores are enforced
  • a connected client can run a deployment and inspect/read trace for the run
  • persisted run semantics match the local WorkflowRunApi tests
  • docs clearly state which source capabilities are first-slice only and which are future work