Run and author a workflow
Chat is improvisation: good for exploration, wrong for work you need to run the same way every time. For repeatable work keelson has workflows, plain YAML files that describe a DAG of nodes the engine executes deterministically. You will run one that already ships, then author one from scratch, break it on purpose to see what validation catches, and watch it run.
Run one that already shipped
Section titled “Run one that already shipped”Your first run seeded eight starter workflows, the ones doctor
counted, and you can run any of them before writing a line of YAML. List the
catalog:
keelson workflow listworkflows: - name: smoke-test description:Use when: User wants to verify the workflow engine end-to-end ... nodeCount: 9 source: global - ...Run smoke-test first. It exercises every node type the engine supports on Bun
alone, so a clean run is an end-to-end proof that the engine works on your
machine. Run it with --watch:
keelson workflow run smoke-test --watch◆ target: cwd=/Users/you▶ run 4f2a1c9e (smoke-test) · prompt-node … · command-node … · loop-node … · bash-json-node … · script-bun-node … ✓ bash-json-node {"echoed":"no-input","timestamp":"2026-06-26T21:12:13.164Z"} ✓ script-bun-node ✓ prompt-node ✓ command-node ✓ loop-node · downstream … · gated … downstream got: ok gated-ok ✓ downstream ✓ gated · merge … merge-ok ✓ merge · assert … PASS: all node types verified ✓ assert■ succeededFive nodes with no dependencies start together; the rest fall in behind them as
their inputs land. The agent nodes did real turns (prompt-node was asked for
exactly ok, and downstream read that answer back through the env channel),
and the final assert node checks that every upstream node produced output and
prints PASS. That is a nine-node workflow shipping in the box, exercising every
node type with no authoring. Now write your own.
Hit the wall first
Section titled “Hit the wall first”You just ran a workflow that ships in the box. Authoring your own is the same
catalog, one directory over: a workflow is a YAML file in ~/.keelson/workflows,
and the one you have in mind is not there yet. Run it and you hit the wall:
keelson workflow run hello-harnesserror: no workflow named 'hello-harness' under /Users/you/.keelson/workflowsThe exit code is 4, the CLI’s stable code for “not found”. The error names
the directory it searched, and that directory is the whole catalog mechanism:
a workflow is a YAML file in ~/.keelson/workflows. Ribs can contribute
workflows there too, but nothing stops you from writing your own, so let’s
fix this error the direct way.
Author the workflow
Section titled “Author the workflow”Create ~/.keelson/workflows/hello-harness.yaml:
name: hello-harnessdescription: Three node kinds, one dependency, one gate. A first workflow.
nodes: - id: ask prompt: "Reply with exactly the words 'engine answers' and nothing else." idle_timeout: 30000
- id: check bash: 'echo ''{"status":"ok"}'''
- id: report bash: 'echo "the agent said: $KEELSON_NODE_ask_OUTPUT"' depends_on: [ask]
- id: done bash: "echo 'all checks passed'" depends_on: [check, report] when: "$check.output.status == 'ok'"Four nodes, three ideas:
promptruns an agent turn. Theasknode sends its prompt to whatever provider the server registered, with an idle timeout so a stalled turn cannot hang the run.bashruns a shell script. Thechecknode emits JSON, and that matters downstream: JSON output unlocks dot-access like$check.output.status.- Edges are data, not order.
reportdeclaresdepends_on: [ask]and reads the upstream output through theKEELSON_NODE_ask_OUTPUTenvironment variable.donewaits on two parents and adds awhen:condition, so it runs only if the gate evaluates true.
The shape on the page is a diagram waiting to happen. Here is the same file as the engine sees it:
Figure 1. The hello-harness DAG. Nodes with no dependencies start
together; done fires only after both parents succeed and the
gate on check’s output holds.
Let validation catch a mistake
Section titled “Let validation catch a mistake”Before running anything, validate. To see what that buys you, first sabotage
the file: change depends_on: [ask] to depends_on: [aks] and run:
keelson workflow validate hello-harnessresults: - filename: /Users/you/.keelson/workflows/hello-harness.yaml ok: false warnings: error: Node 'report' depends_on unknown node 'aks'failed: 1total: 1The validator reads the whole graph, so a reference to a node that does not
exist is caught before any node executes, with the exact node and the exact
bad reference named. Fix the typo back to ask and validate again:
results: - filename: /Users/you/.keelson/workflows/hello-harness.yaml ok: true warnings: error:failed: 0total: 1Watch it run
Section titled “Watch it run”keelson workflow run hello-harness --watch◆ target: cwd=/Users/you▶ run 7b17995e (hello-harness) · ask … · check … ✓ ask {"status":"ok"} ✓ check · report … the agent said: engine answers ✓ report · done … all checks passed ✓ done■ succeeded (2.1s)Read the trace against Figure 1. ask and check started together because
neither has dependencies. report waited for ask, then read its output
through the env channel. done waited for both parents, evaluated its gate
against check’s JSON, and ran.
One line deserves a second look: report printed the agent’s answer, not your
prompt. The ask node asked the model for exactly engine answers, and report
surfaced that through the env channel, not a shared chat history. The nodes never
share a conversation; they hand each other output, which is what makes a workflow
a DAG and not a chat.
The full node taxonomy
Section titled “The full node taxonomy”You have used two node kinds. There are seven:
| Node | What it runs |
|---|---|
prompt | One agent turn against a provider. |
bash | A shell script, with upstream output in KEELSON_NODE_* env vars. |
command | A named, reusable prompt file from ~/.keelson/commands. |
loop | An agent turn repeated until an answer matches until:, bounded by max_iterations. |
script | A script file under a declared runtime, such as runtime: bun. |
approval | A pause that waits for a human yes or no before downstream nodes run. |
cancel | A controlled early exit for a branch. |
Combined with depends_on, when:, and trigger_rule: for merge semantics,
this is the entire vocabulary. Confirm your workflow joined the catalog,
alongside the seeded starters:
keelson workflow listworkflows: - name: fix-issue description: ... nodeCount: 24 source: global - name: hello-harness description: Three node kinds, one dependency, one gate. A first workflow. nodeCount: 4 source: global - name: smoke-test description: ... nodeCount: 9 source: global - ...The list includes all eight bundled starters plus any workflows you have added.
Bundled and user-authored workflows are both reported as source: global in
server mode. The path field only appears when the server is down and the CLI
runs discovery in-process.
Common errors
Section titled “Common errors”| Symptom | What’s happening |
|---|---|
error: no workflow named '...' (exit 4) | The name matches no file in ~/.keelson/workflows. Run keelson workflow list to see the catalog, and check the spelling and the .yaml extension. |
A prompt node fails with idle_timeout | The agent turn produced no output for longer than the node’s idle_timeout (30s here). Raise it on the node, or confirm a provider is registered, since an unconfigured provider can stall the turn. |
A bash node reads an empty upstream value | Dot-access like $ask.output is for when: and prompt text. A bash node reads upstream output from the KEELSON_NODE_<id>_OUTPUT env var (the id’s hyphens become underscores). |
A prompt node fails with a Copilot authentication error | Copilot is not signed in for the server running the workflow. Re-run the first-run sign-in; keelson doctor confirms it. |
What you proved
Section titled “What you proved”A workflow is a YAML file you can read in one screen: nodes, edges, gates. Validation catches broken graphs before execution, the watch stream shows you the DAG executing in dependency order, and the provider is swappable without touching the file.
A workflow runs the same way every time, but each run still starts from nothing it learned last time. The next page gives the harness a memory: standing context a run can draw on, and conclusions it can hand back to you.
Continue to Make it remember.