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:

  1. Bundled. Ships with Cantrip itself.
  2. ~/.config/cantrip/flows/*.md — your personal flows, available across every charm.
  3. <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 literal flow
The discriminator that distinguishes a flow file from a skill. Mismatch raises on load.
description — required, non-empty string
One-line summary; surfaced in /flow and /help.
name — optional, must match the filename stem when present
The lowercased filename stem (without .md) is the flow name regardless. Setting name in 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:

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:

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:

  1. Looks up the flow in the registry.
  2. 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; emit BRANCH: <label> at decision nodes”).
  3. Hands the prompt to the agent's primary conversation loop.
  4. 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.