agentleFS
adwarp-devadwarp-dev/paid-ads-skills/skills/ad-budget-pacing/SKILL.md

ad-budget-pacing

Skill0 starsChanged 26 days agoMITView on GitHub

Daily monthly-pacing check for LinkedIn Ads and Google Ads. Spend to date plus the last 7 days' daily average times the days left, against a monthly cap, per platform and combined. 1 line when on track; a short alert with the daily budget change when projected spend runs over or under the cap. Built for a daily schedule. Works from CSV exports or live through Adwarp. Use for "are we on pace this month", "budget pacing", "will we overspend", "projected month-end spend", "are we underspending", "daily pacing check".

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

5 rules. About 0 are common, 5 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈5

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    After the 16th: 2 downloads from Campaigns.

    line 38
  2. Distinctive:

    Every change Adwarp makes is recorded in its change journal, so later lift reads and weekly reports see it.

    line 86
  3. Distinctive:

    Run after the reporting day has closed: LinkedIn days end at midnight UTC, Google days at midnight in the account time zone.

    line 99
  4. Distinctive:

    Combined: when platform caps are off pace in opposite directions and the combined cap is on track, say that no change is needed if the caps …

    line 127
  5. Distinctive:

    Never raise a budget to fix under-pace without the budget-limited check in step 7.

    line 172

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Ad budget pacing

Answers 1 question every morning: at the current rate, where does each platform's spend land at month end against its cap? When the answer is close enough, it prints 1 line. When it isn't, it prints a short alert with the daily budget change that lands the month on the cap, and checks whether a budget change can fix it at all.

Use cases

  • "Are we on pace this month?", "will we overspend?", "projected month-end spend".
  • A daily scheduled run that stays quiet unless spend is off pace.
  • LinkedIn Ads, Google Ads, or both, with a cap per platform, a combined cap, or both.

Out of scope:

  • Judge whether the money is well spent. Use paid-media-weekly-report for the weekly scoreboard, or linkedin-ads-performance-review and google-ads-account-audit for depth.
  • Change budgets on its own. It shows the change and waits (Safety).
  • Pace lifetime or flight budgets that don't follow the calendar month. Swap the month for the flight dates and the same math works.

Inputs

  • Monthly caps, from the user: per platform, combined, or both. Keep them in pacing-caps.csv (scope,monthly_cap,currency, with scope LinkedIn Ads, Google Ads or Combined) so a scheduled run can read them.
  • Thresholds: alert when projected spend is above 105% or below 85% of a cap. These are defaults; change them per cap.
  • Current daily budgets, optional but needed for an exact proposal: daily-budgets.csv (platform,entity,daily_budget,currency). Without them, the alert gives the target daily spend per entity instead of new budgets.
  • Known changes this month, optional: an entity ending on a set date, a planned budget change. The projection uses them (Method step 4).

Export mode

Daily spend from the first day of the month (or 7 days before yesterday, if that is earlier) through yesterday. Today is never included.

LinkedIn, from Campaign Manager: open the ad account, Advertise section, ad sets tab. Set the time range as above. Click Export, report type Ad set performance, time breakdown Daily, then Export. Current accounts name the levels campaign, ad set, ad; older exports and the LinkedIn API say campaign group, campaign, creative.

Google Ads: Google allows the Day segment only for date ranges of 16 days or fewer.

  • Through the 16th of the month: Campaigns, custom date range as above, segment icon, Time, Day, then the download icon, Download, .csv.
  • After the 16th: 2 downloads from Campaigns. First, the 1st through yesterday with no segment, for spend to date. Second, the last 7 days with the Day segment, for the run rate.

Columns used. Match by name, ignoring case:

FieldLinkedIn columnGoogle Ads column
DateStart Date (in UTC) (or Day, Date)Day
EntityAd Set Name (older exports: Campaign Name)Campaign
SpendTotal Spent (or Spent, Amount Spent)Cost
CurrencyCurrencyCurrency code
Status, if presentAd Set StatusCampaign status

If a LinkedIn file has an Ad Set Name column, that is the ad set; if not, Campaign Name is. Find the header row first: LinkedIn exports put report-info rows above it, and Google downloads can start with a title row and a date-range row. The header is the first row holding both a date column and a spend or impressions column. Drop Google rows starting with Total. Strip currency symbols and thousands separators; treat -- and blanks as 0.

Connected mode

Connected mode uses Adwarp, a free open-source MCP server and CLI for LinkedIn Ads and Google Ads. Setup: adwarp.dev/docs/install. Pacing and budget changes: LinkedIn Ads optimization. Reads: LinkedIn Ads reporting, Google Ads reporting.

NeedMCP toolCLI
LinkedIn spend to date and last 7 daysget_performance with level: "account" or "campaign" (ad set), startDate, endDateadwarp report summary -p month_to_date, adwarp report perf campaign -p last_7_days
LinkedIn daily spend per ad setget_performance with level: "campaign" and startDate = endDate, 1 call per daynone
Google spend to date and last 7 daysgads_performance with level: "campaign", startDate, endDateadwarp google query "<query>"
Google daily spendgads_search with the first query belowadwarp google query "<query>"
Google daily budgets, statuses, end datesgads_list_campaignsadwarp google campaigns list
Google shared budgets, counted oncegads_search with the second query belowadwarp google query "<query>"
LinkedIn ad set budget change, after a yesupdate_campaign with dailyBudget, dryRun: true firstadwarp campaigns update <id> --daily-budget <amount> previews; add --apply to send
SELECT segments.date, campaign.id, campaign.name, metrics.cost_micros
FROM campaign
WHERE segments.date BETWEEN '<first of month or 7 days back>' AND '<yesterday>'
SELECT campaign.name, campaign_budget.id, campaign_budget.amount_micros, campaign_budget.explicitly_shared
FROM campaign
WHERE campaign.status = 'ENABLED'

Divide cost_micros and amount_micros by 1,000,000.

Things to know:

  • Adwarp keeps the LinkedIn API names: campaign_group is a Campaign Manager campaign, campaign is an ad set.
  • Pass explicit startDate and endDate. The CLI presets count today, so -p month_to_date includes today so far and -p last_7_days is 6 full days plus today. Custom dates are in the MCP tools only; on the CLI, Google takes exact dates through a query, and LinkedIn runs closest to exact just after 00:00 UTC.
  • Adwarp's read tools don't return LinkedIn budgets, so LinkedIn proposals need daily-budgets.csv or the numbers from Campaign Manager. update_campaign changes ad set budgets only; a budget on a Campaign Manager campaign (the group level) is changed in Campaign Manager.
  • Adwarp has no tool that changes an existing Google budget. Google budget changes happen in the Google Ads UI.
  • Every change Adwarp makes is recorded in its change journal, so later lift reads and weekly reports see it.
  • The Google tools run on the local MCP server, not on Adwarp's hosted endpoint.

Running it every day

  • Scheduled agent: "Every day at 9:00, run the ad-budget-pacing skill with the caps in pacing-caps.csv. Post only its output." The 1-line result is the all clear.
  • Cron with the Adwarp CLI: Adwarp has no scheduler, so cron saves the raw pulls and an agent reads them. See how it works.
# Daily 06:30. In crontab, % must be written as \%. GNU date shown; on macOS use date -v-1d +\%F.
30 6 * * * adwarp google query "SELECT segments.date, campaign.name, metrics.cost_micros FROM campaign WHERE segments.date BETWEEN '$(date -d '7 days ago' +\%F)' AND '$(date -d yesterday +\%F)'" > "$HOME/pacing/google-last7-$(date +\%F).txt"

Run after the reporting day has closed: LinkedIn days end at midnight UTC, Google days at midnight in the account time zone. On the 1st of the month, "yesterday" belongs to last month; the run then projects the new month from the last 7 days alone.

Method

  1. Set the dates. Today is the run date and is never counted. Spend to date runs from the 1st through yesterday. Days left = the days from today through the last day of the month, today included. Use each platform's own reporting days (UTC for LinkedIn, the account time zone for Google).
  2. Spend to date per platform, and their sum for Combined. If the 2 platforms report different currencies, pace each in its own and skip Combined unless the user gives a rate.
  3. Run rate = the last 7 complete days' spend / 7. Why 7 days:
    • It holds each weekday once. B2B delivery dips at weekends, so a 3-day window read on a Monday is mostly weekend.
    • It is recent enough to reflect last week's budget changes, which a month-to-date average dilutes.
    • It smooths single-day swings. LinkedIn can spend up to 100% above a daily budget on a given day, and Google up to 2 times the average daily budget.
    • It matches LinkedIn's weekly limit: on a continuous schedule, an ad set won't spend more than 7 times its daily budget in a week.
  4. Adjust the run rate for known changes, per entity, then add up:
    • An entity that ended, or ends before month end: count it only up to its end date.
    • A budget change, or a start, within the last 7 days: use the entity's daily average since that day (at least 3 days, default). With fewer than 3 days, use the new daily budget as the rate and say so.
    • A planned change later this month: apply it from its date.
  5. Project: projected spend = spend to date + run rate x days left. Share = projected / cap.
  6. Classify each cap: over above 105%, under below 85%, on track between (defaults). Cap reached: spend to date already at or above the cap. In the first 3 days of the month (default), hold under-pace alerts, since the data is thin; over-pace and cap-reached alerts always fire, because overspend can't be undone.
  7. Work out the fix for each cap off pace.
    • Required daily spend = (cap - spend to date) / days left. Change = required - run rate, in money and as a percentage of the run rate.
    • Is budget the constraint? An entity is budget-limited when its spend reached at least 90% of its daily budget on 3 or more of the last 7 days (default). Raising budgets only adds spend where entities are budget-limited. For an under-pace alert with no budget-limited entities, say so plainly and point at delivery instead: bids, audience size, keyword coverage.
    • Proposed budgets: scale each budget-limited entity's daily budget by required / run rate, rounded to whole currency units. For under-pace, compute the ratio over the budget-limited entities' spend only. Without budgets, give each entity's target daily spend (its run rate times the same ratio).
    • Google check: after a mid-month budget change, Google paces the rest of the month to the new daily budget times the remaining days, and its monthly charges stay within 30.4 times the average daily budget for most campaigns. So spend to date + current Google budgets x days left is roughly where Google paces the month at today's budgets. If the run-rate projection is above that, Google is overdelivering early and should slow down; report both numbers.
    • Combined: when platform caps are off pace in opposite directions and the combined cap is on track, say that no change is needed if the caps can share money. That is the user's call.
  8. Write the output. Exactly 1 line when every cap is on track. Otherwise the alert: caps that reached their limit first, then over-pace, then under-pace, each ordered by the projected gap in money, then the on-track caps in 1 line each.

Judgment floors and caveats

  • Thresholds are defaults. 105% and 85% leave room for normal daily swings. Tighten them late in the month, when there is less time to correct; loosen them early.
  • A run rate assumes the last 7 days repeat. Month-end pushes, new launches and planned pauses break that; feed known changes into step 4.
  • Early month is noisy. In the first week, most of the run rate comes from last month. Say so in the alert.
  • Overspend and underspend are not symmetric. Over-pace is fixed with a budget cut, which works at once. Under-pace is often a delivery problem that a bigger budget won't touch, which step 7 checks.
  • LinkedIn daily overspend is allowed. Daily spend can run up to 100% above the daily budget; total spend stays within 7 times the daily budget per week on a continuous schedule, the daily budget times the scheduled days on a fixed schedule, and a lifetime budget when set. A single high day is not an alert.
  • Spend is not final until the day closes. Reading after midnight in each platform's reporting time zone keeps yesterday complete.
  • Blind spots: whether the spend is working, invoices or billing adjustments (Google credits costs above its spending limits, so billed amounts can sit below reported cost), or budgets on entities that didn't deliver.

Output

On track, exactly 1 line:

Pacing <Month yyyy>, data through <Mmm d>: on track. Projected month end: LinkedIn Ads $<n> of $<cap> (<x%>), Google Ads $<n> of $<cap> (<x%>), combined $<n> of $<cap> (<x%>).

Off pace, a short alert:

Pacing alert: <Month yyyy>, data through <Mmm d>, <n> days left
Run rate = average daily spend over the last 7 days (<Mmm d> to <Mmm d>). Alert above <105>% or below <85>% of a cap (<defaults | set by you>).

<Platform>: <OVER | UNDER | CAP REACHED>. Projected $<n> against a $<cap> cap (<x.x%>).
  Spent $<n> to date; the last 7 days averaged $<n> a day. Landing on the cap takes $<n> a day, a change of <+/-$n> a day (<+/-x.x%>).
  <Budget-limited check: which entities are limited, and whether a budget change will move spend.>
  <Proposed daily budgets, each scaled by <x.x%>: <entity> $<old> to $<new>; ... | Why a budget change won't fix it, and what to check instead.>
<Platform>: on track. Projected $<n> against $<cap> (<x.x%>).
Combined: <status>. Projected $<n> against $<cap> (<x.x%>). <Note when platform misses offset each other.>

Nothing has changed. Reply with the budgets you approve. <Which changes Adwarp can make after you confirm, and which happen in the platform UI.>

Order inside an alert: cap reached first, then over, then under (overspend can't be undone), each by the projected gap in money; on-track caps last. A full example computed from made-up data is in references/sample-alert.md.

Safety

  • Reads only: exports, or Adwarp's read tools (get_performance, gads_performance, gads_search, gads_list_campaigns). gads_search takes read-only queries.
  • Changes nothing on its own, scheduled or not. A proposal is a list of old and new budgets.
  • After the user approves specific budgets, LinkedIn ad set budgets can be changed through Adwarp: update_campaign with dryRun: true first (or adwarp campaigns update <id> --daily-budget <amount> without --apply), show the preview, then send on a yes. Budgets on a Campaign Manager campaign (the group level) and every Google budget are changed in the platform UI, because Adwarp has no tool for them; say so in the alert.
  • Never raise a budget to fix under-pace without the budget-limited check in step 7.

Sources

All checked 2026-10-20.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.