v0.7.0
v0.7.0 — RSA pinning + globally installable Claude rules
Two features land together. The first closes a real brand-safety gap in draft_responsive_search_ad. The second makes AdLoop's orchestration intelligence available in Claude Code sessions outside this repo, so Claude users get the same safety patterns and tool guidance that Cursor users have always had via workspace rules.
New: RSA headline & description pinning
draft_responsive_search_ad now accepts pinned fields per asset. Headlines and descriptions can be either plain strings (unpinned) or dicts of the form {"text": "...", "pinned_field": "HEADLINE_1"}. Mixed shapes are allowed in a single call — pin the brand to HEADLINE_1, leave the rest unpinned.
draft_responsive_search_ad(
headlines=[
{"text": "Your Brand", "pinned_field": "HEADLINE_1"},
"Free trial today",
"Save 30% off",
"Quick & easy setup",
],
descriptions=[
{"text": "Trusted by 1000+ teams", "pinned_field": "DESCRIPTION_1"},
"Get started in minutes.",
],
final_url="https://example.com/",
)- Valid pin slots:
HEADLINE_1,HEADLINE_2,HEADLINE_3,DESCRIPTION_1,DESCRIPTION_2. - Google permits at most 2 headlines per slot, 1 description per slot —
_validate_rsaenforces both caps at draft time. - Backward compatible:
headlines: list[str]continues to work for every existing caller.
Before this release, the only options were (a) accept Google's auto-rotation across all assets, or (b) bypass AdLoop entirely and drop into google-ads-python for any campaign that needed brand or compliance pins. Option (a) was a real brand-control loss; option (b) defeated the purpose of the safety layer.
New: adloop install-rules (and update-rules / uninstall-rules)
A new family of CLI subcommands installs AdLoop's orchestration rules + slash commands at the user level (~/.claude/), so every Claude Code session inherits them — not just sessions launched inside this repo.
adloop install-rules # default: inline mode
adloop install-rules --lazy # cheaper baseline cost
adloop install-rules --no-commands
adloop update-rules # refresh after upgrading
adloop uninstall-rules # surgical removalThe adloop init wizard also detects Claude installations now and offers to install at the end of setup.
Two install modes, with an explicit context-cost trade-off:
- inline (default) — full rules embedded in
~/.claude/CLAUDE.mdbetween sentinel comments. Reliable; the LLM has the rules in-context every time. Adds ~10K tokens to every Claude Code session. - lazy (
--lazy) — short directive inCLAUDE.mdpointing at~/.claude/rules/adloop.md. The LLM reads the rules file only when AdLoop tools are in scope. Cheaper baseline cost; depends on the LLM following the directive.
Idempotency is handled by versioned sentinel comments:
<!-- adloop:rules:start v0.7.0 -->
... managed content ...
<!-- adloop:rules:end -->update-rules detects drift across versions and replaces stale blocks cleanly. uninstall-rules only touches the managed block and adloop-* prefixed slash commands — anything you authored yourself in CLAUDE.md is preserved verbatim. Run install-rules ten times in a row, you still get exactly one block.
Claude Desktop / claude.ai has no programmatic rules location, so the installer detects it and prints copy-paste instructions for Project settings → Custom instructions on claude.ai.
Updated orchestration rules
.cursor/rules/adloop.mdc (and the synced Claude / package copies) now document pinning:
- The
draft_responsive_search_adrow in the tools table describes thestr | dictentry shape and pin-slot caps. - The "When user wants to create an ad" pattern gets a dedicated step on when to pin, the ad-strength trade-off, and — for phone numbers specifically — the recommendation to use a campaign-level call asset rather than a pinned headline.
- The RSA best-practices entry was strengthened: "pin only when necessary" now spells out why (reduces Google's rotation, lowers ad strength) and points at the dict shape.
Architecture
- New
src/adloop/rules/package directory ships the rules + slash commands inside the wheel viauv_build's default file inclusion. Verified by building locally and inspecting the wheel. scripts/sync-rules.pynow writes three targets from the canonical.cursor/rules/adloop.mdcsource:.claude/rules/adloop.md(in-repo Claude Code),src/adloop/rules/adloop.md(bundled), andsrc/adloop/rules/commands/*.md(mirrored from.claude/commands/). Single source of truth maintained.- New
src/adloop/rules_install.pymodule exposesdetect_clients,install_rules,update_rules,uninstall_rules. Frontmatter is stripped for inline mode (CLAUDE.mdis itself a rules file and shouldn't contain nested frontmatter) but preserved for the lazy-mode sibling file.
Safety
- RSA pinning validation runs at draft time, before any preview is returned. Invalid pin slot values, slot-cap violations, and length overruns on dict entries are all caught and surfaced in
validation failed → details[]rather than letting them reach the Google Ads API. - The pinning apply path uses
client.enums.ServedAssetFieldTypeEnum[...]lookup — no hand-built enum integers. install-ruleswrites are idempotent and version-tagged. Sentinel comments are HTML-comment-style so they're invisible in rendered Markdown but trivially regex-matchable in source. The block uses a single regex match on uninstall — no false positives in user content possible unless they also authored exactly the AdLoop sentinel comment, which is namespaced asadloop:rules:*.
Verification
- 184 tests passing (158 baseline + 13 new for RSA pinning + 26 new for install-rules + 12 net updates from sync). Full suite passes on Python 3.11–3.13.
- RSA pinning was live-smoke-tested through Cursor — preview correctly rendered the pin column, validation caught invalid pin names, mixed-shape calls worked end-to-end. The existing string-only callers continue to work (covered by unchanged tests).
install-ruleswas end-to-end verified locally against a temp$HOME: inline install preserves user content above the block, second install reportsupdated(not duplicated),--lazywrites the small directive + 580-line rules file,update-ruleswithout flags preserves the existing mode,update-rules --inlineswitches modes correctly,uninstall-rulescleans up the block + lazy file +adloop-*commands while leaving everything else intact.
Also in this release
- The
initwizard's MCP-config snippet section now points users atadloop install-rulesrather than instructing them to manually copy.claude/rules/and.claude/commands/into their project. Manual copy remains documented for users who prefer it.
Install / upgrade
pipx upgrade adloop # if installed via pipx
pip install --upgrade adloop # if installed via pip
uvx adloop@0.7.0 # one-off run via uvxAfter upgrading, run adloop update-rules to refresh the global rules block to the v0.7.0 sentinel. Restart your MCP host (Cursor reload, Claude Code restart) to pick up the new tool signature for draft_responsive_search_ad.
Credits
- @SullyGitHub — RSA pinning (#21), with 13 tests covering canonical pin patterns, slot caps, and validation edge cases.
- @sg-modlab — surfaced the global-rules gap that motivated the install-rules feature (#23).
- @luison — flagged the context-bloat concern that shaped the inline-vs-lazy mode trade-off (#18).
Full Changelog: v0.6.5...v0.7.0