Docs · Agent safety

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.

Last tested with AdRails 0.2.2 · Python 3 · macOS and Linux · Apache-2.0 · runs on your machine, nothing hosted
The problem

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 is enforced

What guardrails AdRails provides

The rails are enforced by the executor in code, not by prompts, and each is covered by the test suite.

RailWhat it enforces
1. Caps in a policy file no AdRails tool can writeMax daily budget, end date, daily and lifetime pause thresholds, ops per run. adsops_set refuses guards and allow_enable outright.
2. Single-use plan IDsA commit must quote the exact plan it was shown; changed, reused or expired plans are refused.
3. Fail closed when nobody is watchingBudget raises, re-enables and end-date extensions are blocked while the guard heartbeat is stale.
4. Pause-only spend guardThe scheduled guard pauses on threshold breach and never enables or raises anything.
5. Validate before every commitGoogle checks the identical payload in validate-only mode first; nothing lands if that fails.
6. Reconcile, never blind-retryIf Google times out mid-commit, AdRails re-reads the account and reports what actually landed.
7. Every change has a reasonAgent changes are committed to git with their stated reason; every call is metered and audited.
8. Protected termsListed 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.

Limits

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_set refuses guards.
  • 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

Setup at a high level

  1. 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 soon
    pipx 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
  2. Write one config file

    Tenant, log paths, credential source, google_ads settings, commit_policy, and one entry per account and campaign with its state_file and policy_file. commit_policy defaults to require_clean_git: true and allow_commit: false, so commits and adsops_set stay off until you turn them on.

  3. 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, and max_guard_age_min to make stale heartbeats block exposure increases. A missing or malformed field is an error naming the field, never a silent default.

  4. Put the desired-state files in git

    git diff is the change review, and an uncommitted edit blocks both adsops_apply and adsops_set.

  5. 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 client
    python3 -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.

  6. Schedule the guard every 15 minutes

    Use launchd, systemd or cron with the templates in deploy/, after reading deploy/GUARD.md. It runs guard-all, and a chat session is not a monitor.

  7. Run it offline first

    python3 -m adsops --selftest (430 unit checks, everything mocked), then the tests/qa_* suites against simulated accounts. The guardrails page covers the policy file and the guard in detail.

FAQ

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.