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"}]'
|
--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/
|
||||||
|
|
||||||
@@ -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"]}}
|
||||||
|
]}
|
||||||
|
]
|
||||||
@@ -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>`.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user