docs: add draft patch and plan artifact workflow guidance

This commit is contained in:
lda
2026-06-15 05:34:05 +07:00 Verified
parent bee5ead63e
commit 49011464fc
10 changed files with 229 additions and 4 deletions
+22
View File
@@ -278,6 +278,14 @@ wf draft patch concat_ws \
--input '[{"op":"replace","path":"/name","value":"concat_ws_v2"}]' --input '[{"op":"replace","path":"/name","value":"concat_ws_v2"}]'
``` ```
For larger structural edits, prefer a patch file:
```bash
wf draft patch concat_ws \
--revision 1 \
--input-file draft-patch.json
```
Validate: Validate:
```bash ```bash
@@ -327,6 +335,20 @@ wf artifact inspect concat_ws 1
Artifacts are immutable saved workflow definitions. List output is compact by Artifacts are immutable saved workflow definitions. List output is compact by
design; use `inspect` for full details. design; use `inspect` for full details.
Create an artifact directly from a raw JSON/YAML workflow plan file:
```bash
wf artifact create-from-plan workflow.plan.json \
--artifact concat_ws \
--version 1 \
--title "Concat Workflow" \
--outcome ok \
--binding local.ops=local.ops
```
Prefer draft workspaces for iterative authoring. Use `create-from-plan` when a
compiler, fixture, or advanced client already has a complete raw workflow plan.
Delete an unreferenced artifact version: Delete an unreferenced artifact version:
```bash ```bash
@@ -30,6 +30,13 @@ If you need to write a workflow definition, write a declarative JSON/YAML file
and then apply/run it through the product-facing workflow tools. Do not hide the and then apply/run it through the product-facing workflow tools. Do not hide the
workflow construction inside a Python script. workflow construction inside a Python script.
Two product-facing authoring paths are acceptable:
- create a draft from one capability, patch it with RFC 6902 JSON Patch, then
validate/save/deploy/run it;
- or, if you already have a complete raw workflow plan file, use
`wf artifact create-from-plan` before deploy/run.
The repository already includes a deterministic source example at: The repository already includes a deterministic source example at:
```text ```text
@@ -0,0 +1,2 @@
.wf_browser_click_store/
+28 -3
View File
@@ -21,16 +21,41 @@ From the repository root:
```powershell ```powershell
uv run wf config validate examples/browser_click_workflow/wf.config.json uv run wf config validate examples/browser_click_workflow/wf.config.json
uv run wf-rpc-server --config examples/browser_click_workflow/wf.config.json uv run wf --config examples/browser_click_workflow/wf.config.json --local status
``` ```
In another terminal: List the configured Python source capabilities:
```powershell ```powershell
uv run wf --config examples/browser_click_workflow/wf.config.json status
uv run wf --config examples/browser_click_workflow/wf.config.json cap list --source local.browser_click uv run wf --config examples/browser_click_workflow/wf.config.json cap list --source local.browser_click
``` ```
## Draft Patch Lifecycle
This example includes `draft-patch.json`, an RFC 6902 JSON Patch array that
turns a single-capability draft into the three-node workflow:
```text
open_click_page -> wait_for_click -> collect_snapshots
```
Apply and run it through the product-facing CLI:
```powershell
uv run wf --config examples/browser_click_workflow/wf.config.json draft create-from-capability browser_click_ws local.browser_click.open_click_page --name browser_click_workflow
uv run wf --config examples/browser_click_workflow/wf.config.json draft patch browser_click_ws --revision 1 --input-file examples/browser_click_workflow/draft-patch.json
uv run wf --config examples/browser_click_workflow/wf.config.json draft validate browser_click_ws
uv run wf --config examples/browser_click_workflow/wf.config.json draft save browser_click_ws --artifact browser_click_case_study --version 1 --title "Browser Click Case Study" --outcome ok
uv run wf --config examples/browser_click_workflow/wf.config.json deploy save browser_click_case_study.default --artifact browser_click_case_study --version 1 --binding local.browser_click=local.browser_click
uv run wf --config examples/browser_click_workflow/wf.config.json deploy validate browser_click_case_study.default
uv run wf --config examples/browser_click_workflow/wf.config.json run start browser_click_case_study.default --input-file examples/browser_click_workflow/run-input.json
```
The example config has a local client target, so these commands build the
configured workflow server in-process. Use `wf-rpc-server --config
examples/browser_click_workflow/wf.config.json` plus `wf --url ...` only when
you specifically want to exercise the JSON-RPC server path.
The full artifact/deployment/run lifecycle is covered by: The full artifact/deployment/run lifecycle is covered by:
```powershell ```powershell
@@ -0,0 +1,75 @@
[
{"op": "replace", "path": "/name", "value": "browser_click_case_study"},
{"op": "replace", "path": "/start", "value": "open"},
{"op": "replace", "path": "/input_schema", "value": {
"type": "object",
"properties": {
"button_label": {"type": "string"},
"open_browser": {"type": "boolean"},
"simulate": {"type": "boolean"},
"timeout_seconds": {"type": "number"}
},
"required": ["button_label", "open_browser", "simulate", "timeout_seconds"]
}},
{"op": "replace", "path": "/state_schema", "value": {
"type": "object",
"properties": {
"opened": {"type": "object", "reducer": "wf.std.replace"},
"clicked": {"type": "object", "reducer": "wf.std.replace"},
"result": {"type": "object", "reducer": "wf.std.replace"}
}
}},
{"op": "replace", "path": "/output_schema", "value": {
"type": "object",
"properties": {
"before": {"type": "object"},
"after": {"type": "object"},
"closed": {"type": "boolean"}
},
"required": ["before", "after", "closed"]
}},
{"op": "replace", "path": "/routes", "value": {
"open": {"ok": "wait"},
"wait": {"ok": "collect"},
"collect": {"ok": "__end__"}
}},
{"op": "replace", "path": "/steps", "value": {
"open": {
"use": "local.browser_click.open_click_page",
"input": [
{"path": {"root": "input", "parts": ["button_label"]}, "target": {"root": "local", "parts": ["button_label"]}},
{"path": {"root": "input", "parts": ["open_browser"]}, "target": {"root": "local", "parts": ["open_browser"]}}
],
"output": [
{"source": {"root": "local", "parts": []}, "target": {"root": "state", "parts": ["opened"]}}
]
},
"wait": {
"use": "local.browser_click.wait_for_click",
"input": [
{"path": {"root": "state", "parts": ["opened", "session_id"]}, "target": {"root": "local", "parts": ["session_id"]}},
{"path": {"root": "input", "parts": ["simulate"]}, "target": {"root": "local", "parts": ["simulate"]}},
{"path": {"root": "input", "parts": ["timeout_seconds"]}, "target": {"root": "local", "parts": ["timeout_seconds"]}}
],
"output": [
{"source": {"root": "local", "parts": []}, "target": {"root": "state", "parts": ["clicked"]}}
]
},
"collect": {
"use": "local.browser_click.collect_snapshots",
"input": [
{"path": {"root": "state", "parts": ["opened", "session_id"]}, "target": {"root": "local", "parts": ["session_id"]}},
{"path": {"root": "state", "parts": ["opened", "before"]}, "target": {"root": "local", "parts": ["before"]}},
{"path": {"root": "state", "parts": ["clicked", "after"]}, "target": {"root": "local", "parts": ["after"]}}
],
"output": [
{"source": {"root": "local", "parts": []}, "target": {"root": "state", "parts": ["result"]}}
]
}
}},
{"op": "replace", "path": "/output", "value": [
{"path": {"root": "state", "parts": ["result", "before"]}, "target": {"root": "local", "parts": ["before"]}},
{"path": {"root": "state", "parts": ["result", "after"]}, "target": {"root": "local", "parts": ["after"]}},
{"path": {"root": "state", "parts": ["result", "closed"]}, "target": {"root": "local", "parts": ["closed"]}}
]}
]
+3
View File
@@ -36,6 +36,7 @@ wf draft patch <workspace_id> --revision <n> --input-file patch.json
wf draft validate <workspace_id> wf draft validate <workspace_id>
wf draft save <workspace_id> --artifact <artifact_id> --version <n> --title <title> wf draft save <workspace_id> --artifact <artifact_id> --version <n> --title <title>
wf artifact create-from-plan workflow.plan.json --artifact <artifact_id> --version <n> --title <title>
wf deploy save <deployment_id> --artifact <artifact_id> --version <n> --binding <logical>=<concrete> wf deploy save <deployment_id> --artifact <artifact_id> --version <n> --binding <logical>=<concrete>
wf deploy validate <deployment_id> wf deploy validate <deployment_id>
wf run start <deployment_id> --input-file input.json wf run start <deployment_id> --input-file input.json
@@ -47,6 +48,8 @@ wf run trace <run_id> --from 0 --limit 25
- Prefer `--input-file` for large JSON. - Prefer `--input-file` for large JSON.
- Prefer `--format ids` or `--format compact` for discovery. - Prefer `--format ids` or `--format compact` for discovery.
- Use `wf cap call` as a cheap smoke test before creating a draft. - Use `wf cap call` as a cheap smoke test before creating a draft.
- Prefer draft workspaces for iterative authoring; use `artifact create-from-plan`
only when you already have a complete raw JSON/YAML workflow plan.
- Do not request unbounded traces. - Do not request unbounded traces.
- Do not treat wrapper hints as semantic guarantees. - Do not treat wrapper hints as semantic guarantees.
- If validation fails, run `wf explain <code>` or `wf explain --input-file <validation-output.json>`. - If validation fails, run `wf explain <code>` or `wf explain --input-file <validation-output.json>`.
+4 -1
View File
@@ -13,7 +13,7 @@ Use this skill for the workflow lifecycle, regardless of front door:
Prefer small discovery calls, draft workspaces, validation, and bounded trace Prefer small discovery calls, draft workspaces, validation, and bounded trace
reads. Do not write raw workflow plans unless the user explicitly asks for the reads. Do not write raw workflow plans unless the user explicitly asks for the
low-level escape hatch. low-level escape hatch or you already have a complete compiler/generated plan.
## Workflow Lifecycle ## Workflow Lifecycle
@@ -41,6 +41,9 @@ low-level escape hatch.
lifecycle behavior. lifecycle behavior.
- Treat wrapper hints as scaffolding, not semantic truth. - Treat wrapper hints as scaffolding, not semantic truth.
- Use draft workspaces for iterative authoring; avoid rewriting full drafts. - Use draft workspaces for iterative authoring; avoid rewriting full drafts.
- If a complete raw JSON/YAML plan already exists, the CLI escape hatch is
`wf artifact create-from-plan`; do not write helper scripts that call
`WorkflowApi.create_artifact_from_plan` directly.
- Use explicit source bindings at deployment time. - Use explicit source bindings at deployment time.
- Keep traces bounded with `trace_range`. - Keep traces bounded with `trace_range`.
- Do not expect a newly saved workflow to become a new MCP tool mid-session. - Do not expect a newly saved workflow to become a new MCP tool mid-session.
@@ -70,3 +70,12 @@ Prefer focused helpers over JSON Patch for common edits:
- `set_step_output_map` - `set_step_output_map`
Use JSON Patch for structural edits the helpers do not cover. Use JSON Patch for structural edits the helpers do not cover.
For larger patches, write a JSON Patch array to a file and pass it with
`--input-file`:
```bash
wf draft patch <workspace_id> --revision <n> --input-file draft-patch.json
```
The patch file must be an RFC 6902 JSON Patch array, not a full draft object.
@@ -27,6 +27,30 @@ validated, runnable deployment.
8. Run the deployment. 8. Run the deployment.
9. Inspect the run summary first; read bounded traces only when needed. 9. Inspect the run summary first; read bounded traces only when needed.
## Raw Plan Escape Hatch
Draft workspaces are the normal interactive authoring path. If a compiler,
fixture, or advanced client already has a complete raw JSON/YAML workflow plan,
use the CLI escape hatch instead of writing a helper script around the Python
API:
```bash
wf artifact create-from-plan workflow.plan.json \
--artifact <artifact_id> \
--version 1 \
--title "Workflow Title" \
--outcome ok \
--binding <logical_source>=<concrete_source>
```
Then continue with the normal deployment and run steps:
```bash
wf deploy save <deployment_id> --artifact <artifact_id> --version 1
wf deploy validate <deployment_id>
wf run start <deployment_id> --input-file input.json
```
## Object Model ## Object Model
- **Source**: owner of capabilities, such as `wf.std` or `everything.default`. - **Source**: owner of capabilities, such as `wf.std` or `everything.default`.
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import json
from pathlib import Path from pathlib import Path
import pytest import pytest
@@ -234,3 +235,57 @@ async def test_browser_click_workflow_artifact_deployment_run_path(tmp_path) ->
assert run["output"]["after"]["status_text"] == "Button clicked" assert run["output"]["after"]["status_text"] == "Button clicked"
assert run["output"]["closed"] is True assert run["output"]["closed"] is True
assert run["trace_count"] >= 3 assert run["trace_count"] >= 3
@pytest.mark.asyncio
async def test_browser_click_draft_patch_lifecycle_example(tmp_path) -> None:
config = load_workflow_config(EXAMPLE_DIR / "wf.config.json")
config.server.store.root = tmp_path / "store"
server = build_workflow_server_from_workflow_config(config)
patch = json.loads((EXAMPLE_DIR / "draft-patch.json").read_text(encoding="utf-8"))
workspace = await server.api.create_draft_workspace_from_capability(
workspace_id="browser_click_ws",
capability_name="local.browser_click.open_click_page",
name="browser_click_workflow",
)
patched = await server.api.patch_draft_workspace(
workspace_id=workspace["workspace_id"],
revision=workspace["revision"],
patch=patch,
)
validated = await server.api.validate_draft_workspace(
workspace_id=workspace["workspace_id"],
)
saved = await server.api.create_artifact_from_workspace(
workspace_id=workspace["workspace_id"],
artifact_id="browser_click_case_study",
version=1,
title="Browser Click Case Study",
outcomes=["ok"],
source_bindings={"local.browser_click": "local.browser_click"},
)
await server.api.save_deployment(
{
"id": "browser_click_case_study.default",
"artifact_id": "browser_click_case_study",
"artifact_version": 1,
"bindings": {"local.browser_click": "local.browser_click"},
}
)
run = await server.api.run_deployment(
deployment_id="browser_click_case_study.default",
workflow_input={
"button_label": "Launch Workflow",
"open_browser": False,
"simulate": True,
"timeout_seconds": 2,
},
)
assert patched["status"] == "valid"
assert validated["status"] == "valid"
assert saved["saved"] is True
assert run["status"] == "completed"
assert run["output"]["before"]["clicked"] is False
assert run["output"]["after"]["clicked"] is True