> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sidecardata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent skill for monitors as code

> Teach Claude Code, Cursor, or Codex to write valid Sidecar monitor definitions

Give your coding agent this skill so it writes Sidecar monitor definitions that follow the schema and the rules the sync enforces. Valid YAML can still be blocked at sync time for reasons outside the repository, such as a model with no catalogued table or an existing UI monitor that needs adoption, and the sync result tells you which.

## Install

<Tabs>
  <Tab title="Claude Code">
    Save the file as `.claude/skills/monitors-as-code/SKILL.md` in your dbt repository. Claude Code loads it when a task mentions monitors; `/monitors-as-code` invokes it directly.
  </Tab>

  <Tab title="Codex">
    Save the file as `.agents/skills/monitors-as-code/SKILL.md` in your dbt repository so Codex loads it on demand. As a fallback, paste the body into `AGENTS.md` under a **Sidecar monitors** heading.
  </Tab>

  <Tab title="Cursor">
    Save the file as `.cursor/rules/monitors-as-code.mdc`, replacing the frontmatter with:

    ```yaml theme={null}
    ---
    description: Writing Sidecar monitor definitions in dbt YAML
    globs: ["models/**/*.yml", "sidecar/monitors.yml"]
    alwaysApply: false
    ---
    ```
  </Tab>
</Tabs>

## SKILL.md

````markdown theme={null}
---
name: monitors-as-code
description: Write and edit Sidecar monitor definitions in a dbt project, in a model's config.meta.sidecar block or in sidecar/monitors.yml. Use when asked to add, change, rename, or remove a Sidecar monitor, data quality check, volume or freshness check, or alert on a dbt model.
---

# Sidecar monitors as code

Sidecar creates monitors from definitions in the dbt project. Two locations, one schema:

1. Model YAML under `config.meta.sidecar` (never top-level `meta`). Sidecar sees changes after the production dbt run and its next catalog refresh.
2. `sidecar/monitors.yml` at the repo root. Sidecar reads the default branch on its next sync, so merged changes are picked up without a dbt run. Use it for many models at once or for package models whose YAML is not in the repo.

## Workflow

1. Search both locations for existing definitions on the model (`grep -n sidecar models/ sidecar/monitors.yml`).
2. Keep existing keys and all unrelated YAML untouched. Edit the block that already defines the monitor rather than adding a second one.
3. Verify the model and column names against the model SQL or its YAML columns before writing SQL.
4. Validate the result parses as YAML and sits under `config → meta → sidecar` for model blocks.

## Identity

A monitor is identified by `<model>::<key>`. Changing `name` keeps the monitor. Changing the key normally creates a new monitor and retires the old one, with one exception: Sidecar detects a rename when the new key is on the same model and, for volume and freshness, binds the same table, or, for custom, has unchanged resolved SQL and is the only candidate. Never define the same key for a model in both locations; both are blocked.

## Model block

```yaml
models:
  - name: fct_orders
    config:
      meta:
        sidecar:
          version: 1
          defaults:                 # optional, applies to all monitors below
            schedule: { cron: "0 * * * *" }
          monitors:
            orders_volume:          # key: ^[a-z][a-z0-9_]{0,63}$
              type: volume
              sensitivity: medium
            orders_freshness:
              type: freshness
              freshness_threshold: { hours: 6 }
            orders_negative_revenue:
              type: custom
              name: Orders with negative revenue
              sql: |
                SELECT order_id FROM [[ this ]] WHERE net_revenue < 0
              fail_when: { operator: gt, value: 0 }
              investigation_instructions: Check recent pricing and refund changes.
```

## Repository file

```yaml
version: 1
defaults:
  schedule: { cron: "0 */6 * * *" }
models:
  - name: fct_orders                # or unique_id: model.<project>.fct_orders
    defaults:
      schedule: { cron: "30 * * * *" }
    monitors:
      orders_volume: { type: volume, sensitivity: high }
```

Defaults apply file → entry → monitor. Use `unique_id` for ambiguous or package models.

## Types

**custom**: `sql` (required) is one SELECT returning the offending rows; Sidecar counts them. `fail_when` (required): `operator` in `gt gte lt lte eq neq`, integer `value`; fails when `count <operator> value`. Enforced: a single statement, no DDL or DML, no Jinja or dbt macros, warehouse SQL dialect, and any referenced model must be in the warehouse the Sidecar source runs custom SQL on. Recommended: reference tables as `[[ this ]]` or `[[ ref('model') ]]` rather than literal names, and omit the trailing semicolon.

**volume**: `sensitivity` in `low medium high` (default `medium`). Model must be materialized as a table.

**freshness**: `freshness_threshold` (required) with `days` and/or `hours`, at least one above zero. `freshness_mode`: `row_count_change` (default) or `last_updated` (not on Redshift). Table materialization required.

## Common fields

- `name`: display name; defaults to the key.
- `schedule`: `{cron: "..."}` (15 min minimum), `{mode: interval, every_hours: 1|2|3|4|6|8|12, days_of_week: [...]}` (Monday = 0), or `{mode: specific_hours, hours: [...], days_of_week: [...]}`. Integers only; `true` and `1.0` are rejected. Every monitor needs a schedule from itself, its defaults, or the Sidecar source.
- `notification_destinations`: a list. Omit for source defaults.
  ```yaml
  notification_destinations:
    - { type: slack, channel_id: C0123456789, channel_name: data-alerts }
    - { type: email, email: data-team@yourcompany.com }   # company domain only
  ```
- `investigation_instructions`: short runbook for the Reliability agent on failure.

No other fields. Unknown fields block the definition. There is no `enabled`; pausing happens in the Sidecar UI.

## Edits

- Threshold, SQL, schedule, name: edit in place; history is kept.
- Remove: delete the block; the monitor is retired, runs are kept.
- Type or table change: blocked. Delete and add under a new key.

## Checklist

Keys valid and unique per model. `version: 1` present. References are `[[ this ]]` / `[[ ref() ]]` and columns exist. Each monitor resolves a schedule. Destinations are a list. YAML parses.

After merge the user runs **Observability → Monitors → Sync from dbt**. A blocked definition's error code names the rule above that was broken; errors such as `table_not_found` or `adoption_required` come from the Sidecar side, not the YAML.
````

Keep your copy in step with the [Monitors as code](/products/observability/monitors-as-code) reference.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.