# Custom recommendation policies

> Write your own recommendation from metrics. Pick the resources to watch, combine up to eight metric signals, choose what to recommend, and ZopNight raises and resolves the finding for you.

Source: https://zop.dev/docs/zopnight/policies/custom-recommendations

---

The built-in [rule catalogue](https://zop.dev/docs/zopnight/optimization/recommendation-rules) covers the patterns we see in every estate. When you want a threshold that's specific to yours, write it yourself. A **custom recommendation policy** watches the resources you choose on the metrics you choose. When every signal holds, ZopNight raises a recommendation; when the signals clear, it resolves it.

![Custom recommendations list with template chips such as Idle databases and Over-provisioned VMs, and policies showing what each watches, its signals, decision, outcome, and an on/off status switch](https://storage.googleapis.com/zopdev-blog-resources/1/files/originals/20261020/3ca48b64-9dbb-4a60-852a-bf16068dccfb-policiescustomrecommendations.png)

*Governance → Policies → Recommendation: policies with their scope, signals, decision, outcome, and status, plus starter templates.*

You manage them at **Governance → Policies → Recommendation**. Their findings appear on the **Recommendations** page when you switch the **System / Custom** toggle to **Custom**.

## Before you start

- **Permission to manage policies.** The built-in **Editor** and **Admin** roles have it; **Viewer** can only look. See [Scopes](https://zop.dev/docs/zopnight/concepts/scopes).
- **Metrics.** Signals read the metrics ZopNight already collects, so the cloud account needs metrics read access (CloudWatch, Azure Monitor, or Cloud Monitoring). See [Cloud permissions](https://zop.dev/docs/zopnight/cloud-setup/permissions).

## Start from a template

Under **Templates** on the list page there are five starting points (you see them only if you can create policies). Each one fills in the whole policy, ready to adjust:

| Template | Watches | Signals | Recommends |
|---|---|---|---|
| **Idle databases** | RDS tagged `env=uat` | average CPU below 5% and max connections at 0, over 7 days | Rightsize |
| **Over-provisioned VMs** | EC2 tagged `env=prod` | average CPU below 40%, average memory below 50%, and max CPU below 80%, over 30 days | Rightsize |
| **Quiet dev servers** | EC2 tagged `env=dev` | average CPU below 10% and average network out below 1 MB/s, over 14 days | Schedule |
| **Idle load balancers** | Load balancers | average network out below 0.5 MB/s, over 14 days | Custom ("needs-review") |
| **Over-provisioned databases** | RDS tagged `env=prod` | average CPU below 30%, average connections below 10, and max CPU below 80%, over 30 days | Rightsize |

## Create a custom recommendation

Click **+ Create**, or pick a template, to open the **New custom recommendation** wizard. It has five steps.

**Start**

  **Name** the policy (required, up to 120 characters) and optionally describe what it should catch.

  Prefer to describe it in words? **Fill with AI** gives you a prompt to paste into any AI assistant. Paste the JSON it returns and click **Fill from JSON** to fill in the rest of the wizard.

**Scope: which resources to watch**

  Choose **Whole organisation** or **One cloud account** (then pick the account). To limit it further, click **+ Narrow with a condition**. All conditions must match:

  - **Resource types**
  - **Cloud provider** (AWS, GCP, Azure)
  - **Name** (contains or equals)
  - **Database engine**
  - **Purchase / lifecycle** (on-demand, spot, low priority, preemptible, scheduled, capacity block)
  - **Tag** key and value

  With no conditions, the policy watches every resource in scope, and the wizard warns you.

**Signals: what to watch**

  A signal reads like a sentence: *average CPU utilization is below 5% over 7 days*. Add up to **8**. They combine with **AND**: every signal must hold for the policy to fire.

  - **Aggregation**: average, max, min, p95, or p99. Max and min use the single highest or lowest hour in the window; p95 and p99 ignore the very highest spikes.
  - **Comparison**: is below, is above, or equals (equals works only with max and min).
  - **Window**: 1 to 90 whole days. Each cloud keeps a different amount of history: 90 days on AWS, 60 on Azure, 42 on GCP. A cloud's resources show as not having enough data when your window is longer than that cloud keeps. **Validate** warns you about this only if you've limited the policy to one cloud account or to certain providers; with a whole-organisation scope it won't warn (for example, a 60-day window still gets no data from GCP). If the window is longer than every cloud keeps, **Validate** stops you.

  Metrics your scope can't collect are marked **(unavailable)**.

**Outcome: what to recommend**

  | Outcome | Files under | Savings shown |
  |---|---|---|
  | **Idle**: the resource looks unused, stop it | Idle | Cost of the hours it sits unused |
  | **Schedule**: usage is predictable, schedule it | Schedule | Cost of the hours it sits unused |
  | **Rightsize**: the resource is over-provisioned | Rightsizing | The price difference to one size down |
  | **Retire**: orphaned or unattached, remove it | Orphan | The whole monthly bill |
  | **Custom**: name your own category (required, up to 40 characters) | Your category | None (a flag for review) |

  A Rightsize outcome can't use an **is above** signal.

**Review**

  Check the summary, then click **Validate**. ZopNight checks the metrics are available for your scope and the windows fit each cloud's history. Once it passes, click **Create policy** (or **Save policy** when editing).

New findings are severity **medium**. Templates, Fill with AI, the API, and the MCP tools can set a different severity.

## When it runs

- **New findings** can appear whenever ZopNight refreshes a resource, and across all your resources straight after you create, edit, or turn on a policy. A finding is raised **once**, when its signals first all hold.
- **Old findings are closed** when a full check finds the signals have cleared or the resource is no longer in scope. That full check runs when you create, edit, or turn a policy on or off. A finding is also closed if its resource is deleted.
- **Missing data never fires.** A signal with no data counts as not holding.

**Note**

A refresh can raise new findings but doesn't close old ones. If a resource has recovered and its finding is still open, turn the policy off and on again. That closes its findings and raises again only the ones that still hold.

A custom finding then behaves like a built-in one, with the same drawer and the same actions. See [Recommendations](https://zop.dev/docs/zopnight/optimization/recommendations).

## How savings are kept honest

Savings on a custom finding follow the same rules as the built-in ones. When ZopNight can't back up a dollar figure, it shows the finding as **advisory** with $0 savings and tells you why. This happens when:

- **The metrics are stale**: the newest data point is more than 48 hours old.
- **There isn't enough data yet**: fewer days of data than the signal's window, or fewer than 7 days for a max, min, p95, or p99 signal.
- **It isn't safe to shrink or stop**: CPU peaks reach 80% or more even though the average is low, or memory use averages 50% or more.
- **No quiet period has been seen yet**: Idle and Schedule need ZopNight to have seen when the resource is actually unused.
- **The resource holds data**: Idle, Schedule, and Retire on a database or other stateful resource are always advisory. Rightsize is fine, which is why the database templates use it.
- **The cost is unknown**: the resource's cost is $0 or ZopNight couldn't determine it ("We couldn't determine this resource's cost"), for any outcome.
- **There's nothing smaller**: a Rightsize where no smaller size is available, or where the current or smaller size can't be priced.

## Manage policies

The list shows each policy's name, what it watches, its signals, the decision (for example `A AND B`), the outcome, and a **Status** switch. Click a row to see the full detail.

- **Turn a policy off** with its switch. Its open findings are closed; turn it back on and they're raised again.
- **Edit** from the row menu. Saving re-checks your whole estate.
- **Delete** from the row menu. This can't be undone, and the open findings it raised are closed.

## From the API or an AI assistant

Developers can manage custom recommendation policies through the ZopNight API, where they're called `watch` policies. If a policy was created there with a more complex scope, editing it in the app replaces that scope, and the wizard warns you first.

From an AI assistant, use the [MCP server](https://zop.dev/docs/zopnight/integrations/mcp) tools `list_watch_policies`, `validate_watch_policy`, `create_watch_policy`, and `update_watch_policy`.
