745 lines
26 KiB
Markdown
745 lines
26 KiB
Markdown
# Workflow Artifacts
|
|
|
|
This document captures the current design direction for saved workflows. It is a
|
|
design note, not an implementation status document.
|
|
|
|
For the current operator workflow around artifacts and deployments, see
|
|
[`wf_mcp_operator_manual.md`](wf_mcp_operator_manual.md).
|
|
|
|
## Ownership Boundary
|
|
|
|
Saved workflows are not inherently MCP concerns. `wf_mcp` is one capability
|
|
provider and one client/proxy surface; it should not become the owner of
|
|
workflow artifact storage, deployments, schedules, run history, and UI policy.
|
|
|
|
Preferred conceptual stack:
|
|
|
|
```text
|
|
wf_core
|
|
execution model, runtime, validation
|
|
|
|
wf_authoring
|
|
ergonomic builders, @node, reusable ops
|
|
|
|
wf_mcp
|
|
MCP adapters, proxying, catalogs, connection auth
|
|
|
|
wf_artifacts
|
|
immutable saved workflow definitions, versions, dependency contracts
|
|
|
|
wf_platform
|
|
deployments, bindings, schedules, run history, UI/admin policy
|
|
```
|
|
|
|
The project does not need all of these as separate packages immediately, but
|
|
new names and docs should preserve the split. `wf_mcp` may host early
|
|
experiments when it is the first consumer, but the domain model should point
|
|
toward extraction rather than making MCP the center of the workflow platform.
|
|
|
|
`wf_mcp` may expose workflow artifacts to an LLM client, but only as a
|
|
projection over an artifact/platform service. It should not own artifact state.
|
|
|
|
Prefer a small stable MCP control surface over dynamically adding one MCP tool
|
|
per saved workflow:
|
|
|
|
```text
|
|
wf.workflow.list_artifacts
|
|
wf.workflow.inspect_artifact
|
|
wf.workflow.validate_deployment
|
|
wf.workflow.run_deployment
|
|
```
|
|
|
|
This is important because MCP clients may not reliably refresh `tools/list` when
|
|
new workflow artifacts are saved. More specifically, LLM harnesses may refresh
|
|
or display a changed tool list without rebuilding the callable tool schema used
|
|
for the current model turn. A stable `run_deployment` tool lets an LLM test saved
|
|
workflows immediately without requiring dynamic tool registration or tool-list
|
|
notifications to work perfectly.
|
|
|
|
This control surface should not fork between backend service layers. The public
|
|
server now has one exposure style, but internally it still combines:
|
|
|
|
- service-backed local workflow/admin tools
|
|
- upstream proxy projection through MCP `tools/list` and `tools/call`
|
|
|
|
Workflow artifact operations should be defined once and projected through the
|
|
server surface instead of owning separate workflow registries or separate run
|
|
semantics.
|
|
|
|
Dynamic projection of saved workflows as individual MCP tools can exist later,
|
|
but it should be optional. The stable run tool is the reliable base layer.
|
|
|
|
Current `run_deployment` calls are synchronous request/response executions until
|
|
the workflow pauses or completes. They return a durable `run_id`, compact
|
|
execution status, terminal workflow outcome when available, output, diagnostics,
|
|
and `trace_count`; optional ranged trace detail is for debugging only. If a run
|
|
pauses at an interrupt, use `wf.workflow.resume_run` with that `run_id`.
|
|
|
|
Non-interrupting saved workflow children can now execute natively through this
|
|
deployment surface. A parent deployment resolves its saved descendants by exact
|
|
artifact version, and the parent binding environment supplies logical source
|
|
bindings for the whole child tree. This is intentionally one configured graph
|
|
environment; future per-child deployment overrides, if added, must be keyed by
|
|
the subgraph use site rather than only the child artifact id.
|
|
|
|
Interrupting saved artifacts can pause and resume through the current
|
|
deployment surface, including interrupts raised inside saved child workflows.
|
|
Stopped snapshots pin the deployment, root artifact, and saved child artifact
|
|
definitions so handler/server recreation does not invalidate the `run_id`.
|
|
|
|
Durable run history is specified in
|
|
[`2026-05-26-durable-workflow-runs-and-resume-design.md`](superpowers/specs/2026-05-26-durable-workflow-runs-and-resume-design.md).
|
|
The operator-facing run contract is summarized in
|
|
[`durable_run_operations.md`](durable_run_operations.md).
|
|
The implemented surface is:
|
|
|
|
- `run_deployment` starts or completes a run and returns `run_id`
|
|
- `inspect_run(run_id)` returns status, output, diagnostics, and trace metadata
|
|
- `read_run_trace(run_id, range)` returns bounded trace slices
|
|
|
|
Before applying a resume payload, the pinned dependency environment is
|
|
revalidated. If it is unavailable or incompatible, the run stays
|
|
`interrupted`, returns `resume_readiness="blocked"` and diagnostics, and does
|
|
not append a new execution checkpoint.
|
|
|
|
For long-running workflow execution, prefer MCP-native execution mechanisms
|
|
where available:
|
|
|
|
- `notifications/progress` for progress updates while a request is still active
|
|
- MCP Tasks for call-now/fetch-later execution when the client and server
|
|
negotiate task support
|
|
|
|
Do not invent a separate `start` convention as the primary MCP shape if MCP
|
|
Tasks are available. A compatibility tool such as `wf.workflow.start_run` can be
|
|
added later for clients that do not support Tasks, but that should be treated as
|
|
a fallback platform API rather than the preferred MCP contract.
|
|
|
|
## Terms
|
|
|
|
### Workflow Artifact
|
|
|
|
A workflow artifact is a saved, validated workflow package. It is more than a
|
|
raw workflow JSON document because it needs enough metadata for reuse,
|
|
inspection, dependency checks, and migration.
|
|
|
|
Expected shape:
|
|
|
|
```text
|
|
WorkflowArtifact
|
|
id
|
|
title
|
|
description
|
|
version
|
|
input_schema
|
|
output_schema
|
|
outcomes
|
|
plan
|
|
required_sources
|
|
created_from_catalog_version
|
|
```
|
|
|
|
Workflow artifact versions are immutable. Once a version is saved, its plan,
|
|
schemas, declared outcomes, and dependency declarations do not change in place.
|
|
Repairs and migrations should create a new artifact version or an external
|
|
binding decision rather than rewriting the old version.
|
|
|
|
Artifacts should prefer logical source aliases over concrete connection ids.
|
|
For example, a saved workflow can require `context7.query-docs` while a
|
|
deployment binding maps `context7` to `context7.default` or
|
|
`context7.team_account`. This gives saved workflows dependency injection for MCP
|
|
capability sources.
|
|
|
|
Logical source aliases are part of the immutable artifact. Concrete source
|
|
bindings are deployment/runtime configuration.
|
|
|
|
In this context, an account means an MCP connection profile with its own auth
|
|
and catalog behavior. It does not imply multiple users of the workflow app. A
|
|
single operator may connect several MCP accounts and bind deployments to
|
|
different accounts.
|
|
|
|
### Saved Workflow As Node
|
|
|
|
A saved workflow can be projected as a workflow node spec. At the parent
|
|
workflow boundary it behaves like a node:
|
|
|
|
- one input schema
|
|
- one output schema
|
|
- multiple declared outcomes
|
|
- interrupt points discovered from declared interrupt nodes
|
|
- nested trace preservation
|
|
|
|
Internally, it still runs as a graph with its own nodes, state, frames, traces,
|
|
foreach behavior, and interrupts.
|
|
|
|
The public boundary should match what an ordinary node provides. Parent
|
|
workflows should depend on the saved workflow's declared input schema, output
|
|
schema, outcomes, and required logical sources. Internal child graph structure
|
|
is not part of the public contract.
|
|
|
|
`interrupt_kinds` is a likely future boundary field, but it is not part of the
|
|
current schema yet. Until then, interrupts should still bubble with trace/path
|
|
metadata at runtime without becoming a declared schema field.
|
|
|
|
Current core interrupt semantics are node-level. An `InterruptNode` has:
|
|
|
|
- `kind`
|
|
- `request`, a canonical input-binding list mapping state/input/context paths
|
|
or literal values to public interrupt payload fields
|
|
- `resume`, a canonical output-binding list mapping resume payload fields back
|
|
into workflow state
|
|
- declared resume `outcomes`
|
|
|
|
That means an artifact can document interrupt boundaries by scanning its
|
|
declarative plan for interrupt nodes and deriving their request/resume payload
|
|
schemas from the bindings and workflow state/input schemas. Legacy
|
|
`request_map` and `out_map` inputs are parse-only compatibility shapes; saved
|
|
artifacts should write `request` and `resume`. They should not need to store
|
|
unrelated child graph internals just to describe the public interrupt points.
|
|
|
|
## Composition Rule
|
|
|
|
LLMs and users should compose saved workflows from the catalog as ordinary node
|
|
specs, for example:
|
|
|
|
```text
|
|
workflow.summarize_docs.v1
|
|
input_schema: ...
|
|
output_schema: ...
|
|
outcomes: done, needs_input, failed
|
|
```
|
|
|
|
The authoring surface should not require the LLM to write Python. It should use
|
|
declarative workflow artifacts plus validation feedback.
|
|
|
|
Saved workflows need two projections:
|
|
|
|
- NodeSpec-shaped catalog entries for composition
|
|
- full artifact records for inspection, debugging, repair, and migration
|
|
|
|
The NodeSpec-shaped projection is what a planner or LLM should use by default:
|
|
|
|
```text
|
|
workflow.summarize_docs.v1
|
|
input_schema
|
|
output_schema
|
|
outcomes
|
|
description
|
|
required_sources
|
|
diagnostics
|
|
```
|
|
|
|
The full artifact projection exposes the declarative plan, dependency contract
|
|
snapshots, deployments, bindings, and validation diagnostics. This is for
|
|
humans, migration tools, and advanced LLM repair flows.
|
|
|
|
Default composition should not require the client to inspect internal graph
|
|
details.
|
|
|
|
The declarative graph plan is the canonical saved form. Compiled or runtime
|
|
forms are disposable caches:
|
|
|
|
```text
|
|
WorkflowArtifact
|
|
plan: declarative workflow graph
|
|
compiled_cache: optional, disposable
|
|
```
|
|
|
|
If a compiled cache is missing or stale, rebuild it from the plan. Do not treat
|
|
compiled state as the source of truth.
|
|
|
|
Workflow steps should reference catalog capabilities by logical name and keep
|
|
contract snapshots separately. Do not inline full executable node specs into
|
|
every step.
|
|
|
|
```text
|
|
step:
|
|
node_ref: context7.query-docs
|
|
|
|
required_capabilities:
|
|
- ref: context7.query-docs
|
|
kind: tool
|
|
input_schema_hash: ...
|
|
input_schema_snapshot: ...
|
|
output_schema_hash: ...
|
|
output_schema_snapshot: ...
|
|
```
|
|
|
|
This is similar to import resolution. The artifact stores stable logical
|
|
imports. The deployment binds those imports to concrete sources. Dependency
|
|
validation checks that the concrete source still satisfies the recorded
|
|
contract.
|
|
|
|
## Dependency Rule
|
|
|
|
Workflow artifacts depend on capability sources. If a required source is removed,
|
|
disabled, or no longer exposes the needed capability, the workflow artifact must
|
|
remain visible but become unrunnable.
|
|
|
|
Unrunnable artifacts should return dependency diagnostics rather than disappear:
|
|
|
|
```text
|
|
workflow: workflow.summarize_docs.v1
|
|
status: unrunnable
|
|
missing_dependencies:
|
|
- source: context7.default
|
|
capability: context7.default.query_docs
|
|
reason: source disabled
|
|
```
|
|
|
|
Diagnostics should be structured and machine-readable:
|
|
|
|
```text
|
|
DependencyDiagnostic
|
|
severity: error | warning
|
|
code: source_missing | source_disabled | capability_missing | schema_changed | binding_missing
|
|
logical_ref
|
|
bound_source
|
|
message
|
|
repair_hint
|
|
```
|
|
|
|
Structured diagnostics let tests, dashboards, and LLM clients handle dependency
|
|
failures without parsing human-readable error strings.
|
|
|
|
This preserves repairability:
|
|
|
|
- users can inspect the saved workflow
|
|
- LLMs can explain what broke
|
|
- dashboards can offer rebind/restore actions
|
|
- future migration tools can map old sources to new ones
|
|
|
|
The artifact definition is immutable, but dependency status is live. A workflow
|
|
version can move between runnable and unrunnable as the surrounding capability
|
|
sources change.
|
|
|
|
Dependency checks should validate bindings, not just names. A concrete source
|
|
can satisfy a logical source alias only when it still exposes a compatible
|
|
capability contract.
|
|
|
|
Artifacts should store a dependency contract snapshot for each capability they
|
|
actually use:
|
|
|
|
```text
|
|
RequiredCapability
|
|
ref: context7.query-docs
|
|
kind: tool
|
|
input_schema
|
|
output_schema
|
|
observed_concrete_source: context7.default
|
|
observed_at
|
|
```
|
|
|
|
The snapshot is not a full catalog pin. It is the minimum contract needed to
|
|
detect whether the currently bound source still satisfies the saved workflow.
|
|
|
|
Workflow artifacts may depend on other workflow artifacts. A parent stores only
|
|
its direct dependencies; it should not copy a child workflow's dependency
|
|
snapshots into itself. Runtime/deployment validation walks the transitive
|
|
dependency graph and reports the chain that failed.
|
|
|
|
```text
|
|
answer_question
|
|
depends on search_docs
|
|
depends on context7.query-docs
|
|
```
|
|
|
|
If `context7.query-docs` breaks, `answer_question` is unrunnable because
|
|
`search_docs` is unrunnable. Diagnostics should preserve that chain.
|
|
|
|
Saved workflow dependencies should start with exact artifact-version pins:
|
|
|
|
```text
|
|
direct_dependency:
|
|
workflow: search_docs
|
|
version: 3
|
|
```
|
|
|
|
Exact pins make saved workflow behavior reproducible. Leave room for a future
|
|
`version_constraint` field if a semver parser is added, but do not start with
|
|
floating latest-compatible behavior.
|
|
|
|
Workflow artifact dependencies must be acyclic. Reject dependency cycles during
|
|
save or deployment validation, before runtime execution.
|
|
|
|
```text
|
|
dependency_cycle:
|
|
chain: workflow.a@1 -> workflow.b@2 -> workflow.a@1
|
|
```
|
|
|
|
Cycles should be rejected even if branch logic appears to make them unreachable.
|
|
The static dependency graph must stay acyclic so validation, tracing, and future
|
|
resume behavior remain understandable.
|
|
|
|
Dependency validation happens in two phases.
|
|
|
|
At save time:
|
|
|
|
```text
|
|
validate graph shape
|
|
resolve logical references
|
|
record required contracts
|
|
reject obviously invalid workflows
|
|
```
|
|
|
|
At deployment or run time:
|
|
|
|
```text
|
|
resolve concrete bindings
|
|
check source enabled or missing
|
|
compare current contracts to recorded contracts
|
|
apply drift_policy
|
|
return diagnostics or run
|
|
```
|
|
|
|
Save-time validation proves that the artifact was valid when created.
|
|
Deployment/run-time validation proves that the current environment can still
|
|
satisfy it.
|
|
|
|
Important dependency failure cases:
|
|
|
|
- source missing: the configured MCP connection no longer exists
|
|
- source disabled: the user or admin turned off the source
|
|
- capability missing: the source exists but no longer exposes the required tool,
|
|
prompt, resource, or node spec
|
|
- capability changed: the source still exposes the capability but its schema or
|
|
behavior no longer matches the artifact's recorded expectation
|
|
|
|
Missing and disabled sources are hard unrunnable failures. Capability changes
|
|
should start as diagnostics and become hard failures when the recorded schema is
|
|
no longer compatible with the saved workflow boundary.
|
|
|
|
Initial compatibility checks can be conservative:
|
|
|
|
- exact input/output schema hash match: compatible
|
|
- missing source: unrunnable
|
|
- missing capability: unrunnable
|
|
- new required input fields: unrunnable
|
|
- removed input fields that the workflow sends: unrunnable
|
|
- changed fields that downstream workflow paths read: unrunnable
|
|
- description/title/metadata-only changes: warning
|
|
- unknown or changed-but-not-proven-incompatible schema: follow deployment drift
|
|
policy
|
|
|
|
## Binding Rule
|
|
|
|
Use binding when the workflow intent stays the same but the concrete source
|
|
changes:
|
|
|
|
```text
|
|
artifact reference: context7.query-docs
|
|
runtime binding: context7 -> context7.default
|
|
```
|
|
|
|
Local system sources use the same binding mechanism. For example:
|
|
|
|
```text
|
|
artifact reference: wf.std.replace
|
|
runtime binding: wf.std -> wf.std
|
|
```
|
|
|
|
These self-bindings are not external account choices. They keep dependency
|
|
resolution uniform across local system sources and upstream connection sources.
|
|
They may become implicit later, but today deployments should include them when
|
|
validation reports `binding_missing` for `wf.std`.
|
|
|
|
or:
|
|
|
|
```text
|
|
artifact reference: context7.query-docs
|
|
runtime binding: context7 -> context7.team_account
|
|
```
|
|
|
|
Use migration when the graph, mappings, schemas, outcomes, or intended provider
|
|
semantics change. Migration creates a new immutable workflow artifact version.
|
|
|
|
The practical split:
|
|
|
|
- rebind: same capability contract, different concrete source
|
|
- migrate: changed workflow behavior, incompatible schema, or different
|
|
provider semantics
|
|
|
|
## Artifact References
|
|
|
|
Reusable saved artifacts should prefer logical capability references, not
|
|
account-specific concrete references.
|
|
|
|
For example, if discovery shows a concrete node spec:
|
|
|
|
```text
|
|
demo.personal.echo_tool
|
|
```
|
|
|
|
and the author wants the artifact to depend on the logical source `demo`, the
|
|
saved plan should use:
|
|
|
|
```text
|
|
demo.echo_tool
|
|
```
|
|
|
|
and the artifact should declare a matching required capability:
|
|
|
|
```text
|
|
RequiredCapability(
|
|
ref="demo.echo_tool",
|
|
kind="node_spec"
|
|
)
|
|
```
|
|
|
|
The deployment then chooses the concrete account or connection profile:
|
|
|
|
```text
|
|
bindings:
|
|
- logical_source: demo
|
|
concrete_source: demo.personal
|
|
```
|
|
|
|
This keeps reusable artifacts portable across accounts while still letting
|
|
runtime validation prove that the concrete deployment can actually supply the
|
|
required capability.
|
|
|
|
Concrete references such as `demo.personal.echo_tool` remain useful for raw
|
|
local plans, tests, direct calls, and backward compatibility. Artifact creation
|
|
tools should avoid saving those concrete names by default when the user is
|
|
creating a reusable workflow or wrapper.
|
|
|
|
Planned authoring helper:
|
|
|
|
```text
|
|
concrete discovered ref + logical source alias -> artifact ref
|
|
demo.personal.echo_tool + demo -> demo.echo_tool
|
|
```
|
|
|
|
That helper should also populate `required_capabilities` so LLM clients do not
|
|
have to reverse-engineer dependency metadata from formatted names.
|
|
|
|
When artifact creation can observe the concrete `NodeSpecInventory` used during
|
|
authoring, it should persist that node spec's input/output schema snapshots and
|
|
stable hashes into the generated `RequiredCapability`. Later deployment
|
|
validation compares the saved contract against the currently bound concrete
|
|
source and can report `schema_changed` when a source still has the same logical
|
|
capability name but no longer the same contract.
|
|
|
|
Bindings should live outside the immutable artifact, preferably on a deployment
|
|
or run configuration:
|
|
|
|
```text
|
|
WorkflowDeployment
|
|
artifact_id: summarize_docs
|
|
artifact_version: 1
|
|
deployment_id: summarize_docs.context7_default
|
|
bindings:
|
|
- logical_source: context7
|
|
concrete_source: context7.default
|
|
drift_policy: block
|
|
```
|
|
|
|
The default binding scope is deployment-level. A per-run override can be added
|
|
later for interactive or one-off runs. Bindings should not be modeled as
|
|
per-user until the application actually has user accounts and tenancy.
|
|
|
|
A deployment id identifies one configured way to run an artifact version. Two
|
|
deployments can point at the same immutable workflow artifact while binding
|
|
logical sources to different MCP accounts or connection profiles:
|
|
|
|
```text
|
|
deployment: summarize_docs.personal
|
|
artifact: summarize_docs@1
|
|
bindings:
|
|
- logical_source: context7
|
|
concrete_source: context7.personal
|
|
|
|
deployment: summarize_docs.work
|
|
artifact: summarize_docs@1
|
|
bindings:
|
|
- logical_source: context7
|
|
concrete_source: context7.work
|
|
```
|
|
|
|
This gives the stable MCP run tool a concrete target without requiring one MCP
|
|
tool per workflow or account.
|
|
|
|
Recommended drift-policy defaults:
|
|
|
|
- scheduled/offline deployments: `block`
|
|
- manual interactive runs: `warn`
|
|
- development runs: `warn` or `allow`
|
|
|
|
This prevents silent scheduled failures while still allowing local repair and
|
|
experimentation.
|
|
|
|
## Storage Direction
|
|
|
|
Workflow artifacts should use a store protocol rather than baking filesystem
|
|
paths into the models. The existing `wf_mcp.storage.Store` persists auth and
|
|
catalog snapshots; workflow artifacts have a different lifecycle, so start with
|
|
a sibling artifact-store protocol instead of immediately expanding the MCP store
|
|
interface.
|
|
|
|
Expected shape:
|
|
|
|
```text
|
|
WorkflowArtifactStore
|
|
save_artifact(artifact)
|
|
get_artifact(id, version)
|
|
list_artifacts()
|
|
resolve_latest(id)
|
|
```
|
|
|
|
The initial implementation can be file-backed and share the same root directory
|
|
family as the existing MCP store:
|
|
|
|
```text
|
|
.wf_mcp_store/
|
|
auth/
|
|
catalog/
|
|
workflows/
|
|
summarize_docs/
|
|
1.json
|
|
2.json
|
|
```
|
|
|
|
Keeping the store behind a protocol makes it straightforward to move artifacts
|
|
to SQLite, Postgres, or another backend later.
|
|
|
|
## Runtime Direction
|
|
|
|
Saved workflow execution eventually needs first-class runtime support for:
|
|
|
|
- nested run state
|
|
- child-frame trace preservation
|
|
- interrupt bubbling with path metadata
|
|
- resume into child run state
|
|
- child final outcome mapping to parent node outcome
|
|
- dependency checks before execution
|
|
- reducer capabilities referenced by declared workflow state fields are saved as
|
|
direct artifact dependencies just like node specs or tools; reducer config is
|
|
part of the state field contract, while the dependency key is the reducer name
|
|
|
|
Runtime execution must resolve reducer dependencies, not just validate that they
|
|
exist. A deployment binding maps the artifact's logical reducer source, such as
|
|
`custom`, to a concrete source, such as `custom.default`. The runtime registers
|
|
the concrete `ReducerDefinition` under the logical reducer name used in the
|
|
saved workflow state schema, such as `custom.multiply`. This keeps artifact
|
|
plans stable while allowing deployments to choose concrete accounts or local
|
|
reducer packages.
|
|
|
|
Node specs may also use deployment-bound logical names. A saved plan can refer
|
|
to `demo.echo_tool`, while the deployment binds `demo` to a concrete source such
|
|
as `demo.personal`. At runtime the compiler builds node definitions from the
|
|
concrete source but leaves the saved artifact immutable. Concrete node names
|
|
such as `demo.personal.echo_tool` remain supported for raw local plans and older
|
|
artifacts.
|
|
|
|
Implementation note: source and capability names now have segment-backed
|
|
platform refs: `SourceRef(parts=...)` and `CapabilityRef(source=..., name=...)`.
|
|
Dot-joined names remain the wire/presentation format, but new runtime code
|
|
should parse or format through those refs instead of rediscovering source/name
|
|
boundaries with ad hoc string splits.
|
|
|
|
Persisted artifact/deployment models use explicit list-of-struct shapes for
|
|
source and capability contracts. Dict-key shapes such as
|
|
`bindings: {"demo": "demo.personal"}` and
|
|
`required_capabilities: {"demo.echo_tool": {...}}` are accepted at parse
|
|
boundaries for compatibility, but model dumps emit the explicit list shapes.
|
|
Runtime code that needs lookup tables should use helper indexes such as
|
|
`WorkflowDeployment.binding_map()` and
|
|
`WorkflowArtifact.required_capability_map()`.
|
|
|
|
Saved workflow artifact names use a separate grammar and ref type:
|
|
`WorkflowCapabilityRef(artifact_id, version)` serializes as
|
|
`workflow.<artifact_id>.v<version>`. Artifact ids may contain dots, so this
|
|
must not be parsed as a generic `CapabilityRef`.
|
|
|
|
When an artifact is used as a child workflow dependency, convert it to the core
|
|
`WorkflowRef` shape instead of reusing display strings:
|
|
|
|
```python
|
|
workflow_ref_from_artifact(artifact)
|
|
workflow_ref_from_capability(WorkflowCapabilityRef("echo_wrapper", 1))
|
|
workflow_capability_ref_from_workflow_ref(ref) # only for artifact-backed refs
|
|
```
|
|
|
|
This keeps the three identities separate: `WorkflowArtifact` is the saved
|
|
document, `WorkflowCapabilityRef` is the public callable capability name, and
|
|
`WorkflowRef` is the core subgraph dependency pointer.
|
|
|
|
The first implementation should prefer artifact validation and dependency
|
|
diagnostics before attempting persistent nested resume.
|
|
|
|
Native subgraphs now have a core `SubgraphNode` boundary. It validates the
|
|
parent-side contract: child workflow reference, declared child input/output
|
|
schemas, binding lists, and declared outcomes. When callers resolve a local
|
|
child into `PreparedSubgraph`, core executes it in child scope, preserves its
|
|
trace, and can bubble and resume child interrupts without exposing child state
|
|
as parent state. Saved child artifacts are resolved by the workflow surface into
|
|
the same `PreparedSubgraph` shape before execution. The current
|
|
`wf_authoring.subgraph_node` and
|
|
`async_subgraph_node` helpers still execute a child workflow as a plain node
|
|
and validate the child output. The async helper is explicit because hiding
|
|
`asyncio.run()` inside the sync wrapper would break inside already-running
|
|
event loops.
|
|
|
|
See `examples/authoring_workflow_as_node.py` for the compatibility wrapper-node
|
|
approach and `examples/authoring_native_subgraph.py` for native prepared-child
|
|
execution. `examples/authoring_native_subgraph_interrupt.py` demonstrates a
|
|
native child pause and builder-driven resume. In the wrapper example the
|
|
parent trace sees one node call; in the native examples child trace entries
|
|
remain in the parent run state.
|
|
|
|
Durable resume persists stopped snapshots for interrupted, completed, and
|
|
failed runs and pins root workflow, prepared child dependencies, deployment
|
|
bindings, and trace metadata. Only declared interrupts become resumable
|
|
pauses; live tool/source failures remain failures.
|
|
|
|
Blocking dependency failures happen before workflow execution and are not normal
|
|
workflow outcomes. A missing source, disabled source, unresolved binding, or
|
|
incompatible capability contract means the deployment is unrunnable.
|
|
|
|
```text
|
|
validate deployment dependencies
|
|
if blocking diagnostics exist:
|
|
return unrunnable dependency diagnostics
|
|
else:
|
|
run workflow
|
|
```
|
|
|
|
Dependency diagnostics may be shown to parent workflows, dashboards, and LLM
|
|
clients, but they should not be routed through ordinary business outcomes such
|
|
as `failed`.
|
|
|
|
MCP tool result errors are different from dependency failures. A generated MCP
|
|
tool wrapper can expose generic outcomes such as `ok` and `error`; workflow
|
|
authors must wire both outcomes explicitly. A `runtime_error` node is a valid
|
|
way to terminate generic tool errors.
|
|
|
|
Richer business outcomes should be modeled with wrapper graph-nodes rather than
|
|
by making every generated MCP node guess domain semantics. For example, a
|
|
workflow can wrap a raw MCP call and map result content to outcomes such as
|
|
`found`, `not_found`, `unauthorized`, or `rate_limited` when those meanings are
|
|
known for that tool.
|
|
|
|
## Raw Plan Import
|
|
|
|
`wf artifact create-from-plan` imports a complete raw workflow plan. This is an
|
|
advanced/compiler path, not the interactive draft authoring path.
|
|
|
|
Raw plans use execution-model fields such as:
|
|
|
|
- `nodes`
|
|
- `edges`
|
|
- node field `node`
|
|
|
|
Draft workspaces use authoring fields such as:
|
|
|
|
- `steps`
|
|
- `routes`
|
|
- step field `use`
|
|
|
|
Do not pass draft JSON to `artifact create-from-plan`; save drafts with
|
|
`wf draft save`.
|
|
|
|
## Open Questions
|
|
|
|
- How should child workflow interrupts compose with parent workflow execution
|
|
once saved workflows can run as real subgraphs instead of plain node wrappers?
|