AgentStack
SKILL verified MIT Self-run

Flowcmd Author

skill-flowcmd-skill-flowcmd-author · by flowcmd

Use when the user asks to write, generate, scaffold, edit, debug, or fix a flowcmd YAML workflow — any file under a .flowcmd/ directory, any file passed to `flowcmd run` or `flowcmd validate`, or any request like "write a flow", "create a flowcmd pipeline", "chain these shell commands with an LLM in the middle". Covers the full flowcmd schema, sequential and parallel steps, retry, when-conditions…

No reviews yet
0 installs
4 views
0.0% view→install

Install

$ agentstack add skill-flowcmd-skill-flowcmd-author

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Flowcmd Author? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Authoring flowcmd workflows

flowcmd runs deterministic YAML workflows of shell steps. Each step is a /bin/sh -c command. Steps pass data by reading earlier steps' stdout through {{ steps.NAME.output }} templates. flowcmd has no built-in LLM abstraction — if a step needs a model, you invoke whatever CLI or HTTP endpoint produces text on stdout.

This skill is the source of truth for writing valid flowcmd YAML. If a field or syntax isn't documented here, it does not exist — do not invent it.

When flowcmd is the right tool

Reach for flowcmd when the user wants:

  • A reproducible pipeline that mixes shell commands and one or more LLM calls
  • Conditional steps (skip the commit if there's nothing staged)
  • Parallel fan-out (run three linters at once, wait, then summarise)
  • Retries with a delay for flaky network calls

Prefer a plain shell script when the work is linear and has no conditionals or parallelism. Prefer a Makefile when the user thinks in targets and dependencies.

Minimum viable workflow

name: Hello
steps:
  - name: greet
    run: echo "hello from flowcmd"

Two required fields at the top (name, steps) and two required fields per step (name, run). Everything else is optional.

The complete schema (every legal field)

name: Workflow Title              # required, free-form string
description: One-line summary.    # optional

steps:                            # required, at least one entry
  - name: step-name               # required. Regex: ^[a-z][a-z0-9_-]*$, max 64 chars, unique
    description: What this does.  # optional
    run: |                        # required. Shell script run via `/bin/sh -c`.
      echo "can be multi-line"    #   Supports {{ steps.X.output }} interpolation.
    when: "{{ steps.prev.exitcode == 0 }}"  # optional. If the rendered value
                                            #   is "", "false", or "0" → skip.
    parallel: true                # optional, default false. Adjacent parallel:true
                                  #   steps form a single concurrent group.
    retry:                        # optional. If present:
      attempts: 3                 #   must be >= 1
      delay: 5s                   #   Go duration: "500ms", "5s", "1m30s"

That is the entire surface. These fields do NOT exist — do not emit them:

shell: · cmd: · env: · working_dir: · cwd: · timeout: · outputs: · continue_on_error: · needs: · depends_on: · on: · vars: · inputs: · matrix: · image: · uses:

If the user wants to set an env var, do it inside run: with standard shell: run: FOO=bar ./script.sh. Step commands inherit the user's environment, so API keys exported in their shell are already visible.

Template syntax

Templates are wrapped in {{ }} and may appear anywhere inside run: or when:.

Reference a step's result:

{{ steps.step-name.output }}     # stdout, whitespace-trimmed
{{ steps.step-name.error }}      # stderr, whitespace-trimmed
{{ steps.step-name.exitcode }}   # integer, as a string ("0", "1", …)

{{ steps[0].output }}            # zero-indexed positional reference
{{ steps[2].exitcode }}

Compare against a quoted literal (only inside when: is this useful):

{{ steps.foo.output != '' }}
{{ steps.bar.exitcode == '0' }}
{{ steps[0].output == "success" }}

Only == and != are supported. The result renders as the string "true" or "false".

What templates CANNOT do:

  • No filters: {{ x | upper }}
  • No env access: {{ env.HOME }} ❌ — use $HOME inside run: instead
  • No arithmetic: {{ a + b }}
  • No boolean ops: {{ a && b }}
  • No forward references: a step can only reference steps that appear earlier in the file. Self-references are rejected.

Validation rules that commonly burn agents

flowcmd validates at parse time. These failures stop execution before any step runs:

  1. Step name must match ^[a-z][a-z0-9_-]*$ (lowercase, digits, -, _, starting with a letter). MyStep, step 1, 1st are all invalid.
  2. Step name must be unique within the workflow.
  3. run is required and non-empty.
  4. Every {{ … }} expression must contain at least one steps.* reference. Bare literals like {{ hello }} are rejected.
  5. A template reference to a step that comes later in the file is rejected with "does not precede this step".
  6. retry.attempts must be ≥ 1.
  7. Workflow needs at least one step.

Always re-read the YAML after writing it and mentally walk through these checks before handing it to the user.

Parallel groups

A run of adjacent steps with parallel: true forms one parallel group. They start simultaneously, and flowcmd waits for every step in the group to finish before moving to the next group.

steps:
  - name: setup       # sequential
    run: ./prep.sh

  - name: lint        # ─┐
    run: ./lint.sh    #  │
    parallel: true    #  │  these three run concurrently
                      #  │  in one group
  - name: test        #  │
    run: ./test.sh    #  │
    parallel: true    #  │
                      #  │
  - name: build       # ─┘
    run: ./build.sh
    parallel: true

  - name: publish     # sequential, runs only after all three above finish
    run: ./publish.sh

Cancellation: if any step in a parallel group fails, the group's shared context is cancelled — the remaining steps in that group receive a cancellation signal and stop. The workflow then fails. Downstream groups do not run.

A single step with parallel: true (no siblings) is legal but pointless — it behaves sequentially.

when semantics

when: is rendered the same way as run:, then the result string is tested:

| Rendered value | Outcome | |---|---| | "" (empty) | skip | | "false" | skip | | "0" | skip | | anything else ("true", "1", "hi", ...) | execute |

Typical patterns:

when: "{{ steps.diff.output != '' }}"         # run only if diff produced output
when: "{{ steps.tests.exitcode == '0' }}"     # run only if tests passed
when: "{{ steps.flag.output }}"               # run if a prior step printed anything

when is evaluated before retry. A step skipped by when never attempts retries.

Retry semantics

retry:
  attempts: 3     # total attempts (NOT additional retries)
  delay: 5s       # wait between attempts; cancelled if the workflow is cancelled

A step is considered successful the first time it returns exit code 0. Non-zero exits and execution errors count as failures; the engine waits delay, then tries again, up to attempts total.

Calling an LLM from a step

flowcmd has no native LLM concept. A step calls a model by running whatever CLI or HTTP client produces text on stdout.

# local runner
- name: classify
  run: ollama run llama3 "Classify this: {{ steps.body.output }}"

# vendor CLI
- name: summarise
  run: claude -p "Summarise: {{ steps.diff.output }}"

# raw HTTP
- name: explain
  run: |
    curl -s https://api.example.com/v1/complete \
      -H "Authorization: Bearer $API_KEY" \
      -d "{\"prompt\": \"Explain: {{ steps.err.output }}\"}" \
      | jq -r '.text'

Full per-backend recipes (Ollama, Claude CLI, llama.cpp server, Hugging Face inference, Unsloth-served, OpenAI, custom) live in the sibling flowcmd-llm-recipes skill and in examples/ next to this file.

Three things that constantly trip agents up when scripting LLM calls inside YAML:

  1. JSON inside YAML inside sh. When interpolating {{ … }} into a JSON body, the step's output may contain quotes or newlines that break your JSON. Pipe through jq -Rs . or write the prompt to a file first:

``yaml run: | printf '%s' "{{ steps.diff.output }}" > /tmp/prompt.txt jq -Rs '{prompt: .}' /tmp/prompt.txt | curl -s … --data-binary @- ``

  1. Multi-line run: with a leading pipe. Use run: | (literal block scalar) for any script that contains newlines, backticks, or significant whitespace.
  1. API keys. They come from the user's shell environment ($OPENAI_API_KEY, $HF_TOKEN, etc.) — never hardcode them in the workflow. If the key isn't set, the step will fail; add a guard step if you want a friendly error.

The workflow you should follow when writing a flowcmd file

  1. Confirm the user's intent and the LLM backend (if any) before generating YAML. If they didn't say, ask.
  2. Sketch the steps in plain English first, then translate to YAML. One step = one shell command or pipeline.
  3. Write the file into the user's local workflow directory: .flowcmd/.yml. If the user wants it globally available across projects, write to ~/.flowcmd/.yml instead.
  4. Validate it: run flowcmd validate .flowcmd/.yml. Fix any reported errors before declaring done.
  5. If flowcmd isn't on the user's PATH, invoke the flowcmd-install skill rather than skipping validation.
  6. Show the user how to run it: flowcmd run (no .yml extension, no path — flowcmd resolves the name against ./.flowcmd/ then ~/.flowcmd/).

Anti-patterns — never emit these

  • env:, working_dir:, timeout:, outputs:, continue_on_error:, needs:, uses: — none exist
  • {{ env.FOO }} — use $FOO inside run:
  • {{ x | upper }}, {{ len(x) }}, any Jinja filter or function
  • ❌ Referencing a later step (or the current step)
  • PascalCase or camelCase step names — must be kebab/snake lowercase
  • ❌ Hardcoding API keys in run:
  • ❌ A bare {{ something }} that doesn't contain a steps.* reference — parser rejects it

References

Deeper detail lives in sibling files (read on demand):

  • references/schema.md — every field, exact Go struct definitions
  • references/template-syntax.md — the template grammar, verbatim from the engine
  • references/validation-rules.md — everything the parser rejects, with error messages
  • references/execution-model.md — parallel groups, cancellation, retry, when

Working examples (every one is validated in CI against the real flowcmd binary):

  • examples/hello.yml — sequential + parallel + templates
  • examples/commit.yml — Claude CLI generates a commit message, when guards the commit
  • examples/ollama.yml — local Ollama summarisation
  • examples/llama-cpp.yml — llama.cpp HTTP server
  • examples/hf-inference.yml — Hugging Face inference API
  • examples/unsloth-served.yml — an OpenAI-compatible server (Unsloth, vLLM, etc.)
  • examples/openai.yml — raw OpenAI chat completions
  • examples/parallel-fanout.yml — concurrent linters with a summarising tail step
  • examples/retry-when.yml — retry on flaky calls plus a conditional follow-up

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.