Matchers and validation

A pipeline is a fragment of Alloy config plus the matchers that decide which collectors receive it. Those two questions — who gets this and is it safe to serve — are answered here, and they apply identically to every pipeline no matter which editor produced it.

Matchers decide who gets it

Matchers use Alertmanager syntax against a collector's keys — cluster="prod-eu-1", role="metrics", role=~"metrics|logs". Every collector has cluster and role; an organisation can opt in to more (below). A pipeline with no matchers reaches nothing. The matcher input suggests the key="value" pairs your collectors actually carry — only keys that matching will evaluate — and a wizard shows how many collectors its matchers select before you commit.

The Pipelines list showing name, source (with a graph link, and a Not following wizard badge on a hand-edited wizard pipeline), matchers and an enabled toggle for each pipeline

The Source column is worth reading: ui, wizard, visual and git pipelines all merge identically, but a git one is owned by its repository and will be overwritten on the next sync. A git pipeline also ignores matchers: it goes to the collector its repository link targets (see GitOps). Beside any source but visual is a graph link to the pipeline’s read-only graph view, and a wizard pipeline edited by hand is badged Not following wizard.

A collector’s role limits what it runs

A match is not the last word. Each role accepts only the signals it is for: a metrics collector takes metrics pipelines, a logs collector takes logs pipelines, a receiver takes metrics, logs and traces, and a singleton takes anything. A pipeline that matches a collector but carries a signal its role does not accept is left out of that collector’s config — not an error, so enabling it still works everywhere else. The served config’s header names every pipeline left out and why, and so does the UI: a pipeline’s page shows Saved pipeline matches N collector(s) with a warning for each collector whose role excludes it, a collector’s Reconciliation tab lists it as Matched, excluded by role, and a wizard’s Review step marks it before you commit.

Matching on labels and agent attributes

Two per-organisation settings, both off by default, let matchers use more than cluster and role. An app admin turns them on under Admin → Organisations → Edit:

  • Match pipelines on collector labels — the labels an org admin sets on a collector's page (team="payments").
  • Match pipelines on agent-reported attributes — the attributes a collector's Alloy reports about itself, from its most recently seen instance. Keys are lowercased, so an agent's Zone matches zone="eu-1".

They are separate because they are trusted differently: anyone holding a collector's token can change what it reports, while only an org admin can set a label. So when the same key comes from both, the admin label wins — and cluster and role always win over either. Some keys can never be set by a label or an attribute: cluster, role, id, os, alloy_version, and anything starting collector. or shepherd.. A key must also be a valid matcher name (letters, digits, underscores and colons, not starting with a digit), so an attribute such as k8s.namespace cannot be matched on.

Turning label matching on can change which collectors your existing pipelines reach. Preview that before you flip it:

shepherd admin audit-matcher-impact --org <org-id>

After that, any label or attribute change that alters which pipelines a collector matches is written to the audit log as pipeline.match.changed and counted in the shepherd_pipeline_match_changes_total metric.

The validation gate

Nothing reaches a collector without passing three stages:

  1. Syntax — the Alloy parser, with errors mapped back to line and column.
  2. Semantics — the real alloy validate binary, against the pinned Alloy version, plus a check of how components are wired to each other against the component schema. alloy validate does not evaluate references between components, so it accepts two mistakes that Alloy then refuses at load. One is a list of targets wrapped in another list (targets = [discovery.kubernetes.pods.targets]; drop the brackets). The other is a single receiver where a list is required (forward_to = prometheus.remote_write.main.receiver; add the brackets). Shepherd refuses both, with the line and column. If the server has no Alloy binary configured, the alloy validate half is skipped and reported as skipped, never as a pass — the editor says Syntax checked — alloy validate skipped; the published images include the binary.
  3. Merge dry-run — the whole merged config for every affected collector. This is the stage that earns its keep: a pipeline can be perfectly valid on its own and still collide with one that is already running, and this catches that before any agent sees it.

The gate proves a config is well formed and fits with the rest; it cannot run it. Alloy can still reject a config at load time for reasons only the running agent knows — a local.file whose path does not exist on that host, or a Kubernetes discovery component on a collector that is not in Kubernetes. The collector then shows FAILED with Alloy’s own error and keeps running its previous config (see Collector status).

A collector’s own page shows the exact merged config it is being served right now, which is the fastest way to confirm a matcher selected what you meant it to.