node output vs workflow output clarification
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
# Draft Output Binding Docs 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:** Clarify the two different `output` binding shapes in workflow drafts so MCP/LLM clients stop applying step-level `source`/`target` bindings to top-level workflow output projection.
|
||||
|
||||
**Architecture:** This is docs-only for now. The core model is internally coherent: step-level `output` uses `OutputBinding` (`source` local -> `target` state), while top-level workflow `output` uses input-binding shape (`path` graph -> `target` local output payload). Update the main draft docs, runbook, and schema-facing descriptions to teach this explicitly without changing runtime behavior.
|
||||
|
||||
**Tech Stack:** Markdown docs, Pydantic field descriptions, pytest schema/docs tests, ruff, basedpyright.
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
Do:
|
||||
|
||||
- Add a clear “Two Outputs, Different Shapes” docs section.
|
||||
- Show exact JSON for both step-level output and top-level workflow output.
|
||||
- Explain the legacy fallback: empty top-level `output` projects same-name top-level state fields.
|
||||
- Update MCP model field descriptions so schema viewers see “uses `path`, not `source`” for top-level output.
|
||||
- Add docs/test assertions that this guidance is exported.
|
||||
|
||||
Do not:
|
||||
|
||||
- Rename fields to `writes` / `returns`.
|
||||
- Change validation behavior.
|
||||
- Remove legacy same-name output fallback.
|
||||
- Add automatic MCP content block extraction.
|
||||
|
||||
## Files
|
||||
|
||||
- Modify: `docs/workflow_drafts.md`
|
||||
- Add the primary explanation and examples.
|
||||
- Modify: `docs/wf_mcp_end_to_end_runbook.md`
|
||||
- Add a short warning in the draft patching section.
|
||||
- Modify: `docs/workflow_capabilities.md`
|
||||
- Mention that `next_actions.patch_examples` may include top-level output projection examples.
|
||||
- Modify: `src/wf_artifacts/drafts/models.py`
|
||||
- Improve `WorkflowDraft.output` field description.
|
||||
- Modify: `src/wf_core/models/workflow.py`
|
||||
- Improve `Workflow.output` field description.
|
||||
- Modify: `tests/wf_mcp/server/test_docs.py`
|
||||
- Assert exported docs include the new guidance.
|
||||
- Modify if needed: `tests/wf_mcp/server/test_config.py`
|
||||
- Assert schema descriptions include “path, not source” if exposed in tool schema.
|
||||
|
||||
## Task 1: Document The Two Output Shapes
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/workflow_drafts.md`
|
||||
|
||||
- [ ] **Step 1: Add docs section**
|
||||
|
||||
After the “Important details” list or before “Explicit Outputs And Error Outcomes”, add:
|
||||
|
||||
```markdown
|
||||
## Two Outputs, Different Shapes
|
||||
|
||||
Drafts have two fields named `output`, but they do different jobs.
|
||||
|
||||
### Step-Level `steps.<id>.output`
|
||||
|
||||
Step output writes a node's local return payload into workflow state. It uses
|
||||
`source` / `target`:
|
||||
|
||||
```json
|
||||
{
|
||||
"source": { "root": "local", "parts": ["text"] },
|
||||
"target": { "root": "state", "parts": ["result_text"] }
|
||||
}
|
||||
```
|
||||
|
||||
Read this as:
|
||||
|
||||
```text
|
||||
node output.text -> state.result_text
|
||||
```
|
||||
|
||||
### Top-Level `output`
|
||||
|
||||
Top-level workflow output projects graph values into the final public workflow
|
||||
output payload. It uses input-binding shape: `path` / `target`, not
|
||||
`source` / `target`.
|
||||
|
||||
```json
|
||||
{
|
||||
"path": { "root": "state", "parts": ["result_text"] },
|
||||
"target": { "root": "local", "parts": ["result_text"] }
|
||||
}
|
||||
```
|
||||
|
||||
Read this as:
|
||||
|
||||
```text
|
||||
state.result_text -> workflow output.result_text
|
||||
```
|
||||
|
||||
If top-level `output` is empty, the runtime keeps the legacy same-name fallback:
|
||||
for every field in `output_schema`, it copies the top-level state field with the
|
||||
same name when present. That fallback is convenient, but explicit output
|
||||
projection is clearer for new workflows.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run grep check**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
rg -n "Two Outputs, Different Shapes|path.*not.*source|state.result_text -> workflow output.result_text" docs/workflow_drafts.md
|
||||
```
|
||||
|
||||
Expected: all terms appear.
|
||||
|
||||
## Task 2: Update Runbook Warning And Example
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `docs/wf_mcp_end_to_end_runbook.md`
|
||||
|
||||
- [ ] **Step 1: Add warning near draft patching section**
|
||||
|
||||
Near “Patch Or Validate The Workspace”, add:
|
||||
|
||||
```markdown
|
||||
When patching output bindings, keep the two levels separate:
|
||||
|
||||
- Step-level `steps.<id>.output` uses `source` local -> `target` state.
|
||||
- Top-level `output` uses `path` graph -> `target` local output payload.
|
||||
|
||||
For explicit final output projection from state, use:
|
||||
|
||||
```json
|
||||
{
|
||||
"path": { "root": "state", "parts": ["result_text"] },
|
||||
"target": { "root": "local", "parts": ["result_text"] }
|
||||
}
|
||||
```
|
||||
|
||||
Do not use `source` at top level. `source` belongs to step output bindings.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run grep check**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
rg -n "Do not use `source` at top level|steps.<id>.output|result_text" docs/wf_mcp_end_to_end_runbook.md
|
||||
```
|
||||
|
||||
Expected: all terms appear.
|
||||
|
||||
## Task 3: Update Field Descriptions
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `src/wf_artifacts/drafts/models.py`
|
||||
- Modify: `src/wf_core/models/workflow.py`
|
||||
|
||||
- [ ] **Step 1: Update `WorkflowDraft.output` field description**
|
||||
|
||||
Change:
|
||||
|
||||
```python
|
||||
output: list[InputBinding] = Field(default_factory=list)
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```python
|
||||
output: list[InputBinding] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
"Top-level workflow output projection. Uses input-binding shape: "
|
||||
"`path` reads from input/state/context and `target` writes to the "
|
||||
"local public output payload. Do not use step output `source` here."
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update `Workflow.output` field description**
|
||||
|
||||
In `src/wf_core/models/workflow.py`, extend the `output` description to include:
|
||||
|
||||
```python
|
||||
"Use `path`, not `source`; `source` belongs to step-level node output bindings."
|
||||
```
|
||||
|
||||
Keep the existing legacy fallback explanation.
|
||||
|
||||
- [ ] **Step 3: Run focused schema/type checks**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
uv run basedpyright --level error src/wf_artifacts/drafts/models.py src/wf_core/models/workflow.py
|
||||
uv run ruff check src/wf_artifacts/drafts/models.py src/wf_core/models/workflow.py
|
||||
uv run ruff format --check src/wf_artifacts/drafts/models.py src/wf_core/models/workflow.py
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
## Task 4: Add Exported Docs Test
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `tests/wf_mcp/server/test_docs.py`
|
||||
|
||||
- [ ] **Step 1: Add docs resource assertion**
|
||||
|
||||
In the docs resource test that reads workflow authoring/draft docs, assert:
|
||||
|
||||
```python
|
||||
assert "Two Outputs, Different Shapes" in text
|
||||
assert "Do not use step output `source` here" in text or "Do not use `source` at top level" in text
|
||||
```
|
||||
|
||||
Use the existing variable names in the file. Do not assert whole payload dict equality.
|
||||
|
||||
- [ ] **Step 2: Run focused docs test**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
uv run pytest tests/wf_mcp/server/test_docs.py -q
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
## Task 5: Optional Schema Description Test
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify if needed: `tests/wf_mcp/server/test_config.py`
|
||||
|
||||
- [ ] **Step 1: Inspect whether draft output field description is exposed**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
uv run pytest tests/wf_mcp/server/test_config.py -q
|
||||
```
|
||||
|
||||
If this test already inspects `create_draft_workspace` request schemas, add:
|
||||
|
||||
```python
|
||||
output_description = minimal_request["properties"]["output"]["description"]
|
||||
assert "path" in output_description
|
||||
assert "source" in output_description
|
||||
```
|
||||
|
||||
If the schema nests the description differently, skip this test change and rely
|
||||
on `test_docs.py`.
|
||||
|
||||
## Task 6: Final Verification
|
||||
|
||||
**Files:**
|
||||
|
||||
- All touched docs and Python files.
|
||||
|
||||
- [ ] **Step 1: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
uv run pytest tests/wf_mcp/server/test_docs.py tests/wf_mcp/server/test_config.py -q
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
- [ ] **Step 2: Run touched-file lint/type checks**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
uv run ruff check src/wf_artifacts/drafts/models.py src/wf_core/models/workflow.py tests/wf_mcp/server/test_docs.py tests/wf_mcp/server/test_config.py
|
||||
uv run ruff format --check src/wf_artifacts/drafts/models.py src/wf_core/models/workflow.py tests/wf_mcp/server/test_docs.py tests/wf_mcp/server/test_config.py
|
||||
uv run basedpyright --level error src/wf_artifacts/drafts/models.py src/wf_core/models/workflow.py tests/wf_mcp/server/test_docs.py tests/wf_mcp/server/test_config.py
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
- [ ] **Step 3: Optional full suite**
|
||||
|
||||
Run when time allows:
|
||||
|
||||
```powershell
|
||||
uv run pytest -q
|
||||
```
|
||||
|
||||
Expected current baseline: full suite passes with the existing skip/xfail count.
|
||||
|
||||
## Notes For Opencode
|
||||
|
||||
- This is docs/description work only.
|
||||
- Do not rename model fields.
|
||||
- Do not remove fallback same-name projection.
|
||||
- Do not auto-extract MCP content blocks.
|
||||
- The exact mental model to teach is:
|
||||
|
||||
```text
|
||||
steps.call.output: local node output -> workflow state
|
||||
workflow output: input/state/context graph path -> public output payload
|
||||
```
|
||||
@@ -0,0 +1,238 @@
|
||||
# Workflow Surface Next Actions Design
|
||||
|
||||
## Purpose
|
||||
|
||||
`next_actions` is becoming a reusable UX pattern for MCP-facing workflow tools.
|
||||
It gives LLM clients a small, machine-readable answer to:
|
||||
|
||||
```text
|
||||
What should I call next?
|
||||
```
|
||||
|
||||
This is especially useful when a client cannot easily read resources/prompts or
|
||||
when the MCP tool schema is technically correct but easy to misuse.
|
||||
|
||||
## Boundary
|
||||
|
||||
`next_actions` is guidance, not authority.
|
||||
|
||||
- Diagnostics describe machine-readable facts about validity, drift, source
|
||||
liveness, blocked resume, and runtime failures.
|
||||
- `next_actions` explains the likely next useful tool call.
|
||||
- Runtime validation, artifact validation, and dependency validation remain the
|
||||
source of truth.
|
||||
- `next_actions.can_save_now` is advisory only. It must not block saving.
|
||||
|
||||
## Proposed Module
|
||||
|
||||
Create a focused module:
|
||||
|
||||
```text
|
||||
src/wf_mcp/workflow_surface/next_actions.py
|
||||
```
|
||||
|
||||
It should own the reusable result models and constructors. This keeps
|
||||
`handlers.py` from accumulating UX policy helpers and keeps `models.py` focused
|
||||
on MCP request/response schemas.
|
||||
|
||||
## Core Types
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import StrEnum
|
||||
from typing import Any, Self
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class NextActionTool(StrEnum):
|
||||
"""Stable MCP workflow tools that guidance may recommend."""
|
||||
|
||||
PATCH_DRAFT_WORKSPACE = "wf.workflow.patch_draft_workspace"
|
||||
VALIDATE_DRAFT_WORKSPACE = "wf.workflow.validate_draft_workspace"
|
||||
VALIDATE_DEPLOYMENT = "wf.workflow.validate_deployment"
|
||||
RUN_DEPLOYMENT = "wf.workflow.run_deployment"
|
||||
RESUME_RUN = "wf.workflow.resume_run"
|
||||
READ_RUN_TRACE = "wf.workflow.read_run_trace"
|
||||
|
||||
|
||||
class NextActionPatchExample(BaseModel):
|
||||
"""Concrete example request for the recommended tool."""
|
||||
|
||||
description: str
|
||||
tool: NextActionTool
|
||||
request: dict[str, Any]
|
||||
|
||||
|
||||
class NextActions(BaseModel):
|
||||
"""Advisory continuation hints for MCP workflow clients."""
|
||||
|
||||
can_continue: bool = Field(
|
||||
description=(
|
||||
"Whether there is an obvious next workflow-surface tool call. "
|
||||
"Advisory only."
|
||||
)
|
||||
)
|
||||
can_save_now: bool | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Advisory wrapper-authoring signal. False means review is "
|
||||
"recommended before saving; the server does not enforce it."
|
||||
),
|
||||
)
|
||||
recommended_next_tool: NextActionTool | None = Field(
|
||||
default=None,
|
||||
description="Suggested next MCP workflow tool, if one is obvious.",
|
||||
)
|
||||
reason: str
|
||||
patch_examples: list[NextActionPatchExample] = Field(default_factory=list)
|
||||
warnings: list[str] = Field(default_factory=list)
|
||||
```
|
||||
|
||||
## Constructors
|
||||
|
||||
Prefer named constructors or classmethods over ad-hoc dict helpers.
|
||||
|
||||
Initial constructor:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def from_wrapper_hints(
|
||||
cls,
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
hints: WrapperAuthoringHints | dict[str, Any],
|
||||
) -> Self:
|
||||
"""Create guidance after create_draft_workspace_from_capability."""
|
||||
```
|
||||
|
||||
Later constructors:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def from_deployment_validation(
|
||||
cls,
|
||||
*,
|
||||
deployment_id: str,
|
||||
diagnostics: list[DependencyDiagnostic],
|
||||
) -> Self:
|
||||
"""Create guidance after validate_deployment."""
|
||||
|
||||
|
||||
@classmethod
|
||||
def from_run_result(
|
||||
cls,
|
||||
*,
|
||||
run_id: str | None,
|
||||
status: str,
|
||||
trace_count: int,
|
||||
diagnostics: list[DependencyDiagnostic],
|
||||
) -> Self:
|
||||
"""Create guidance after run_deployment, inspect_run, or resume_run."""
|
||||
```
|
||||
|
||||
## Wrapper Draft Guidance Rules
|
||||
|
||||
For `create_draft_workspace_from_capability`:
|
||||
|
||||
- High confidence and no missing decisions:
|
||||
- `can_continue=true`
|
||||
- `can_save_now=true`
|
||||
- `recommended_next_tool=wf.workflow.validate_draft_workspace`
|
||||
- Low/medium confidence or any missing decisions:
|
||||
- `can_continue=true`
|
||||
- `can_save_now=false`
|
||||
- `recommended_next_tool=wf.workflow.patch_draft_workspace`
|
||||
- include conservative `patch_examples` when the missing decision can be
|
||||
represented safely
|
||||
|
||||
Patch examples must not invent business semantics.
|
||||
|
||||
Allowed examples:
|
||||
|
||||
- replace step output bindings with an empty list as a scaffold
|
||||
- point the caller to `patch_draft_workspace` with the correct `workspace_id`
|
||||
and `revision`
|
||||
- include an empty patch for boolean-outcome decisions when no safe automatic
|
||||
route exists
|
||||
|
||||
Not allowed:
|
||||
|
||||
- auto-extract `content[0].text`
|
||||
- auto-route on boolean output fields
|
||||
- infer error outcome mapping from arbitrary output data
|
||||
|
||||
## Future Deployment Guidance
|
||||
|
||||
For `validate_deployment`:
|
||||
|
||||
- No diagnostics:
|
||||
- recommend `wf.workflow.run_deployment`
|
||||
- `source_unreachable`:
|
||||
- recommend fixing/reloading source, then `wf.workflow.validate_deployment`
|
||||
with `live_check=true`
|
||||
- `binding_missing`:
|
||||
- recommend `wf.workflow.save_deployment`
|
||||
- `capability_missing` or `schema_changed`:
|
||||
- recommend inspecting capabilities or refreshing catalog before running
|
||||
|
||||
The exact deployment constructor can come later. The important rule is that
|
||||
diagnostics stay the source of truth; `next_actions` only summarizes.
|
||||
|
||||
## Future Run Guidance
|
||||
|
||||
For `run_deployment`, `inspect_run`, and `resume_run`:
|
||||
|
||||
- `status=completed`:
|
||||
- no required next tool
|
||||
- optionally suggest `read_run_trace` only if caller is debugging
|
||||
- `status=failed`:
|
||||
- recommend `inspect_run` or bounded `read_run_trace`
|
||||
- `status=interrupted`:
|
||||
- recommend `wf.workflow.resume_run`
|
||||
- blocked resume:
|
||||
- recommend repairing diagnostics and retrying `resume_run`
|
||||
|
||||
Never recommend reading the full trace. Always point to bounded trace ranges.
|
||||
|
||||
## JSON Compatibility
|
||||
|
||||
Existing `create_draft_workspace_from_capability` output should remain stable:
|
||||
|
||||
```json
|
||||
{
|
||||
"next_actions": {
|
||||
"can_save_now": false,
|
||||
"recommended_next_tool": "wf.workflow.patch_draft_workspace",
|
||||
"reason": "...",
|
||||
"patch_examples": [],
|
||||
"warnings": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the generic model adds `can_continue`, it is additive. Existing clients that
|
||||
only read `can_save_now`, `recommended_next_tool`, `reason`, `patch_examples`,
|
||||
and `warnings` continue to work.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Add `next_actions.py` with generic models and `from_wrapper_hints`.
|
||||
2. Re-export or import these models from `workflow_surface.models` if needed for
|
||||
FastMCP schema generation.
|
||||
3. Replace handler-local `_wrapper_draft_next_actions` helpers with the model
|
||||
constructor.
|
||||
4. Keep response JSON fields stable.
|
||||
5. Add tests proving the old `next_actions` fields still serialize the same.
|
||||
6. Later, add deployment/run constructors in separate small passes.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should `recommended_next_tool` allow non-workflow tools such as
|
||||
`wf.admin.reload_config`, or should guidance stay workflow-surface only?
|
||||
- Should patch examples grow a typed `tool_request_schema` later, or is raw JSON
|
||||
request payload enough?
|
||||
- Should `can_continue=false` appear when there is no useful next tool, or should
|
||||
`recommended_next_tool=null` be enough?
|
||||
@@ -657,6 +657,22 @@ artifact.
|
||||
|
||||
### 3. Patch Or Validate The Workspace
|
||||
|
||||
When patching output bindings, keep the two levels separate:
|
||||
|
||||
- Step-level `steps.<id>.output` uses `source` local -> `target` state.
|
||||
- Top-level `output` uses `path` graph -> `target` local output payload.
|
||||
|
||||
For explicit final output projection from state, use:
|
||||
|
||||
```json
|
||||
{
|
||||
"path": { "root": "state", "parts": ["result_text"] },
|
||||
"target": { "root": "local", "parts": ["result_text"] }
|
||||
}
|
||||
```
|
||||
|
||||
Do not use `source` at top level. `source` belongs to step output bindings.
|
||||
|
||||
If the hints are good enough, validate:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -350,6 +350,8 @@ treating the scaffold as complete.
|
||||
This is advisory guidance for clients that cannot easily read the full docs.
|
||||
It summarizes whether the scaffold is safe-looking enough to validate, which
|
||||
tool to call next, and concrete patch examples for common missing decisions.
|
||||
`patch_examples` may include top-level output projection bindings, which use
|
||||
`path` / `target` (not step-level `source` / `target`).
|
||||
|
||||
`next_actions.can_save_now` is not enforced. A caller can still save a low
|
||||
confidence draft, but the field exists to make that risk explicit.
|
||||
|
||||
@@ -106,6 +106,53 @@ Important details:
|
||||
- When saved with source bindings, concrete refs can be normalized to logical
|
||||
refs such as `demo.echo_tool`.
|
||||
|
||||
## Two Outputs, Different Shapes
|
||||
|
||||
Drafts have two fields named `output`, but they do different jobs.
|
||||
|
||||
### Step-Level `steps.<id>.output`
|
||||
|
||||
Step output writes a node's local return payload into workflow state. It uses
|
||||
`source` / `target`:
|
||||
|
||||
```json
|
||||
{
|
||||
"source": { "root": "local", "parts": ["text"] },
|
||||
"target": { "root": "state", "parts": ["result_text"] }
|
||||
}
|
||||
```
|
||||
|
||||
Read this as:
|
||||
|
||||
```text
|
||||
node output.text -> state.result_text
|
||||
```
|
||||
|
||||
### Top-Level `output`
|
||||
|
||||
Top-level workflow output projects graph values into the final public workflow
|
||||
output payload. It uses input-binding shape: `path` / `target`, not
|
||||
`source` / `target`. Do not use step output `source` here; it belongs to
|
||||
step-level node output bindings only.
|
||||
|
||||
```json
|
||||
{
|
||||
"path": { "root": "state", "parts": ["result_text"] },
|
||||
"target": { "root": "local", "parts": ["result_text"] }
|
||||
}
|
||||
```
|
||||
|
||||
Read this as:
|
||||
|
||||
```text
|
||||
state.result_text -> workflow output.result_text
|
||||
```
|
||||
|
||||
If top-level `output` is empty, the runtime keeps the legacy same-name fallback:
|
||||
for every field in `output_schema`, it copies the top-level state field with the
|
||||
same name when present. That fallback is convenient, but explicit output
|
||||
projection is clearer for new workflows.
|
||||
|
||||
## Explicit Outputs And Error Outcomes
|
||||
|
||||
Use `__end__` as the compact terminal path for the normal `ok` workflow outcome.
|
||||
|
||||
@@ -308,7 +308,14 @@ class WorkflowDraft(BaseModel):
|
||||
state_schema: JsonObject
|
||||
output_schema: JsonObject
|
||||
outcomes: list[str] = Field(default_factory=lambda: ["ok"], min_length=1)
|
||||
output: list[InputBinding] = Field(default_factory=list)
|
||||
output: list[InputBinding] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
"Top-level workflow output projection. Uses input-binding shape: "
|
||||
"`path` reads from input/state/context and `target` writes to the "
|
||||
"local public output payload. Do not use step output `source` here."
|
||||
),
|
||||
)
|
||||
start: str
|
||||
steps: dict[str, DraftStep]
|
||||
routes: dict[str, dict[str, str]] = Field(default_factory=dict)
|
||||
|
||||
@@ -30,10 +30,11 @@ class Workflow(BaseModel):
|
||||
output: list[InputBinding] = Field(
|
||||
default_factory=list,
|
||||
description=(
|
||||
"Optional final output projection bindings. Sources read from graph "
|
||||
"paths such as state.result, and targets write into the workflow "
|
||||
"output payload. When omitted, legacy same-name top-level state "
|
||||
"projection is used."
|
||||
"Optional final output projection bindings. Uses input-binding shape: "
|
||||
"`path` reads from graph paths such as state.result, and `target` "
|
||||
"writes into the workflow output payload. Use `path`, not `source`; "
|
||||
"`source` belongs to step-level node output bindings. When omitted, "
|
||||
"legacy same-name top-level state projection is used."
|
||||
),
|
||||
)
|
||||
node_defs: list[NodeDef] = Field(default_factory=list)
|
||||
|
||||
@@ -36,6 +36,14 @@ def test_server_exposes_platform_documentation_resources() -> None:
|
||||
assert "live_check" in result[0].text
|
||||
assert "delete_deployment" in result[0].text
|
||||
|
||||
drafts_result = await client.read_resource("wf://docs/workflow-drafts")
|
||||
assert isinstance(drafts_result[0], mcp_types.TextResourceContents)
|
||||
assert "Two Outputs, Different Shapes" in drafts_result[0].text
|
||||
assert (
|
||||
"Do not use step output `source` here" in drafts_result[0].text
|
||||
or "Do not use `source` at top level" in drafts_result[0].text
|
||||
)
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user