7.9 KiB
Source Architecture
This map explains how workflow capability sources fit together. Use it when
deciding where to add a new source family or when untangling wf_sources_mcp
from the older wf_mcp compatibility package.
Short Version
wf_authoring
creates NodeSpec, reducers, and authored workflows
wf_platform
defines neutral source DTOs:
CapabilitySource
CapabilityBuckets
SourceVisibility
SourcePermissions
wf_api
consumes source DTOs through WorkflowSpecProvider
owns built-in local sources:
wf.std
wf.recipes
wf_sources_mcp
implements MCP as an upstream source provider
wf_sources_python
implements trusted in-process Python source loading
wf_server
composes configured providers into WorkflowServer
wf_transport_*
exposes WorkflowServer over protocols
The workflow API should not care whether a capability came from MCP, Python,
OpenAPI, or a built-in source. It should see CapabilitySource and executable
NodeSpec objects.
Package Responsibilities
| Package | Owns | Does not own |
|---|---|---|
wf_authoring |
@node, NodeSpec, builder DSL, reducers, reusable authored ops. |
Server config, source loading, MCP sessions. |
wf_platform |
Neutral source DTOs and source visibility/permission metadata. | Provider-specific loading or execution. |
wf_api |
Application operations over capabilities, drafts, artifacts, deployments, and runs. Built-in wf.std / wf.recipes sources. |
MCP/Python/OpenAPI source-specific behavior. |
wf_sources_mcp |
MCP source ids, connection models, auth/catalog stores, discovery, SDK facade, persistent runtime pool, converters, wrappers. | MCP frontend/proxy compatibility, durable server composition. |
wf_sources_python |
Trusted Python module registry loading and projection to CapabilitySource. |
Authoring primitives, registry mutation/apply, sandboxing. |
wf_server |
WorkflowServer composition from config/store/source providers. |
JSON-RPC method definitions, MCP protocol frontend. |
wf_transport_rpc_http |
JSON-RPC HTTP app/client around an existing WorkflowServer. |
Server startup policy, source-provider composition. |
wf_mcp |
Legacy/special-purpose MCP frontend, broker glue, proxy, compatibility shims. | New durable product behavior unless explicitly retiring old callers. |
Data Flow
wf_config.server.sources[]
-> wf_server.config selects source providers
-> provider loads live source inventory
-> CapabilitySource map
-> WorkflowSpecProvider
-> WorkflowApi / WorkflowServer
-> transport or CLI
The first shared provider seam is intentionally static:
class WorkflowSourceProvider(Protocol):
def load_sources(self) -> Mapping[str, CapabilitySource]: ...
This covers source families that can project configured inventory into
workflow-facing CapabilitySource objects. Provider-specific runtime pools,
admin/apply hooks, auth, catalog caches, and live health checks stay outside
this narrow seam until a source family needs them.
Capability, Tool, Resource, And Prompt
Use these terms precisely:
- A source is the owner and namespace. Examples:
everything.default,wf.std,wf.source, or a future OpenAPI/Python source. - A tool is provider-native. For MCP, it is an MCP tool discovered from an upstream server.
- A workflow capability is workflow-native. It is the
NodeSpecshape a graph can call with typed input, typed output, outcomes, validation, and trace behavior. Tools can be projected into workflow capabilities, but the two are not identical concepts. - A resource is source-owned addressable content. The URI is not globally meaningful by itself; it must be interpreted with the source that owns it.
- A prompt is source-owned prompt/template inventory. Listing prompts is inventory; rendering a prompt is an upstream operation and may be stateful.
Saved workflow data should prefer logical source references over concrete source ids. For example, a resource ref should store:
{"logical_source": "drive", "uri": "gdrive://file/abc"}
The deployment binding decides whether drive means drive.personal,
drive.work, or another concrete source. Platform sources such as wf.std and
wf.source are special because their logical source id is also their concrete
source id, so they do not require deployment bindings. Legacy explicit
self-bindings such as wf.std=wf.std are accepted as no-op compatibility, but
new deployments should omit them. Validation rejects non-self platform bindings
such as wf.std=custom.std as stale or misleading configuration.
Runtime dereference is explicit. Passing a resource ref by value does not fetch
content. A helper capability such as wf.source.read_resource receives the ref,
uses runtime/platform context to resolve the logical source, and applies bounded
output policy before returning text into workflow state/output.
Prompt rendering uses the same source ownership rules, but it is not yet exposed
as a workflow helper source. Keep prompt support at inventory/inspection level
until there is a concrete graph use case, argument schema contract, and bounded
output policy equivalent to wf.source.read_resource.
For MCP, the provider also owns stateful upstream sessions:
McpSourceConnection
-> McpRuntimePool
-> McpSourceClient
-> MCP ClientSession
For Python, the provider is simpler:
PythonSourceConfig(path, module, registry)
-> PythonSourceProvider
-> import module
-> load NodeSpec registry
-> qualify specs under source id
-> CapabilitySource(kind="python")
For a concrete operator flow, see the
Python source runbook. It covers writing ops.py,
configuring path/module/registry, validating the config, calling a
capability, and running a saved workflow deployment.
Built-Ins Versus Configured Sources
wf.std and wf.recipes are built-in local sources owned by wf_api.local_sources.
They are always platform-versioned content.
Configured sources are explicit server/operator choices:
kind: "mcp": upstream MCP server capabilities.kind: "python": trusted in-process project capabilities.- future
kind: "openapi": HTTP/OpenAPI operations.
Do not move wf.std or wf.recipes into wf_sources_python. They are not
operator-configured project sources.
Generated draft workflows may still use built-in helper sources such as
wf.std. Platform sources do not need deployment bindings, so deployment
examples should bind configured sources only. If validation reports a platform
source binding, remove it unless it is an old self-binding kept only for
compatibility.
wf_sources_mcp Internal Layers
wf_sources_mcp is clearer if read from bottom to top:
ids / transports / connections
source identity and MCP connection description
auth / storage
source auth records and catalog cache files
client
one live MCP ClientSession facade
runtime
persistent session pool for stateful upstream operations
sdk
one-shot adapter, operation protocols, and MCP-to-workflow converters
catalog / discovery / tool_wrappers
turn MCP tools/resources/prompts into workflow-facing source inventories
wf_mcp may still re-export or adapt some of this while old callers exist. New
durable server/source work should prefer wf_sources_mcp directly.
Adding A New Source Family
Start with a provider package:
src/wf_sources_<kind>/
__init__.py
loader.py or provider.py
tests/
Then add:
- A
wf_configdiscriminated-union source model. - A provider loader that returns
CapabilitySource. wf_server.configcomposition for that source kind.- Tests proving
cap list,cap call, and one workflow run path. - Docs stating which parts are static, mutable, reloadable, or deferred.
Do not add source-family branches inside wf_api run execution. Source-specific
logic belongs in the provider package or server composition layer.