Authoring workflows
A workflow is one YAML file you can read in a single screen. Here is the practical how-to: the loop you follow to write one, where workflows live, and the recipes for the shapes you reach for most. For the full schema, see Workflow nodes; for a narrated first run, see Run and author a workflow.
The authoring loop
Section titled “The authoring loop”- Draft the YAML. Prefer inline
prompt,bash, andscriptnodes. Acommand:node references a markdown file that must already exist on disk. - Validate.
keelson workflow validate <name>runs the real loader. Fix every error and re-validate until it parses cleanly; read the warnings, which are advisory. - Run it.
keelson workflow run <name> --watchstreams the DAG executing in dependency order.
Once it runs, write a case set and grade it so the next prompt tweak is measured against held-out cases instead of eyeballed.
keelson workflow validate test-triagekeelson workflow run test-triage --watchWhere workflows live
Section titled “Where workflows live”| Scope | Location | Visible to |
|---|---|---|
| bundled | shipped with keelson (read-only) | The starter set, always present. |
| global | <home>/workflows/ | Every project and conversation. |
| project | <root>/.keelson/workflows/ | Only that project. |
| rib | workflows/ inside an installed rib package | Every project and conversation. |
A later file scope shadows a same-named earlier one: project over global over
bundled. Rib entries fill in around the file set, so any same-named file in the
three file scopes overrides a rib entry. The catalog hot-reloads the file
scopes, so a saved file is runnable immediately, with no restart; rib entries
load at server boot with the rest of rib activation. The workflow’s name:
field is its catalog key and must match the filename.
Give every workflow the structured description block so the UI cards and
workflow list render it scannably:
description: | Use when: a PR needs a structured review before merge Triggers: "review PR 42", "look at this pull request" Does: fetches the diff, reviews it, posts findings as a comment NOT for: writing new code or fixing the issues it findsRecipes
Section titled “Recipes”Fan-out and fan-in
Section titled “Fan-out and fan-in”Independent nodes run in parallel; a join node waits for all of them. Reference
each upstream output with $<id>.output.
nodes: - id: lint bash: bun run check - id: types bash: bun --filter '*' typecheck - id: verdict prompt: "Summarize: lint=$lint.output types=$types.output" depends_on: [lint, types]Classify, then branch
Section titled “Classify, then branch”A node emits a label; downstream nodes gate on it with when:. A false condition
skips the node.
- id: classify prompt: "Reply with exactly BUG or FEATURE for: $ARGUMENTS" - id: fix prompt: "Investigate and fix: $ARGUMENTS" depends_on: [classify] when: "$classify.output == 'BUG'"Approval gate
Section titled “Approval gate”Plan, pause for a human, then act only on approval. capture_response: true
exposes the reply as the node’s output so a later node can branch on it.
- id: plan prompt: "Draft a plan for: $ARGUMENTS" - id: gate approval: message: "Run this plan?" capture_response: true depends_on: [plan] - id: execute prompt: "Execute the plan: $plan.output" depends_on: [gate]Resume a paused run from the CLI with keelson workflow respond <runId> <nodeId> <text>, or from the Workflows surface in the browser. A simple approval gate like
this one needs only those three arguments; an interactive-loop pause also takes
--pause-id, the per-pause token from the run’s approval_awaiting frame, to
disambiguate retries.
Loop until done
Section titled “Loop until done”A loop repeats its prompt until a completion token appears, hard-bounded by
max_iterations so it can never run away.
- id: fix-until-green loop: prompt: "Run the tests, fix one failure, reply DONE when all pass." until: "DONE" max_iterations: 5Guard with cancel
Section titled “Guard with cancel”End a branch deliberately, with a recorded reason, when there is nothing to do.
- id: classify prompt: "Reply NONE if there is nothing actionable, else describe the work." - id: bail cancel: "nothing actionable" when: "$classify.output == 'NONE'" depends_on: [classify]Bound an agent’s reach
Section titled “Bound an agent’s reach”Agent nodes run inside declared limits. The file picks the provider and model
per node, gates tools with allowed_tools and denied_tools (rib tools are
default-off, so opt in by name), bounds stalls with idle_timeout, and pins
output shape with output_schema. Above the file, the operator floor still
applies: KEELSON_WORKFLOW_PROVIDER pins the provider every prompt node uses.
Feed agent output into a script safely
Section titled “Feed agent output into a script safely”A prompt node’s output can flow into a bash or script node, but the two
channels keep it safe: shell nodes read upstream output from
KEELSON_NODE_<id>_OUTPUT environment variables (where hyphens and any other
non-alphanumeric characters in the node id are replaced with underscores, e.g.
node fix-it → KEELSON_NODE_fix_it_OUTPUT), never spliced into the source,
so the model’s text is always data and never code.
- id: draft prompt: "Write a one-line release note for: $ARGUMENTS" - id: record bash: 'printf "%s\n" "$KEELSON_NODE_draft_OUTPUT" >> NOTES.md' depends_on: [draft] - id: fix-it prompt: "Fix the issue: $ARGUMENTS" - id: apply-fix bash: 'printf "%s\n" "$KEELSON_NODE_fix_it_OUTPUT"' depends_on: [fix-it]The variable is capped at 16 KiB and head+tail truncated past that, with a
marker in the middle: fine for prose, fatal for JSON. Every output is also
written in full to KEELSON_NODE_<id>_OUTPUT_FILE, which is always set, so
structured output is read from the file:
- id: plan prompt: "Return a JSON plan for: $ARGUMENTS" output_format: { type: json_object } - id: count-steps bash: 'jq -r ".steps | length" "$KEELSON_NODE_plan_OUTPUT_FILE"' depends_on: [plan]keelson workflow validate warns when a shell body parses the capped variable
directly. See the env channel for
the full table.
Related
Section titled “Related”- Workflow nodes: the full schema, every field, and the validation rules.
- Workflows: the execution model and the run lifecycle.
- Run and author a workflow: a narrated first build, breaking it on purpose to see what validation catches.