Skip to content

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 19 Apr 16:47
· 25 commits to main since this release

v0.6.0 — Shared negative keyword list lifecycle

Closes the loop on shared negative keyword lists. Before this release, propose_negative_keyword_list could create a new list, but once it existed there was no way to modify its contents through MCP — every change had to go through the Google Ads UI. The orchestration rules already told the AI to check for existing lists before creating new ones, but that check had no follow-up tool, so the AI would find a matching list and then create a duplicate anyway.

This release ships the three missing pieces so the full create → append → remove lifecycle works end-to-end through the same preview / dry-run / confirm safety layer as every other write tool.

New tools

  • add_to_negative_keyword_list(shared_set_id, keywords, match_type) — appends keywords to an existing SharedSet instead of forcing a new list. Collapses case-insensitive duplicates, rejects whitespace-only entries, and requires a numeric shared_set_id. Returns a preview with a plan_id for confirm_and_apply.
  • remove_entity now accepts entity_type="shared_criterion" — removes a single keyword from a shared list. The entity_id is the composite sharedSetId~criterionId; feed it straight from the new resource_id field below. Still irreversible, still triggers the double-confirmation safety annotation.

Read-side helper

  • get_negative_keyword_list_keywords now returns resource_id on every row ("sharedSetId~criterionId"). Callers pipe it into remove_entity without hand-assembling composite IDs. Mirrors the existing resource_id pattern on get_negative_keywords.

Updated orchestration rules

The negative-keyword pattern in .cursor/rules/adloop.mdc (and the synced .claude/rules/adloop.md) now documents the three-way branch:

  1. Direct campaign negatives (add_negative_keywords) — campaign-specific, no reuse.
  2. Append to an existing shared list (add_to_negative_keyword_list) — use whenever a matching list exists. Call get_negative_keyword_list_keywords first to avoid duplicate-keyword errors at apply time.
  3. Create a new shared list (propose_negative_keyword_list) — only when no suitable list exists yet.

Plus the removal path: remove_entity with entity_type="shared_criterion" and the resource_id from the read tool.

Safety

  • New append tool goes through the standard ChangePlanstore_planconfirm_and_apply pipeline. No bypass.
  • shared_criterion removal inherits the existing "remove" match in requires_double_confirmation, so both confirmations still fire.
  • Apply-time validation rejects a bare entity_id for shared_criterion with a clear error pointing at the correct format, rather than letting an opaque Google Ads API error surface.

Verification

  • 135/135 unit tests passing (16 new).
  • Two new behavioral evals cover: preferring add_to_negative_keyword_list over recreating a duplicate list, and using entity_type="shared_criterion" with an irreversibility warning for removal.
  • End-to-end verified against the live Google Ads API: append + remove round-trip on a real shared set, with resource_id visible on every row.

Also in this release

  • remove_entity docstring in src/adloop/ads/write.py now enumerates every supported entity_type and its composite entity_id format. It had grown stale as shared_criterion, campaign_asset, asset, and customer_asset were added over several releases.
  • Write evals re-ordered to 8 → 9 → 10 so the file scans in id order.

Install / upgrade

uv add adloop          # or
pip install --upgrade adloop

Credits

New tool design, implementation, tests, and live verification by @illia-sapryga in #15 — their first contribution to AdLoop. Welcome!

Full Changelog: v0.5.2...v0.6.0