Protections — cooldown, backoff, limits

Last updated: May 19, 2026

Protections — cooldown, backoff, limits

Wevion enforces 5 protections to prevent rules from causing harm: cooldown (per-entity time-between-acts), max executions per day, budget change limits, pending-action check, and a circuit breaker that auto-pauses misbehaving rules. Plus exponential backoff on failures.

Who is this for

Anyone designing rules that change live spend. Protections are how you sleep at night.

Why protections matter

A rule without protections can:

  • Pause the same adset repeatedly as transient metric blips trigger conditions

  • Increase budget 10× in one day chasing a noisy ROAS spike

  • Cascade errors when the platform API is down, locking the rule in failure mode

Each protection guards a specific failure mode.

Protection 1: cooldown_minutes

After acting on an entity, the rule won't act on that entity again for cooldown_minutes.

  • Default: 360 (6 hours)

  • Minimum for destructive actions (pause, decrease_budget_pct, relaunch): 60 minutes

  • Per-entity scope: cooldown is per-rule-per-entity; the same rule can still act on OTHER entities during cooldown

Use case: prevents thrashing when a metric oscillates around the threshold. Adset's CPA crosses 30 → pause. 5 minutes later metric refreshes and shows 29.50 → without cooldown, would activate. With cooldown, action holds for 6 hours.

Protection 2: max_executions_per_day

Hard cap on total executions across all entities, per rule, per UTC day.

  • Default: 3

  • Hard cap: 50 (cannot raise above this)

Use case: a scale-winner rule capped at 3/day prevents runaway scaling. If 5 adsets all hit the ROAS threshold today, only the first 3 are acted; others are skipped with daily_cap_reached reason.

Protection 3: budget_change_limit_pct

Caps the % delta of any single budget action.

Example: budget_change_limit_pct: 50 + rule action increase_budget_pct: 100 → action capped at +50%. Recorded with note that limit was applied.

Use case: hard safety on increase_budget_pct rules. Even if rule says +100%, never apply more than +50% in one shot.

Protection 4: budget_daily_cap

Cumulative cap on total budget delta per entity per UTC day.

Example: budget_daily_cap: 100 (USD) + adset receives 4 separate +20% increases summing to $120 → 4th action capped to keep total at $100.

Use case: layered safety — even if max_executions_per_day allows 3 increases, total $ delta is bounded.

Protection 5: pending check (30 min)

Before executing, the worker checks: is there already a pending action for this rule + entity in the last 30 minutes? If yes, skip + record reason.

Prevents duplicate actions when:

  • Two consecutive cron ticks both enqueue the rule (rare edge case)

  • The previous action is still in flight (SQS retry, slow platform API)

Not user-configurable; always active.

Circuit breaker (auto-pause)

If a rule's error rate goes critically high, it auto-pauses to status: error.

Trigger conditions (both must hold):

  • Error rate ≥ 50% over recent executions

  • At least 20 total errors in the settle window

When tripped:

  • Rule status flips to error

  • auto_paused_at timestamp set

  • auto_pause_reason field captures cause

  • cb_reset_after timestamp marks when auto-resume eligibility starts

  • Notification sent (if notify_on_execution + notify_channels set)

To recover: review last_error, fix the upstream issue (e.g. token expired, platform API change), then manually toggle status back to active. Or wait until cb_reset_after for automatic resume eligibility.

Backoff (per-execution retries)

For each execution attempt that fails (API timeout, transient error):

  • Max 5 attempts over 24 hours

  • Exponential backoff between attempts

  • After 5 failures: marked entities_errored with last error message

This is per-entity-per-execution, distinct from circuit breaker (which is per-rule across executions).

Protection priority order

When multiple protections could apply, they're checked in this order:

  1. Rule status (paused / error → skip)

  2. Schedule (not due yet → skip)

  3. Daily cap (max_executions_per_day reached → skip)

  4. Pending check (in-flight action → skip)

  5. Cooldown (within window → skip)

  6. Budget caps (apply on execute)

  7. Action attempted; on failure → backoff

Each skip is recorded in entities_skipped with the specific reason.

Visible in execution history

/rules/:id → execution timeline shows:

  • entities_skipped with breakdown per reason: cooldown_active, daily_cap_reached, pending_action_exists, budget_cap_reached, entity_state_conflict

  • entities_errored with last error message + attempt count

Useful for tuning: if everything is skipped for daily_cap_reached, raise the cap or accept the throttle.

Recommended defaults

For new rules:

  • Cooldown: 360 min (6h) — default. Drop to 60 only for explicit reasons.

  • Max executions/day: 3 (default). Raise carefully.

  • Budget change limit: 30% (single-action). 50%+ only for proven rules.

  • Budget daily cap: 50-100% of entity's average daily budget. Limits cumulative drift.

Common mistakes

  • Cooldown too short on pause action: rule re-pauses + re-activates on metric noise. Default 360 min exists for this reason.

  • No daily cap on increase_budget_pct: rule scales 3× in one day chasing noisy ROAS spike. Set budget_daily_cap always.

  • Resuming error-status rule without fixing root cause: immediately re-trips circuit breaker. Review last_error first.

  • Setting max_executions_per_day: 50: hard cap; rule can fire 50 times in a day. Almost always overkill — start at 3.

Related