docs: add draft patch and plan artifact workflow guidance
This commit is contained in:
@@ -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/
|
||||
|
||||
@@ -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"]}}
|
||||
]}
|
||||
]
|
||||
@@ -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>`.
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user