Walk a flow
Mermaid decision trees with per-node instructions — the agent walks the diagram, you follow along.
The idea
Recipes and skills both encode "how to do X" but in different
shapes — a recipe is a parameterised execution
(/recipe charm-cos-add charm_name=ntfy), a skill is a
knowledge bundle the agent reads when context demands. Flows fill
the gap between them: a visual decision tree the
agent walks step by step, with branch announcements you can
follow as the conversation unfolds.
A flow is a markdown file with type: flow
frontmatter and a single mermaid fenced block. The
Mermaid diagram captures the choices; per-node
%% <id>: <annotation> lines tell the
agent what to do at each step. Cantrip validates the diagram up
front (so a typo never reaches the model) and feeds it to the
agent's primary conversation loop. The agent walks the tree and
emits BRANCH: <label> lines at decision nodes
so the user can follow its reasoning.
Bundled flows
Three built-in flows ship with Cantrip and are available in every session:
/flow charm-cos-enable- Walk through adding COS observability to an existing charm. Decision points: workload exposes metrics? writes logs? upstream dashboards? want tracing? Each branch leads to a concrete charm-library-and-relation step or a refusal that asks for author input.
/flow charm-reactive-to-ops- Migrate a reactive charm to the Operator Framework. Decision points: out-of-tree dependency without an ops equivalent? actions to preserve? relation interfaces stay or change? Pebble plan needed? Composes with the recipe of the same name — flows describe the decision tree, recipes describe the parameterised execution.
/flow charm-upgrade-ladder- Walk through the SUPPORTED → DEPRECATED → REMOVED ladder for breaking changes. Decision points: high operator impact? already on N-1? operators on N-2? rollback path safe? The flow refuses to ship breaking changes without a documented rollback path.
Bare /flow lists the catalogue;
/flow <name> --help shows the per-flow
detail page (entry node, decision nodes with branches,
terminal nodes).
Where Cantrip looks
Three roots, in precedence order — later wins on name collision so you can override a built-in just by dropping a same-named markdown file into one of the writable scopes:
- Bundled. Ships with Cantrip itself.
~/.config/cantrip/flows/*.md— your personal flows, available across every charm.<charm>/.cantrip-flows/*.md— per-charm flows, committed alongside the code so every team member gets them automatically.
The repo path is a sibling of the SQLite session file at
<charm>/.cantrip. Cantrip cannot use
<charm>/.cantrip/flows/ because a single path
can't be both a regular file and a directory.
Schema
A flow file has three parts:
1. Frontmatter
---
type: flow
description: One-line summary; surfaced in `/flow` and `/help`.
name: optional — must match the filename stem
---
type— required, must be the literalflow- The discriminator that distinguishes a flow file from a skill. Mismatch raises on load.
description— required, non-empty string- One-line summary; surfaced in
/flowand/help. name— optional, must match the filename stem when present- The lowercased filename stem (without
.md) is the flow name regardless. Settingnamein frontmatter is a redundancy check.
Unknown frontmatter keys raise — no silent typo absorption.
2. The Mermaid diagram
Exactly one fenced mermaid block. The parser
accepts a deliberately narrow subset of Mermaid:
```mermaid
flowchart TD
survey[Inspect charm]
decide{Add metrics?}
add[Add prometheus_scrape]
finish(Done)
survey --> decide
decide -->|yes| add
decide -->|no| finish
add --> finish
```
Three node shapes encode three semantic kinds:
id[label]— action: the agent does something. Single outgoing edge.id{label}— decision: the agent picks a branch. Two or more outgoing edges, each with a label.id(label)— terminal: the flow ends. Multiple terminals are allowed (e.g., success / abort).
Edges are id --> id or
id -->|label| id. Decision-node edges
must carry labels and labels must be unique within a
node's outgoing set.
3. Per-node annotations
%% survey: Read charmcraft.yaml + src/charm.py.
%% decide: Pick yes if the workload exposes /metrics, no otherwise.
%% add: Add the prometheus_scrape provider on metrics-endpoint.
%% finish: Done.
Every node referenced by the diagram needs an annotation
— the agent reads them as the per-step instructions.
Annotations live inside the Mermaid block (Mermaid treats
%% as a comment, so a renderer still draws the
diagram unchanged).
Validation
Cantrip refuses to load a flow that violates any of these rules:
- Frontmatter must declare
type: flow. - Filename stem must match
[a-z0-9][a-z0-9_-]*(the lowercased stem becomes the flow name). - The body must contain exactly one
mermaidfenced block. - The diagram must start with a
flowchartheader (TD, LR, RL, TB, or BT). - Every node needs a
%% <id>: ...annotation. - Every edge must name declared nodes.
- Exactly one entry node — a graph with multiple roots, or a cycle with no clear start, is refused.
- Decision nodes must have ≥2 outgoing edges, each with a label, and labels must be unique within the node's outgoing set.
- Action and terminal nodes can have at most one outgoing
edge — multiple branches require a decision node
(
id{label}).
Validation errors surface with the path and (where relevant) line number, so a typo lands as a clear failure rather than a mysterious render. Malformed files in a discovery directory log a warning and are skipped — one bad flow doesn't block the rest of the catalogue.
Invocation
Two forms reach the same dispatcher:
> /flow charm-cos-enable
> /flow:charm-cos-enable
Cantrip:
- Looks up the flow in the registry.
- Renders a structured prompt with the diagram fenced for
a Mermaid renderer, the per-node annotations as a numbered
list, and walking instructions
(“Start at
survey; emitBRANCH: <label>at decision nodes”). - Hands the prompt to the agent's primary conversation loop.
- Returns the agent's reply — you see the walk in the chat as it unfolds.
Flows take no arguments. /flow charm-cos-enable
metric=foo refuses with a pointer to /recipe
— if you want a parameterised execution, that's the
neighbouring tool.
Flows vs recipes vs skills
Three different shapes for "how to do X":
- Skill
- Knowledge the agent reads when context demands. Authoring a skill writes what the agent should know about a topic. See Add a custom skill.
- Flow
- A visual decision tree the agent walks step by step. Authoring a flow describes which branch of a decision tree applies and what to do at each node.
- Recipe
- A parameterised, retryable execution. Authoring a recipe captures do this exact thing, with these inputs, until these checks pass. See Run a recipe.
The three are complementary, not alternatives. The
charm-reactive-to-ops migration ships as both a
flow (the decision tree the user reads before committing) and a
recipe (the parameterised execution the agent runs). The flow's
%% port_pebble: Translate the reactive layer's service-
management code into a Pebble plan annotation tells the
agent what to do; the recipe's
parameters: — name: target_ops_version binds
the inputs.
Related
- Run a recipe — parameterised retryable execution alongside flows.
- Add a custom skill — knowledge bundles the agent reads when context demands it.
- Define custom slash commands — lighter-weight prompt templates with positional / file / shell expansions.