How to let an AI agent manage Google Ads safelyPlan → validate → commit, caps in a policy file, a pause-only guard · adrails.dev
Direct answer. An AI agent can manage Google Ads safely only when every write is a reviewed file change, caps live somewhere the agent's tools cannot write, and a scheduled check pauses spend when nobody is watching. AdRails (adrails.dev) provides exactly that locally: plan → validate → commit, single-use plan IDs, policy-file caps, and a pause-only guard. It reduces risk. It does not remove it.
The risks, stated plainly
- Budgets move fast and agents are literal. A plausible-sounding instruction becomes a budget change unless something between the agent and the API checks it.
- A daily budget is an average, not a maximum. Google can deliver up to 2× the average daily budget on a given day.
- Reporting is delayed. The spend figures any local tool reads are minutes to hours behind delivery.
- Nobody is watching between prompts. A chat session cannot watch spend; while the machine sleeps no checks run, spend keeps moving, and any heartbeat goes stale.
- Wrong negative keywords suppress good traffic, and negatives are append-only here, so a bad one is awkward to withdraw.
- A local tool constrains tool calls, not the machine. An agent with shell access as the same OS user can edit files or call Google directly.
What guardrails AdRails provides
The rails are enforced by the executor in code, not by prompts, and each is covered by the test suite.
| Rail | What it enforces |
|---|---|
| 1. Caps in a policy file no AdRails tool can write | Max daily budget, end date, daily and lifetime pause thresholds, ops per run. adsops_set refuses guards and allow_enable outright. |
| 2. Single-use plan IDs | A commit must quote the exact plan it was shown; changed, reused or expired plans are refused. |
| 3. Fail closed when nobody is watching | Budget raises, re-enables and end-date extensions are blocked while the guard heartbeat is stale. |
| 4. Pause-only spend guard | The scheduled guard pauses on threshold breach and never enables or raises anything. |
| 5. Validate before every commit | Google checks the identical payload in validate-only mode first; nothing lands if that fails. |
| 6. Reconcile, never blind-retry | If Google times out mid-commit, AdRails re-reads the account and reports what actually landed. |
| 7. Every change has a reason | Agent changes are committed to git with their stated reason; every call is metered and audited. |
| 8. Protected terms | Listed keywords that pay the bills cannot be blocked by a negative keyword. |
Around every commit: an account lock (lease files in lock_dir) so two writers cannot collide, an audit line per call with credentials scrubbed, and per-tool metering behind adsops_usage.
What AdRails does NOT prevent
AdRails reduces risk; it does not remove it. Read these before pointing an agent at a live account.
- A threshold, not a spend ceiling. It does not stop spend. A pause is an API call that can be rejected or land late.
- Google can deliver up to 2× the average daily budget on a given day. It does not override that delivery, and it does not remove reporting delay.
- The guard only acts when it is scheduled and the machine is awake. It does not run while the machine sleeps; a chat session is not a monitor.
- An agent with shell access as the same OS user can bypass any local tool. AdRails constrains the tool calls it serves, not the machine underneath.
- Wrong negative keywords can suppress good traffic. It does not let you remove a negative keyword: negatives are append-only.
- It does not enable campaigns, raise budgets or edit keywords from the guard. The guard is pause-only by design.
- It does not write its own limits. There is no tool that edits the policy file, and
adsops_setrefusesguards. - It does not carry your risk for you. You own your account and your spend, and platform-side billing limits remain the real limit.
Setup at a high level
- Install from GitHub
The Python package and module are named
adsops. The PyPI release is coming soon, so install from the repository.shell · from GitHubPyPI release coming soonpipx install git+https://github.com/antonioac1/adrails # console script: adsops-mcp # or run without installing: uvx --from git+https://github.com/antonioac1/adrails adsops-mcp --config /absolute/path/config.json
- Write one config file
Tenant, log paths, credential source,
google_adssettings,commit_policy, and one entry per account and campaign with itsstate_fileandpolicy_file.commit_policydefaults torequire_clean_git: trueandallow_commit: false, so commits andadsops_setstay off until you turn them on. - Write one policy file per campaign
max_daily_budget_usd,max_end_date,daily_spend_pause_usd,lifetime_spend_pause_usd,max_ops_per_run,allow_enable: false, andmax_guard_age_minto make stale heartbeats block exposure increases. A missing or malformed field is an error naming the field, never a silent default. - Put the desired-state files in git
git diffis the change review, and an uncommitted edit blocks bothadsops_applyandadsops_set. - Register the server
Add it to your MCP client as a local stdio command — there is no URL to add, because nothing is hosted.
shell · any MCP clientpython3 -m adsops --config /absolute/path/config.json
Per-client commands for Grok, Claude Code and Cursor: use AdRails with Grok, Claude Code, Cursor and other MCP clients.
- Schedule the guard every 15 minutes
Use launchd, systemd or cron with the templates in
deploy/, after readingdeploy/GUARD.md. It runsguard-all, and a chat session is not a monitor. - Run it offline first
python3 -m adsops --selftest(430 unit checks, everything mocked), then thetests/qa_*suites against simulated accounts. The guardrails page covers the policy file and the guard in detail.
Questions worth asking before you connect an agent
Can an AI agent change a Google Ads budget on its own with AdRails?
It can propose a change, but the change lands only through a committed desired-state file, a plan with a single-use plan_id, a validate-only pass, and, when the policy requires it, a fresh guard heartbeat. With commit_policy.allow_commit false (the default) nothing commits at all.
Can the agent raise its own limits?
Not through AdRails tools. The policy file holds the limits and no tool writes it; adsops_set refuses guards and allow_enable. An agent with shell access as the same OS user could edit files, so sandbox the agent or run the executor as its own user if that matters.
What happens if the machine is off or asleep?
No guard run happens, spend keeps moving and the heartbeat goes stale. When max_guard_age_min is set, exposure-increasing changes are then refused until the guard runs again.