Skip to content

Recommendations: read and pause auto-apply subscriptions - #118

Merged
kLOsk merged 1 commit into
kLOsk:mainfrom
xgp:feat/recommendation-subscriptions
Oct 27, 2026
Merged

kLOsk merged 1 commit into
kLOsk:mainfrom
xgp:feat/recommendation-subscriptions

Conversation

@xgp

@xgp xgp commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Closes #107.

Adds a read tool and a pause-only write tool for auto-apply recommendation subscriptions, in the shape agreed on the issue. Re-enabling is left out entirely. Both live in a new module, ads/recommendation_subscriptions.py, next to recommendations.py.

get_recommendation_subscriptions (read-only)

  • Query: one GAQL read of recommendation_subscription: resource_name, type, status, create_date_time, modify_date_time.
  • Output: each subscription, sorted by type, plus total_subscriptions and enabled_count. A type that isn't listed is not auto-applied, and the docstring says so.
  • insights[]:
    • one line naming every ENABLED type ("auto-apply without a preview");
    • one line per ENABLED type whose modify_date_time falls in the last 30 days (RECENT_DAYS), so a weekly audit catches auto-apply being switched on;
    • "No recommendation type is set to auto-apply." when none is enabled.
  • health_check: untouched, as you asked.

draft_recommendation_subscriptions (write, pause only)

  • No way to ask for ENABLED: the tool takes recommendation_types and customer_id, and has no status parameter. The applier hard-codes PAUSED whatever a stored plan says. A test changes a plan to ENABLED and checks the request still pauses.
  • Only real changes: each type must have an ENABLED subscription. A type with no subscription, or one already paused, is refused by name, so the preview lists only what the apply will change. It shows current_status → status and modify_date_time per type.
  • Confirmation: a single confirmation.
  • Apply: one MutateRecommendationSubscriptionRequest, with an update and an update_mask of status per type.

Dry run

I checked the v25 protos. MutateRecommendationSubscriptionRequest has validate_only (fields: customer_id, operations, partial_failure, validate_only, response_content_type). So the dry run goes through ValidateOnlyClient like other Google Ads writes, with no re-read check, and a test pins validate_only=True on the request. The operation is in the normal _dispatch_ads_plan table.

Checked against a live account

  • Read: the account has no subscriptions, so the read returns [] with the "nothing auto-applied" insight.
  • Pause: a validate-only pause of a type it doesn't subscribe to came back from Google as RESOURCE_NOT_FOUND. That's expected, and it shows the request is well-formed. I couldn't test a real ENABLED subscription without turning one on, which I didn't want to do in a live account.

Rules and tests

  • Rules: .cursor/rules/adloop.mdc now has both tools in the tables. The recommendations pattern gets step 8: check subscriptions, offer to pause, never suggest re-enabling. Synced.
  • Tests: tests/test_recommendation_subscriptions.py (17 tests) covers:
    • the read: rows, insights, recent-change flag, nothing enabled, a missing date;
    • the draft: preview, dedupe, not-subscribed and already-paused refused by name, bad input refused before any read, no status parameter, blocked operation;
    • the apply: one request with status masks, a tampered plan still pauses, validate-only in the dry run, dispatch;
    • tool annotations and parameter docs.
  • Suite: uv run pytest gives 1680 passed, 2 skipped.

get_recommendation_subscriptions lists every recommendation_subscription with its type, status and create/modify times; insights[] names each ENABLED type and flags one modified in the last 30 days. health_check is unchanged.

draft_recommendation_subscriptions can only set types to PAUSED: there is no status parameter, and the applier hard-codes PAUSED whatever a stored plan says. Re-enabling stays in the Google Ads UI. Each type needs an ENABLED subscription, else it is refused by name. Single confirmation. MutateRecommendationSubscriptionRequest has validate_only in v25, so the dry run goes through the validate-only client.

Fixes kLOsk#107.

@kLOsk kLOsk left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is exactly the shape from #107: nothing can re-enable, and the dry run uses validate_only. Merging now. Two small things for a follow-up, not blocking: a round-trip test through confirm_and_apply(dry_run=True), and an assertion that the operation is an update on the planned resource name. We'll add the README rows ourselves when the batch lands.

@kLOsk
kLOsk merged commit 2602580 into kLOsk:main Oct 27, 2026
3 checks passed
@xgp

xgp commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for merging. The round-trip test and the update-on-planned-resource assertion are in #119.

kLOsk pushed a commit that referenced this pull request Oct 28, 2026
#119)

Follow-up to #118 (review notes): a test drafts a pause and runs confirm_and_apply(dry_run=True) through the real validate-only path, checking one validated call, validate_only on the request, and an update of the planned resource to PAUSED; another asserts every operation is an update on the planned resource name. Tests only.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recommendations: read and pause auto-apply subscriptions

2 participants