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"}]'
```
For larger structural edits, prefer a patch file:
```bash
wf draft patch concat_ws \
--revision 1 \
--input-file draft-patch.json
```
Validate:
```bash
@@ -327,6 +335,20 @@ wf artifact inspect concat_ws 1
Artifacts are immutable saved workflow definitions. List output is compact by
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:
```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
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:
```text
@@ -0,0 +1,2 @@
.wf_browser_click_store/
+28 -3
View File
@@ -21,16 +21,41 @@ From the repository root:
```powershell
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
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
```
## 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:
```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 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 validate <deployment_id>
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 `--format ids` or `--format compact` for discovery.
- 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 treat wrapper hints as semantic guarantees.
- 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
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
@@ -41,6 +41,9 @@ low-level escape hatch.
lifecycle behavior.
- Treat wrapper hints as scaffolding, not semantic truth.
- 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.
- Keep traces bounded with `trace_range`.
- 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`
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.
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
- **Source**: owner of capabilities, such as `wf.std` or `everything.default`.
@@ -1,5 +1,6 @@
from __future__ import annotations
import json
from pathlib import Path
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"]["closed"] is True
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