MGID Auto-Optimization Simulator

Use when the advertiser asks to optimize, simulate, preview, or plan MGID changes across campaigns — e.g. "optimize my account", "what should I change", "simulate budget moves", "preview source blocks", "build an optimization plan".

MGID Auto-Optimization Simulator

Orchestrator only: gate → collect signals → merge → rank → preview. Does not reimplement signal rules — load and execute each signal skill's playbook.

Tools: tool reference
Schemas: schemas
Merge/rank: reference.md


Signal skills (load, do not duplicate their rules)

Skill Output
mgid-tracking-health-monitor Gate pass/fail
mgid-budget-reallocator reallocate_budget
mgid-source-auditor block / adjust_qf
mgid-creative-fatigue-detector pause_teaser

mgid-campaign-launch-builder — separate unless user asks setup + optimize.


Required inputs

Input Required Default
Scope Yes see Campaign scope below
Date range Yes last 7 days; baseline = prior 7d
Goal / target Yes user or get_campaign → bidding.price
Mode Yes simulation

No get_account_goals. Target CPA must come from user or campaign config.

Campaign scope

Default: analyze all active campaigns up to 25 (DEFAULT_CAMPAIGN_SCOPE).

Active count Behavior
≤ 25 Analyze every active campaign — no silent truncation
> 25 Stop and ask the user before Phase 2 (see prompt below)

User may override scope at any time:

  • Named campaign IDs → analyze only those (still run gate on all active)
  • “All active” / “full account” → analyze every active campaign regardless of count
  • “Top N by spend” → analyze top N only; state N and the cutoff spend in chat

Never analyze only 1–2 campaigns unless the user explicitly scoped that way.

When active count > 25, ask:

This account has {N} active campaigns. Default scope covers up to 25.

How should I scope the simulation?
1. Top 25 by 7d spend (default — fastest)
2. All {N} active campaigns (more MCP calls, full portfolio)
3. Specific campaign IDs (you name them)

Wait for the user's choice before collecting signals. If they pick (1), rank by get_portfolio_statistics → spent and take the top 25. Record scope metadata in the plan file (see reference.md).


Workflow

Phase 0 — Gate

Run mgid-tracking-health-monitor playbook.

On fail → fix-first plan only; gates.tracking_health = fail.

Phase 1 — Scope campaigns

  1. get_portfolio_statistics → spend, conversions and CPA for every campaign in one call
  2. list_campaigns → filter status.name == active, pick up type, limits, bidding, sourcesOptimization → count N
  3. Drop search_feed and newsletter campaigns from scope and record them as unsupported (see tool reference)
  4. Apply Campaign scope rules (ask user if N > 25 unless user already specified scope); "top 25 by spend" sorts on the portfolio row's spent
  5. get_campaign_statistics only for campaigns whose daily rows a signal skill actually needs (budget cap detection, period-over-period)

Phase 1 costs 2 calls regardless of portfolio size. Never open with one get_campaign_statistics per campaign.

Phase 2 — Collect signals

Execute each signal skill on every campaign in scope; collect Recommendation[]. Do not re-apply their thresholds here.

Pass the scoped campaign list to budget reallocator and source auditor — they must not silently shrink scope on their own.

Creative fatigue scoping (priority order):

  1. Primary: campaigns with no source/budget/tcpa recommendations in this plan and avg daily spend < 85% of limits.dailyLimit (underspending)
  2. Fallback: campaigns with 7d spend ≥ $200 or top 5 by spend in scope (whichever set is smaller)

Use run_custom_report with dimension teaserId (see creative skill) when the account has the customReports grant; fall back to get_teaser_statistics (v1) per teaser.

Record every campaign scanned in the plan file under summary.creative_fatigue.campaigns_scanned — not only campaigns with pause recommendations.

Phase 3 — Merge

Apply conflict rules in reference.md. Dedupe; populate suppressed.

Phase 4 — Rank

Sort by (confidence × estimated_savings) / risk. Cap at 10 (ASSUMPTION).

Phase 5 — Preview

Chat: summary block + table only.

File: write optimization-plan-{plan_id}.json with full OptimizationPlan + stats_snapshot (plain numbers — no hashing).

Ask user to approve by rank number.

Phase 6 — Apply (after approval, write scope)

Read plan file back. Re-fetch statistics; compare to stats_snapshot (ASSUMPTION: warn if >10% drift). Call write tools per ads-mcp-tools.md.

Plan storage, audit trail, idempotent apply → ads-mcp server, not this skill.


Anomalies (simulator-only)

ASSUMPTION: flag when 7d totals vs prior 7d change ≥ 40% on CPC, CVR, or CPA.

Do not use day-over-day — native traffic is too volatile.

Emit alert_anomaly only (no auto-action).


Guardrails

  1. Never apply without explicit approval.
  2. Simulation default.
  3. Mark recommendations mcp_tool_unavailable when write tools absent.

Example chat output

Optimization plan — SIMULATION
Plan ID: opt-2026-07-06-001
Scope: 16 / 16 active campaigns · 2026-06-29 → 2026-07-05
Goal: CPA ≤ $15.50 (from campaign 12061865)
Gates: tracking ✓
Plan file: optimization-plan-opt-2026-07-06-001.json

| # | Action | Entity | Reason | Impact | Risk |
| 1 | Block source | 242669 @ 12061865 | CPA 2.1× target | −$18–23/day | Low |
...

Do not paste full JSON into chat.


Simulator — merge, rank, preview only

Signal skills own thresholds and impact formulas. This file covers orchestration only.

Schemas: schemas
Scope default: threshold assumptions (DEFAULT_CAMPAIGN_SCOPE)


Campaign scope (orchestrator)

The simulator owns scope — signal skills analyze the campaign list they receive.

Constant Value
DEFAULT_CAMPAIGN_SCOPE 25

Selection algorithm

active = list_campaigns filtered to status=active
N = len(active)

if user gave explicit scope → use it
else if N <= 25 → scope = all active ids
else → ask user (see SKILL.md); default choice = top 25 by 7d spend

After scope is fixed, fetch get_campaign_statistics for each id in scope before Phase 2.

Plan file scope block

Always populate:

"scope": {
  "auth_id": 200316,
  "client_id": 330838,
  "account_id": 330838,
  "total_active_campaigns": 42,
  "analyzed_campaign_ids": [12061397, 12061865],
  "analyzed_count": 25,
  "scope_mode": "top_by_spend",
  "scope_limit": 25,
  "user_confirmed": true
}
scope_mode Meaning
all_active Every active campaign analyzed
top_by_spend Subset ranked by 7d spend (record limit)
user_specified User named ids or a custom filter

When analyzed_count < total_active_campaigns, chat summary must show {analyzed} / {total} active campaigns and name the scope mode.


Conflict resolution

Conflict Resolution
Block vs budget to same campaign Defer block unless CPA > 2× target (ASSUMPTION) and post-learning
Pause teaser + budget same campaign Never pause last active teaser
Budget out + in same pair Single net reallocation line
Multiple QF on same entity Smallest decrease wins
Top converter Defer to source-auditor downgrade rules
Tracking fail Suppress all spend actions
CPA Tune learning Suppress blocks — source-auditor owns detail

Ranking

score = (confidence_score × estimated_daily_savings) / risk_score
confidence: high=3, medium=2, low=1
risk: low=1, medium=2, high=3

Take top 10 unless user asks for full list.


Apply order

When write tools available:

  1. set_teaser_status (pause)
  2. update_quality_factors
  3. update_source_filters (blocks)
  4. update_campaign (budget)

Stop on first hard failure; report partial apply in chat.


Drift check at apply

Compare fresh get_campaign_statistics totals to stats_snapshot in plan file.

ASSUMPTION: if spent or conversionsCostBuy moved >10%, warn and require re-confirm before writes.

No sha256 — numeric compare only.


Plan file location

Write under workspace, e.g. ./optimization-plan-{plan_id}.json.

When ads-mcp adds plan storage API, prefer server-side plan id over local files.