Case study: Grafana Workflows Blueprint Engine

The system behind Grafana Workflows blueprints: one readable file, for humans and agents, that is an executable automation scenario.

Roles: Product designer, Builder

Create workflow dialog with the Blueprints library filtered to Incidents

The Problem

Every automation starts from a blank canvas.

To route an alert, page someone or chase a post-incident review, a person has to know which trigger to pick, which steps exist, how they connect and what goes into each field, all before anything worked.

That's a lot to hold in your head, even for people who know the product. And the scenarios aren't unique. Teams keep rebuilding the same handful of patterns, each time from an empty editor.

The Blueprints library can solve it. A person picks one from the list, opens it as a real workflow and adapts it. An AI agent uses it as a starting point instead of generating automation from nothing.

The Challenge: Two audiences, one scenario

A blueprint needs to be one scenario that serves both humans and agents.

A person in the library

Needs one or two sentences about what the scenario does. The full graph on a card is noise.

An agent in the assistant

Needs the full picture: why the scenario exists, when it fits, how the steps relate and what to customize.

The obvious answer is a separate description for each audience, plus a catalog config for category, badge, icons and ID. That's a second artifact next to the workflow, one that drifts out of sync, and every new scenario would mean maintaining a catalog on the side.

Choosing the direction

The key trade-off was how complex the engine needed to be.

Explored

A blueprint as a folder

Each blueprint is a folder that bundles the scenario, its tests, metadata and other assets. More room to describe and verify a scenario, at the cost of a heavier engine and a structure of its own next to the node model.

Chosen

A blueprint as a JSON file

A simpler format: one JSON file written in the same language the product uses to describe nodes and what's inside them. Less room for extras, nothing new to learn, and it reads as plain text.

Why this approach

No one score settled it. This direction gave the strongest overall balance across creation, consistency, flexibility, portability and long-term maintenance.

Simplicity of creationOne file per blueprint. No separate catalog configuration to maintain.
Product consistencyBlueprints follow the same semantics as nodes.
Authoring flexibilityThe format supports different ways of creating workflows, so the catalog isn't tied to one authoring path.
Portability and migrationA self-contained definition is easy to move between environments and to migrate as the system evolves.
Maintenance and testingOne shared build pipeline validates definitions, normalizes metadata and catches incomplete entries before they reach users.

Solution

1. One file, two required fields

This is a real blueprint from the repo. The only things an author has to write are the two descriptions at the top, one per audience. Everything else is optional: the steps are added by the assistant or copied from the nodes, and a step gets its own scenario note only where the node's default text isn't enough.

for the person: card copy for the agent: purpose, plus optional per-step notes comes from the nodes or is derived
types/templates/auto-escalation.json{
  "description": "Page the on-call engineer when a critical incident is updated.",
  "agentDescription": "Monitors incident update events and automatically pages the on-call
    engineer when an incident reaches critical severity. Combines an event trigger with a severity
    filter and IRM paging action. Useful for teams that want immediate escalation without manual
    intervention on high-severity incidents.",

  "definition": {
    "name": "Auto-Escalation",                          // → card title
    "enabled": false,
    "startWhen": {
      "matchingRules": [{ "eventNameRegex": ".*.incident.updated$" }],  // → category + badge
      "schedules": []
    },
    "steps": [
      {
        "id": "check-critical",
        "type": "filter",                                  // → icon
        "name": "Is critical?",
        "description": "Gates the workflow to only proceed for critical severity incidents.
          Adjust the condition value to match different severity levels (e.g. 'high') if needed.",
        "inputs": { "condition": "inputs.data.incident.severity == 'critical'" }
      },
      {
        "id": "page-oncall",
        "type": "irm.page-user",                           // → icon
        "name": "Page on-call engineer",
        "description": "Pages the on-call engineer via IRM. Set userID to the engineer's
          email address from your on-call schedule. Customize the message to include relevant incident context.",
        "inputs": {
          "userID": "",
          "incidentID": "${inputs.data.incident.incidentID}",
          "important": "true",
          "message": "Critical incident: ${inputs.data.incident.title}"
        }
      }
    ]
  }
}
The file name becomes the ID. Nothing else about presentation is written by hand.
2. Built from the nodes the product already has

A blueprint is mostly made of nodes Workflows already defines. Every step type lives in the shared node catalog, which we compile for the editor: its name, group, description, inputs with hints and required flags, outputs and icon. A blueprint step only references a type and sets values. Everything else about that step comes from the node.

Node · types/steps.yamlkind: StepType
spec:
  type: irm.page-user
  name: Page User via IRM
  group: IRM
  description: Create an IRM/OnCall escalation
    to page a user…
  params:
    - name: userID
      required: true
      description: Email address of the user
        to page (for example alice@example.com).
    - name: incidentID
      required: false
      …
  outputs:
    - name: escalationID
    - name: state
    …
Defined once for the whole product. The editor, the build and the agent all read it.
Blueprint step · auto-escalation.json{
  "id": "page-oncall",
  "type": "irm.page-user",
  "name": "Page on-call engineer",
  "description": "Pages the on-call
    engineer via IRM. Set userID to
    the engineer's email address from your
    on-call schedule…",
  "inputs": {
    "userID": "",
    "incidentID":
      "${inputs.data.incident.incidentID}",
    "message":
      "Critical incident: ${…title}"
  }
}
The blueprint adds only what's specific to this scenario: wiring, values and why.
What the product showsWhere it comes from
Step names, inputs, hints, required fieldsThe node in the catalogsteps.yaml → compiled step types
IconsThe nodes the scenario uses, in order, unique, up to four on the cardstep type → node icon → integration icon
CategoryThe trigger node: what event it listens to, or a scheduleIncidents · Alerts · Scheduled · General
BadgeTrigger typeEvent · Schedule · Manual
TitleThe workflow's own name
IDFile name, injected at build

So an author fills in two fields: a sentence for the card and a description for the agent. The steps come from the assistant, which writes a good blueprint from a prompt because the nodes already carry most of the meaning, or are copied straight from the nodes. Step descriptions default to the node's own text, and anything else is optional.

This was a product decision more than a technical one. Every field an author types is another way for an entry to be incomplete, and another place where the catalog can say something the workflow doesn't.

3. The build is the quality gate

One command turns the folder of blueprint files into the catalog. It checks that each file has both descriptions and a definition, that the workflow has a name and at least one trigger, strips author-only notes, and injects the ID. Files starting with _, like the authoring template, are skipped.

The authoring loop: write one file, run the build, get a catalog entry.

go/scripts/generate-templates-json/main.go// Validate required top-level fields
for _, field := range []string{
  "description", "agentDescription", "definition",
} {
  if _, ok := raw[field]; !ok {
    fail("%s is missing required field %q", name, field)
  }
}
if def.Name == "" {
  fail("%s definition.name is empty", name)
}
if len(def.StartWhen.MatchingRules) == 0 &&
   len(def.StartWhen.Schedules) == 0 {
  fail("%s definition.startWhen has no triggers", name)
}
// Inject id from filename, strip author notes
raw["id"] = idFrom(name)
delete(raw, "_comment")
delete(raw, "_definitionNote")
terminal$ make generate-templates-json
Error: pir-reminder.json is missing required field "agentDescription"

# add the description, run again
$ make generate-templates-json
Updated plugin/src/features/templates/data/templates.json

$ make generate-templates-json
templates.json is up to date

Left: the validation step, condensed. Right: an illustrative run using the build's real error messages.

The boundary is deliberate. The build guarantees an entry is complete, not that the workflow is correct. A condition that never fires still compiles.

Result

A simple engine on the outside that maintains and tests itself.
Person · library card
Auto-Escalation
Page the on-call engineer when a critical incident is updated.
⚡ Event

One sentence from description. Everything else derived.

Agent · context
{
    "agentDescription": "Monitors incident
      update events and pages…",
    "steps": [{
      "id": "page-oncall",
      "description": "Set
        userID to the
        engineer's email…",
      "inputs": { "userID": "" }
    }]
  }

The agent knows exactly what the empty field is for.

Simple to useWrite two descriptions, run one command. No UI code, no catalog config, no new format to learn.
Self-maintainingSteps, hints, icons and categories come from the nodes and the trigger. When a node changes, every blueprint that uses it picks up the change. There is no catalog to keep in sync.
Self-testingThe build rejects incomplete files, and the same test suite runs over every blueprint in the catalog. A new blueprint is covered the moment it's added, with no tests to write.
Grafana Workflows blueprint opened as a ready workflow scenario with steps to fill in
The blueprints are fully ready to function as workflows; the user simply needs to add details to each step, and the flow is ready to be launched.

What's next and criteria of success

Blueprints as building blocks for AI

A blueprint is a structured workflow with explicit intent, down to each step. The assistant on the home screen can start from one and adapt it to a request instead of generating automation from scratch. The same decisions that make the gallery work turn the catalog from a set of examples into reusable patterns for AI-assisted creation.

Grafana Assistant helping build a workflow with an Ask Assistant step
Blueprints give the assistant a structured scenario to start from — then Ask Assistant can fill the remaining steps in context.
How we plan to measure it
useBlueprint adoptionShare of workflow runs that come from blueprints.
reuseAuthoring leverageWorkflows created from a blueprint vs. from a blank canvas.
AIAgent effectivenessShare of blueprint-grounded agent suggestions that people accept.

Known tech debt

The team already sees where the engine needs to grow next, and what the next steps are.

1. Scenario freshnessA blueprint can be valid when it ships and break later, for example when a node it uses is deprecated. The next step is a marker that flags a blueprint as outdated or broken, so it gets reviewed before people run into it.
2. Granular accessAll blueprints live in one folder today. Role-based access may need a richer structure, so different teams or roles can see and publish different sets of blueprints.

Next case study

Case study: Unified Agent Memory

From isolated agentic sessions and tools to one shared memory hub.

Product designer, Builder

Case study: Unified Agent Memory