run-agentic-workflow
Load this skill when repeated work runs from its `flow.yaml`: stages in order, approvals, gates, run state on disk, interactive or headless. Trigger on "run the flow.yaml", or when a generated `<name>
SKILL.mdplugins/standalone/ceh-workflow-runner/skills/run-agentic-workflow/SKILL.md
GitHubRun an Agentic Workflow
Run the stages of one flow.yaml in order, exactly as written. The runner owns ordering, approvals,
gates, run state and resume; each stage owns its own work. It ends every run with one line,
FLOW STATUS: done, FLOW STATUS: awaiting-approval <stage-id> or FLOW STATUS: failed <stage-id> <reason>, because a headless caller has nothing else to branch on.
Native first, the runner only for the delta. Each stage's work goes to the Claude Code
capability the config names: the Skill tool, the Agent tool, Bash, the Workflow tool. The runner
does not reimplement any of them. It keeps only what Claude Code does not provide across stages:
approvals asked mid-run between stages, world checks before a stage re-runs, run state that
survives the session for resume, and the FLOW STATUS: line. Ordering and gates exist to carry
those four. The reason is that native capabilities keep growing: when Claude Code covers one of the
four, that part of the runner goes, so the runner shrinks rather than competes with it.
The config format, the validation rules, the run-state.md layout and the status line are in
${CLAUDE_PLUGIN_ROOT}/references/flow-config-schema.md. Read it before step 1; this skill does not
restate it.
Procedure
- Load and check the config. Read
config, then check it against every rule under Validation rules in the schema doc, and the.jsof eachworkflowstage for carriage returns (a CRLF file fails at launch with "control characters that would be hidden in the approval dialog"). Resolve relative paths against the directory holdingconfig. On the first failure end withFLOW STATUS: failed - config invalid: <rule>, <stage>. Do this before anything runs, because a failure at stage 4 of a cheap flow is a smaller loss than one at stage 4 after an irreversible stage 3. - Resolve inputs. Fill each input from the arguments, then its
default. A missing required input is asked for withAskUserQuestionin interactive mode and ends the run asFLOW STATUS: failed - missing input <name>in headless mode. - Preflight capabilities. For each
workflowstage, check the Workflow tool is in your tool list (search deferred tools forWorkflowif it is not listed). For eachskillstage naming a plugin skill (plugin:skill), check it appears in the available skills. When the capability is absent: run the stage'sfallback, or end withFLOW STATUS: failed - <stage-id> needs <capability>if it isfail. Record each fallback chosen in the stage's note. Never imitate: do not read a workflow's.jsand do its work by hand to look successful. Files on disk do not prove a workflow ran. - Find or create the run. The run directory is
<run-dir>/<name>/<run-id>/, where<run-dir>is$CEH_WORKFLOW_RUN_DIR, defaulting to.agents_workspace/, and<run-id>is the start timeYYYYMMDD-HHMM. Create it unless the flow is notresumable, writes no artifact and has nofan_out, in which case it has no run directory at all.- A flow that is not
resumablealways starts a new run and ignoresresume. - Otherwise take the newest run under
<run-dir>/<name>/whoserun-state.mdhas a stage that is notdone.resume=latestresumes it,resume=newstarts a new run, and with neither, interactive mode asks and headless mode starts new. With no such run, start new. - On resume, take the inputs recorded in
run-state.md; a re-passed input that differs ends the run asFLOW STATUS: failed - inputs changed on resume. - Check now that the repo's
.gitignoreignores the run directory, and append it if not. Run artifacts are never committed.
- A flow that is not
- Walk the stages in order, skipping those already
done. For each stage:- Check
reads. Every input file must exist, elsefailed <id> missing-input <artifact>. - Approval. If the stage has
approvaland is not covered by an earlier approval, see Approvals below. - World check. If
unsafe_to_rerun, evaluateworld_checknow, on every attempt and every resume. It runs before dispatch and ahead of the run state, because state is only a record. When it is false the effect may already exist: stop withfailed <id> world-check-failed. Do not skip the stage and do not guess that it finished. - Dispatch per
run.kind, below. - Gate, below.
- Record the stage in
run-state.mdright away, with the gate evidence in its note.
- Check
- End with the status line, under Output.
Launch arguments
The arguments are key=value tokens. Quote values that contain spaces.
| Argument | Meaning |
|---|---|
config |
Required. Path to flow.yaml. Unset or unreadable: FLOW STATUS: failed - config unreadable. |
mode |
interactive (default) or headless. Explicit, never guessed: a skill cannot tell which it runs in, and a wrong guess either hangs on a question or skips a confirmation. Any other value fails. |
resume |
latest or new. Default: ask in interactive mode, new in headless. |
approve |
Comma-separated stage ids approved in advance. Honoured in both modes and recorded with source approve=<id>. |
| other | The flow's own inputs. An unknown key fails the run, so a typo is not silently ignored. |
A resumed session may instead carry the message APPROVED <stage-id>. Treat it as approval of that
stage when run-state.md shows it awaiting-approval, with source resume-prompt; otherwise ignore
it.
Dispatch
Pass {run}, every input path and the path of every schema the stage reads or writes, since an
isolated stage sees nothing else. Substitute placeholders in every value first.
skill— invoke the Skill tool with the named skill andargsplusrun=<run path>. The call runs in your own context, so write its artifact to thewritesfile yourself if it does not.agent— dispatch the Agent tool with the stage'ssubagent_type. The prompt carries theinstructions(or the instruction to load the named skill),{run}, thereadspaths with their schema paths, thewritespaths with theirs, and these three rules: it cannot ask the user, it must not invokerun-agentic-workflow, and a result that only exists in its reply is lost, so it writes its artifact to the stated path.script— runbash <script path> <args>with Bash through the path as resolved againstconfig's directory. A non-zero exit is a red gate.inline— do theinstructionsyourself.workflow— call the Workflow tool by name withargs, and wait for its result. Never launch it with a/<name>slash command, which fails headless. Write the result to the stage's onewritesfile in the form its schema gives. A launch that is denied or errors isfailed <id> workflow-denied; the fallback is for an absent tool only, because a fallback there would hide a missing permission. Dropnullresults.fan_out— read the item list fromitems_fromusing its schema. More thanmax_itemsfails the stage astoo-many-items. Derive each item id asitem_idsays. Dispatch oneagentper item with no output file yet, at mostmax_parallelat once, each writing its ownoutputfile. After they return, count the files against the item list, which is the first half of the gate: a worker that died leaves no file, not a failing one.
Approvals
The approval belongs to the runner, never to a stage: AskUserQuestion is stripped from every
subagent, so a dispatched stage cannot ask and would run straight through. Never let a stage ask.
If the stage id is in approve= or recorded as approved in run-state.md, proceed and record the
source. Otherwise:
| Mode | Behaviour |
|---|---|
| interactive | Ask with AskUserQuestion, showing what show names: the exact data, the whole batch once. If it cannot be answered, end as failed <id> no-user-to-ask. Approve proceeds. Decline ends as failed <id> declined with the stage pending, so a resumed run asks again. |
| headless | Follow approval.headless: stop writes {run}/approval-<id>.md with the show content, marks the stage awaiting-approval, prints that content above the status line and ends as awaiting-approval <id>. preapproved-only does the same unless the id was in approve=. fail ends as failed <id> approval-required. |
A stage named in another stage's covers shares that approval and does not ask again. Record each
approval in run-state.md with its source and time. Never proceed on silence.
Gates
After a stage, evaluate gate.check with Read, Grep or Bash, and record one line of evidence in the
stage's note. Do not accept "looks right".
- Green — mark the stage
doneand continue. - Red,
stop— mark the stagefailedand end asfailed <id> <what the check found>. Never continue degraded and never skip a stage to reach a later one: the order is the only reason a later stage's inputs are valid. - Red,
retry— re-run the stage withretry.changesapplied and the failure output in front of it, at mostretry.maxmore times, recording the attempt in the note. Runworld_checkagain before each attempt. When the bound is spent, stop as above. Never raise the bound. If an attempt fails with the same check output as the attempt before it, stop early asfailed <id> no-progress: the change between attempts did nothing, so the remaining attempts would spend the budget on the same result.
A green gate counts only if the stage left the check alone. Before dispatching a stage, record
git hash-object for the files its gate.check names, the script it runs, and the tests it runs.
Hash them again before marking done. If any changed, end as failed <id> gate-edited, because a
stage that rewrites its own check can turn any result green. Skip this for a stage whose stated job
is to edit those files.
Rules
- Run the config as written. Do not reorder stages, merge them, add a stage, or tune a bound. Where the config is wrong, fail and say which rule. The stages and gates are what was approved, so a run that edits them is no longer the approved workflow.
- Never imitate an absent tool and never fall back after a denial.
- One level deep. A stage may not run this skill, and a stage you dispatch is told so.
- No secrets on disk. Run artifacts and
run-state.mdhold references to secrets, never values. - In headless mode, ask only through the approval path above.
Output
Run state and the status line
When the flow is resumable, keep {run}/run-state.md in the layout the schema doc gives: stage
states, approvals, and the status line. Update it after every stage change, not at the end, because
context that dies mid-run takes anything unwritten with it. A resumed run skips stages marked done
and re-enters the first that is not.
The last line of your reply is the status line, and with run-state.md it is the last line of that
file too. Say nothing after it.
Stop conditions
- The config fails a validation rule, an input is missing or a capability is absent → end before
any stage runs with
FLOW STATUS: failed - <reason>. - A gate is red and stops the run, a retry makes no progress, a stage edited its own gate's check,
a world check fails, an input file is missing or an approval is declined →
FLOW STATUS: failed <stage-id> <reason>. - A headless approval is needed and not given →
FLOW STATUS: awaiting-approval <stage-id>.