---
name: calibrate-cost-cap-and-roas-goals
description: >-
  Turns a chosen COST_CAP, LOWEST_COST_WITH_BID_CAP, or LOWEST_COST_WITH_MIN_ROAS strategy into an actual
  number, calibrated from real breakeven unit economics rather than a wish. The cost or bid cap starts at or
  near the recent trailing average cost-per-result on lowest-cost bidding, then tightens gradually toward
  the true breakeven ceiling in small steps - never launched at the ceiling itself. The ROAS goal for
  min_roas is derived from margin/breakeven ROAS and expressed as a multiplier (2.0 = 200%), with the exact
  unit the tool expects verified before writing it. A cap set too tight doesn't get more efficient, it just
  stops delivering, and the only fix is to loosen it. Reach for it once select-bid-strategy has landed on a
  capped or goaled strategy and the number is still open, or when a cap is starving delivery. It ends at a
  calibrated cap or goal that still delivers - the strategy choice belongs to select-bid-strategy, and the
  VALUE prerequisites to set-up-value-optimization.
---
# Calibrate Cost Cap and ROAS Goals

## Purpose
A cost cap or ROAS goal is only useful if it reflects what the account can actually afford, measured from
real unit economics and real recent delivery, not an aspirational number chosen to look good in a deck.
This skill turns breakeven math and trailing performance into a specific `bid_amount` or ROAS multiplier,
sets it conservatively, and defines how to tighten it over time without strangling delivery.

## When to run
- `select-bid-strategy` landed on `COST_CAP`, `LOWEST_COST_WITH_BID_CAP`, or `LOWEST_COST_WITH_MIN_ROAS`
  and the number itself still needs setting.
- An existing cap or goal is under-delivering (spend stalled well below budget) and needs recalibrating.
- Unit economics changed (new margin, new breakeven) and the live cap or goal no longer reflects them.
- Tightening a cap gradually as an ad set proves it can hold a lower cost.

## When NOT to run
- The strategy itself isn't chosen yet → `select-bid-strategy`.
- `optimization_goal` isn't `VALUE` yet but a ROAS goal is wanted → `set-up-value-optimization` first;
  `LOWEST_COST_WITH_MIN_ROAS` with no value signal has nothing to optimize against.
- The underlying breakeven cost-per-result or ROAS hasn't been worked out from real margin data → get
  that settled (HUMAN STEP with finance/the account owner) before running this.
- The ad set isn't reliably exiting learning yet → `manage-the-learning-phase`; a cap calibrated against
  learning-phase noise is calibrated against nothing real.

## Prerequisites
- Real breakeven cost-per-result (max affordable CPA) and/or breakeven ROAS, derived from actual margin, not guessed.
- At least 1-2 weeks of stable delivery history on the current strategy to read a trailing average from,
  via `mads_run_insights`.
- `bid_strategy` already chosen (`select-bid-strategy`); for `MIN_ROAS`, `optimization_goal=VALUE`
  already live (`set-up-value-optimization`) with value reliably passing.

## Procedure
1. **Get the real ceiling.** Breakeven cost-per-result, or breakeven ROAS, from actual margin data, price minus cost of goods and fulfillment minus target margin, expressed as the max affordable CPA (or
   min affordable ROAS). **HUMAN STEP** if this hasn't been worked out yet, do not proceed on a guess.
2. **Pull the trailing average.** `mads_run_insights` over the last 1-2+ stable weeks, excluding
   any learning-phase days: average cost-per-result (for `COST_CAP` / `BID_CAP`) or average ROAS (for
   `MIN_ROAS`) per ad set.
3. **Set the starting cap or goal near the trailing average, not the breakeven ceiling.** `COST_CAP`
   `bid_amount` ≈ the recent average cost-per-result, or a touch above it, never launch below what the
   ad set has already achieved. `MIN_ROAS` ≈ at or slightly below the recent average ROAS. Breakeven from
   step 1 is the outer limit to tighten toward over time, not the day-one setting.
4. **Convert to the tool's units.** `bid_amount` is money in the currency's minor unit, a $12.00 cost
   cap is `bid_amount=1200`. The ROAS goal for `MIN_ROAS` is a multiplier (e.g., 2.0 = 200% ROAS), confirm whether `mads_update_adset` / `mads_update_campaign` expects a decimal multiplier or a
   whole-percent value before writing it, and state both the human number and the raw value sent.
5. **Preview, diff, approve, commit.** Entity, field (`bid_amount` or the ROAS goal field), from → to in
   human terms ($12.00 CPA cap, or 2.0x ROAS) and raw units, expected impact, and the learning-reset note
   if this is a live ad set. **HUMAN STEP.** Get explicit approval before committing. Commit via
   `mads_update_adset` or `mads_update_campaign`.
6. **Watch delivery against the cap or goal.** For the following 1-2 weeks, excluding the immediate
   post-change learning window, pull `mads_run_insights`: is spend pacing near budget, or stalling
   well under it? Stalled or throttled means the cap is too tight.
7. **Tighten gradually, only from a delivering baseline.** Once delivery is healthy and stable at the
   current cap or goal for a full cycle, move it 10-15% toward the breakeven ceiling, never in one jump
   to the ceiling itself. Re-preview, re-approve, and re-commit each step exactly as in step 5.
8. **Loosen immediately if starved.** If a cap or goal change causes spend to stall well below budget,
   loosen it back toward, or past, the prior working value rather than waiting it out. A cap that
   prevents delivery cannot be optimized, it can only be relaxed.
9. **Re-validate on a cadence.** Whenever unit economics change, or roughly quarterly, redo steps 1-2 and
   confirm the live cap or goal still reflects reality.

## Decision rules
- IF no trailing delivery history exists yet at the current strategy THEN don't calibrate a cap yet, that is `select-bid-strategy`'s job, keeping the entity on `LOWEST_COST_WITHOUT_CAP` until data exists.
- IF the proposed cap or goal is tighter than the recent trailing average THEN expect under-delivery, start at or looser than the average, not at the aspirational breakeven number.
- IF spend stalls well below the daily budget after a cap or goal change THEN loosen it, this is the
  single most reliable diagnostic that the number is too tight.
- IF delivery has been healthy and stable for a full post-change cycle THEN tighten by 10-15% toward
  breakeven, one step at a time, never straight to the ceiling.
- IF the ROAS goal's unit is ambiguous (multiplier vs percent) THEN verify against the tool's schema
  before writing, a 10x unit error, sending 2.0 as 200 or the reverse, either guts delivery or removes
  the goal entirely.
- IF unit economics (margin, breakeven) change THEN recalibrate, a cap or goal calculated against stale
  margins optimizes for the wrong number even while it looks like it's "working."
- **Done means:** a `bid_amount` in minor units, or a ROAS multiplier, set at or looser than recent
  trailing delivery, with the breakeven ceiling documented as the tightening target, and a confirmed read
  that delivery is not starved at the current setting.

## Common failure modes
- **Calibrating from a wish, not a margin sheet.** A cap set to "what we'd like to pay" rather than real
  breakeven either throttles delivery (too tight) or quietly bleeds margin (too loose).
- **Launching at the breakeven ceiling.** That's the tightest defensible number, not a starting point, day-one caps belong near the trailing average.
- **Tightening a cap that's already causing under-delivery.** The correct response to stalled spend is
  always looser, never tighter.
- **Jumping straight to the ceiling in one step** instead of 10-15% moves, shocks the auction and often
  reverses whatever stability had been achieved.
- **Guessing the ROAS goal's unit.** Sending a percent where a multiplier is expected, or the reverse,
  silently miscalibrates the entire strategy.

## Related skills
- Before: `select-bid-strategy` (chooses which of `COST_CAP` / `BID_CAP` / `MIN_ROAS` applies),
  `set-up-value-optimization` (`MIN_ROAS` needs `VALUE` optimization live first).
- Related: `manage-the-learning-phase` (cap/goal changes on live ad sets reset learning),
  `set-minimum-viable-budget` (budget floor is sized separately but interacts with cap headroom).
