UNPKG

@workflow-manager/runner

Version:

CLI runner for in-memory and markdown workflow orchestration using ATEP-like envelopes

311 lines (248 loc) • 18.5 kB
--- name: workflow-author description: > Author repeatable wfm workflows from a task description, validate them, run them, and narrate progress as the workflow executes. Load this skill when a user describes a multi-step task that should run the same way every time — turn it into a wfm workflow file, validate it, and then drive `wfm run` while translating status polls and approval gates into plain-language updates for the user. type: core library: "@workflow-manager/runner" sources: - "navio/workflow-manager:src/parser.ts" - "navio/workflow-manager:src/types.ts" - "navio/workflow-manager:src/engine.ts" - "navio/workflow-manager:src/index.ts" - "navio/workflow-manager:src/sessionFile.ts" - "navio/workflow-manager:doc/guide/workflow-schema.md" - "navio/workflow-manager:doc/guide/runner-api.md" --- # workflow-author Turn a natural-language task description into a `wfm` workflow file: a static, reviewable, re-runnable artifact instead of a one-off chat session. This skill is the authoring + operating counterpart to `workflow-manager-cli` — read that skill for the general command reference; this one is about *deciding what to build* and *narrating a run to a human*. ## 1. When to use Use this skill when the user has a task that: - has more than one meaningful step, with a clear order or dependency between steps - should produce the **same shape of result** every time it runs — not a bespoke plan improvised fresh each session - benefits from an explicit quality gate (an objective check, a human sign-off, or both) before moving on - is worth sharing: a teammate, a CI job, or a future session should be able to run it unchanged Do **not** reach for a workflow file for a single ad-hoc question, a one-shot investigation, or a task whose steps genuinely can't be known in advance. Ad-hoc orchestration (just doing the work directly) is cheaper than authoring a workflow when there is nothing to repeat. The signal to watch for: the user says some version of "every time we do X" or "we need a repeatable way to Y." ## 2. The authoring loop Follow this sequence; do not skip validation. 1. **Elicit the task and success criteria.** Ask (or infer from context) what "done" looks like for the overall task, and for each step you expect to need. Concrete, checkable criteria beat vague ones — "the change satisfies the objective and includes tests" is checkable; "does a good job" is not. 2. **Decompose into chronological steps.** Every step gets a stable, kebab-case `key` (`implement-fix`, not `Step 1`). Wire `dependsOn` to express strict order — the engine resolves dependencies deterministically and rejects cycles, so an explicit `dependsOn: [previous-step]` is safer than relying on array order. 3. **Choose a quality gate per step.** For each step's `validation` (or an `approval` step's `approvalSpec.validation`), pick one: - `mode: agent` with concrete `criteria` — an objective, checkable condition a second agent call can verify against the step's output (tests pass, files changed match scope, no TODOs left). Prefer this whenever the check can be phrased as a fact about the artifact. - a dedicated `kind: approval` step — a human judgment call (does this look right, is this the right tradeoff, are we comfortable shipping this). Use for genuinely subjective or high-stakes decisions. - `mode: external` — an outside system resolves the step (a webhook, a deploy pipeline finishing). - `mode: none` — mechanical steps with no interesting failure mode (formatting, a fixed notification). 4. **Write the workflow.** Prefer the Markdown frontmatter format — humans (and you, later) can read the body notes alongside the machine-readable frontmatter; use JSON only for machine-generated definitions. Start from a scaffold: ```bash wfm scaffold --template agent-validated my-flow.md ``` This drops a working three-step example (agent-validated task → approval → finalize) that you edit in place rather than writing from a blank file. 5. **Validate, and keep validating.** `wfm validate my-flow.md` — fix every reported line, one at a time, until it prints `Validation OK`. Never hand a workflow to the user (or run it) with unresolved validation errors. 6. **Dry-run adapter-heavy workflows with mocks.** If steps use real adapters (`pi-agent`, `claude-code`, `opencode`, `acp`), set `taskSpec.payload.mockResult: success` (or `retry` / `rollback` / `fail` to test routing) and `taskSpec.adapterKey: mock` temporarily, or `wfm doctor my-flow.md` to check host requirements without executing. `opencode` runs real by default, so to dry-run an `opencode` step specifically, either set `taskSpec.payload.useRealAdapter: false` (keep `adapterKey: opencode`) or swap `adapterKey: mock`. This confirms dependency wiring and approval gating before spending a real adapter call. 7. **Done.** The file *is* the reusable artifact — no further "session state" to preserve. Re-running it later reproduces the same step sequence. ## 3. Schema cheat-sheet Everything here matches `src/types.ts` on this branch — do not invent fields. **Top level** (required: `key`, `title`, `steps`): ```yaml key: my-workflow # stable external identifier title: My Workflow # default run objective description: optional summary objectives: [optional, run-level, objectives] defaultRetryPolicy: { maxAttempts: 2 } skills: # named skills resolvable by taskSpec.init.skills my-skill: source: ./skills/my-skill/SKILL.md # must match skills/**/SKILL.md under the workflow dir steps: [ ... ] ``` **Step** (required: `key`, `kind`): ```yaml - key: my-step # stable, kebab-case kind: task # task | approval | system title: optional display title objective: optional step-level objective dependsOn: [other-step-key] retryPolicy: { maxAttempts: 2 } validation: # gates confirmation for THIS step's own record mode: none | human | external | agent required: true autoConfirm: false agent: # only when mode: agent adapterKey: pi-agent | mock | opencode | codex | claude-code | kimi | gemini | qwen | acp # default: this step's adapter criteria: "plain-language acceptance criteria the validator checks against" init: { model, skills, mcps, systemPrompts, context } payload: { mockResult: success } # lets mock drive the validator in tests taskSpec: # required when kind: task adapterKey: pi-agent | mock | opencode | codex | claude-code | kimi | gemini | qwen | acp # omit -> pi-agent init: model: openrouter/anthropic/claude-sonnet-4 skills: [skill-name] mcps: [mcp://endpoint] systemPrompts: [Focus on X] context: { any: json } payload: mockResult: success | retry | rollback | restart | yield | fail # mock adapter only timeoutMs: 600000 # per-step subprocess timeout (pi-agent default); SIGTERM on expiry approvalSpec: # required when kind: approval autoApprove: false validation: { mode: human, required: true, autoConfirm: false } ``` **Critical gotcha** (verified on this branch, `src/parser.ts`): the parser fills an *unset* step-level `validation` with `{ mode: "none", required: false, autoConfirm: true }` by default — **even on `approval` steps**. `canConfirm` in `src/engine.ts` checks `step.validation?.autoConfirm` *before* `step.approvalSpec?.validation?.autoConfirm`. If you only set `approvalSpec.validation` and leave the step's own top-level `validation` unset, the default `autoConfirm: true` wins and the gate **silently auto-approves** instead of waiting for a human. Always set both `validation` and `approvalSpec.validation` on an approval step with matching `mode`/`required`/`autoConfirm` — see the worked example below. **Validation rules the CLI enforces:** unique step keys; every `dependsOn` references an existing step; no dependency cycles; `kind` is `task | approval | system`; `taskSpec.adapterKey` (if set) is one of the supported adapters; `validation.mode` is `none | human | external | agent`; `mode: agent` is **not allowed** on approval steps (`approvalSpec.validation`). **Agent validation routing:** a validator agent's verdict becomes a QA action — `PROCEED` (continue), `RETRY_CURRENT` (rerun this step with feedback), `ROLLBACK_PREVIOUS` (rerun an earlier step), or `RESTART_ALL` (restart the run) — bounded by the step's `retryPolicy.maxAttempts`. **Step timeouts:** there is no top-level `timeoutSec` (or similar) step field — `StepDefinition` has no such field, and nothing in `src/engine.ts` reads one. The only supported timeout knob is adapter-specific: `taskSpec.payload.timeoutMs`, read by the subprocess-based executors (`pi-agent`, and the deprecated bespoke `claude-code`/`opencode` executors). It's milliseconds, defaults to `600000` (10 min) for `pi-agent`, and controls how long WFM waits before sending `SIGTERM` and failing the step with `timedOut: true` in `mutated_payload`. A long-running real step (an API-backed script, a slow build) should set this explicitly rather than relying on the default. ## 4. Running and narrating (the agent-as-UI protocol) Once a workflow validates, you are the UI for the run: start it detached, poll its state, and turn each transition into a short update instead of dumping raw JSON at the user. **Start it detached, with a session file:** ```bash wfm run my-flow.md --session-file .wfm/session.json & ``` The session file is written the moment the attach API is listening — `{ baseUrl, attachToken, runId, pid, startedAt }` — and rewritten with `endedAt` + `status` when the run finishes. It is never deleted, so it doubles as your "is this still running" signal. Every attach command below accepts `--session-file .wfm/session.json` instead of separate `--url`/`--token`. **Poll on a cadence and narrate transitions**, not raw payloads: ```bash wfm status --session-file .wfm/session.json ``` Read `status` and `currentStepKey` off the JSON, and translate: `"running"` + `currentStepKey: "implement-fix"` becomes something like *"step 1/3 (implement-fix) is running, attempt 1..."*. Don't re-poll faster than the work can plausibly progress — a few seconds between polls is usually plenty; back off further once a step has been running a while. If you're dry-running with `adapterKey: mock`, expect steps to resolve near-instantly: your very first `status` poll may already show several steps succeeded (or the run parked at an approval gate) with no intermediate "running" beat to narrate. That's expected — narrate what you actually observe rather than assuming a step-by-step cadence. **Use `events` for incremental detail** between polls instead of re-reading the whole snapshot: ```bash wfm events --session-file .wfm/session.json --since 4 ``` Track the last `nextSequence` you saw and pass it back as `--since` next time. Add `--include-logs` only when you actually want `agent.stdout`/`agent.stderr` chunks inline. **Use `logs` when the user asks what a step is doing right now:** ```bash wfm logs --session-file .wfm/session.json --step implement-fix --limit 50 ``` **Detect and handle `waitingForApproval`.** When `status`'s top-level `status` is `"waiting_for_approval"`, the JSON includes a `waitingForApproval` object with `stepKey`, `reason`, and a `preview` (`summary` plus `items` describing what's being reviewed, including dependency outputs). Summarize that preview for the user in plain language. Then either: - relay the decision the user gives you, or - if the user has explicitly delegated authority for this gate ("auto-approve the review steps," "you decide"), decide yourself and act: ```bash wfm approve --session-file .wfm/session.json --step review-gate --note "why you approved" wfm cancel --session-file .wfm/session.json --step review-gate --note "why you're stopping the run" ``` Never approve on the user's behalf without either their live input or a standing delegation they gave you for that specific gate — it is a QA checkpoint, not decoration. **On terminal status, report the outcome** by reading `endedAt` and `status` back from the session file (the run process has exited by then, so the attach API is gone — the session file is the only source left): ```bash cat .wfm/session.json # { ..., "endedAt": "...", "status": "succeeded" } ``` **Exit codes** (for the `wfm run` process itself, if you're waiting on it directly rather than polling): `0` — run succeeded; `2` — run finished but not successfully (failed, cancelled, or ended waiting); `1` — validation or runtime error before/during execution, not a normal terminal status. ## 5. Repeatability rules - Never rename or remove a published workflow's step keys — other automation and history may reference them. Add new steps or a new workflow `key`/version instead of mutating shape in place. - `key` (workflow) and step `key`s are stable external identifiers; change them only deliberately, and treat it as a breaking change for anything that depends on them. - Keep `taskSpec.payload` deterministic — avoid embedding timestamps, random IDs, or environment-specific paths that would make two runs diverge for reasons unrelated to the actual task. - Prefer `validation.mode: agent` criteria that a validator can check as a fact about the output (tests pass, a file exists, a diff touches only expected paths) over criteria that require taste. Save taste calls for `approval` steps. ## 6. Install/share ```bash wfm skill install workflow-author # this skill -> ./.claude/skills/ wfm skill install workflow-author --agent opencode # -> ./.opencode/skill/ wfm skill install workflow-author --global # -> ~/.claude/skills/ ``` To share the workflow *file* itself (not this skill) with teammates, use the remote registry — `wfm publish my-flow.md` / `wfm pull owner/slug`; see the `workflow-manager-cli` skill and `doc/guide/` for the full registry contract. ## Worked example A repeatable "fix a flaky test" workflow: an agent-validated implementation step, a human sign-off gate, then a finalize step. Validated on this branch with `wfm validate` (`Validation OK`) and executed end-to-end with the mock adapter. ```yaml --- key: fix-flaky-login-test title: Fix Flaky Login Test description: Diagnose and fix an intermittently failing login test, with a human sign-off before landing the fix objectives: - the login test passes reliably and the fix is reviewed before merge defaultRetryPolicy: maxAttempts: 2 steps: - key: implement-fix kind: task objective: Reproduce the flake in tests/login.test.ts, diagnose the root cause, and fix it dependsOn: [] retryPolicy: maxAttempts: 2 validation: mode: agent required: true autoConfirm: false agent: criteria: >- tests/login.test.ts passes 20 consecutive local runs, the fix addresses a root cause (not a retry/sleep workaround), and no unrelated files changed. init: model: openrouter/anthropic/claude-sonnet-4 systemPrompts: - Check the diff against the criteria; call out any retry/sleep workaround explicitly taskSpec: adapterKey: mock init: context: repo: example/webapp skills: [debugging, testing] systemPrompts: [Find the root cause before writing a fix; add a regression test] payload: mockResult: success - key: review-gate kind: approval objective: Human sign-off on the fix before it merges dependsOn: [implement-fix] # validation must be set here too, not just under approvalSpec — an unset # step.validation defaults to autoConfirm: true, which would silently skip # this gate. See the schema cheat-sheet above. validation: mode: human required: true autoConfirm: false approvalSpec: autoApprove: false validation: mode: human required: true autoConfirm: false - key: finalize kind: task objective: Open a PR with the fix, the regression test, and a summary of the root cause dependsOn: [review-gate] validation: mode: none required: false autoConfirm: true taskSpec: adapterKey: mock init: systemPrompts: [Open a PR summarizing the root cause, the fix, and the new regression test] payload: mockResult: success --- # Fix Flaky Login Test Repeatable workflow for chasing down a flaky test: implement a fix (agent-validated against explicit, checkable criteria), get a human sign-off, then finalize. ``` ### Simulated narration transcript ``` $ wfm run fix-flaky-login-test.md --session-file .wfm/session.json & Attach API: http://127.0.0.1:43121 (token b354...) [agent] Started the workflow in the background — I'll check in as it progresses. $ wfm status --session-file .wfm/session.json {"status":"running","currentStepKey":"implement-fix", ...} [agent] step 1/3 (implement-fix) is running, attempt 1 — reproducing the flake and writing a fix. $ wfm status --session-file .wfm/session.json {"status":"waiting_for_approval","currentStepKey":"review-gate", "waitingForApproval":{"stepKey":"review-gate", "reason":"confirmation required for review-gate", "preview":{"summary":"Approve this gate to continue after implement-fix.", "items":[{"title":"Dependency implement-fix","status":"succeeded", "summary":"Reproduce the flake in tests/login.test.ts, diagnose the root cause, and fix it"}]}}} [agent] implement-fix passed agent validation (tests green, real root-cause fix, no unrelated files touched) and is now waiting on you: review-gate wants a human sign-off before finalize runs. Want me to look at the diff summary, or are you approving as-is? [user] approve it $ wfm approve --session-file .wfm/session.json --step review-gate --note "Fix looks correct, root cause addressed" approved review-gate [agent] Approved review-gate. finalize is running now (opening the PR)... $ cat .wfm/session.json { ..., "endedAt": "2026-07-11T13:35:40.756Z", "status": "succeeded" } [agent] Done — the workflow succeeded. implement-fix and finalize both ran clean, and your sign-off on review-gate is recorded in the run's approval audit trail. ```