Skip to content

Add draft_dismiss_recommendations - #110

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

kLOsk merged 1 commit into
kLOsk:mainfrom
xgp:feat/dismiss-recommendations

Conversation

@xgp

@xgp xgp commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #108.

Adds draft_dismiss_recommendations, so recommendations the user has decided not to take can be turned down through the usual draft → preview → confirm_and_apply flow.

API: RecommendationService.DismissRecommendation (DismissRecommendationRequest{customer_id, operations[], partial_failure}).

What changed

  • get_recommendations now returns recommendation.id and recommendation.resource_name on each row. It already selected the resource name but dropped it, so there was no way to say which recommendation you meant.
  • New ads/recommendations.py with the draft, the dry-run check and the applier.
    • The draft reads the active recommendations and refuses any ID that's unknown or already dismissed, naming it. The preview lists each recommendation's type, campaign, ad group and the estimated_improvement that get_recommendations already computes.
    • Accepted inputs: bare IDs or full resource names. A resource name from another account is refused, and IDs are checked before anything is read.
    • The applier sends one request with partial_failure=True and reports any failed recommendation by ID, the same way attach_shared_set_to_campaigns does.
  • confirm_and_apply, dry run. DismissRecommendationRequest has no validate_only field, so this op can't use _validate_with_google. It gets an is_ads_reread branch next to the Reddit/GTM/GA4 ones instead.
    • The dry run re-reads the account and fails if any planned recommendation has been dismissed, applied or expired since the preview.
    • The success note says "Google Ads has no validate-only mode for dismissing recommendations".
  • Rules, docs and registration. The tool is registered in server.py and added to the rules (via scripts/sync-rules.py) and to the README write table. The rules' recommendations pattern gains a step 7: dismiss only what the user turned down.

Safety reasoning

  • Single confirmation. Dismissing changes nothing that serves, and Google can show the recommendation again later. A double confirmation felt heavier than the change warrants, but it's a one-line change if you'd rather have it.
  • It fails closed. ValidateOnlyClient still refuses dismiss_recommendation because it isn't a mutate*/upload* method. Even if a dry run somehow reached the applier, nothing would be dismissed; a test pins that.
  • The blocked_operations guard applies under the op name dismiss_recommendations.

Tests

There are 18 new tests in tests/test_recommendations_dismiss.py, none of them hitting the live API:

  • Draft: the preview content; resource names and de-duplication; unknown or dismissed IDs refused by name; a resource name from another account; malformed IDs refused before any read; an empty list; a blocked operation.
  • Dry run:
    • it re-reads and never calls _validate_with_google;
    • it fails, naming only the recommendation that disappeared;
    • the validate-only client refuses the call.
  • Apply:
    • it builds the request from real v25 proto types, with partial_failure;
    • a failed operation is reported by recommendation ID;
    • the operation goes through the dispatch table.
  • get_recommendations exposes the ID.
  • Tool registration: annotations and parameter descriptions.

I mutation-checked the dry-run test: with the "still active" check disabled, it fails.

uv run pytest
1642 passed, 2 skipped, 6 warnings

Baseline on main was 1624 passed, 2 skipped.

I haven't touched adloop-docs/reference/tools.mdx. CLAUDE.md says it's regenerated with scripts/generate_tools.py after a tool change, and I'm happy to open that PR in adloop-docs once this one is settled.

Dismiss Google recommendations the user has decided not to take, through the usual draft, preview and confirm_and_apply flow. get_recommendations now returns each recommendation's ID and resource name, which the new tool takes. DismissRecommendation has no validate_only field, so the dry run re-reads the account and checks every recommendation is still active, the way the Reddit, Tag Manager and GA4 writes do. Closes kLOsk#108.
@xgp xgp mentioned this pull request Oct 26, 2026

@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 careful work. The re-read dry run is the right answer to the missing validate_only, and pinning that ValidateOnlyClient refuses the call is exactly the kind of test we want. Single confirmation is fine with us. We'll regenerate the docs tool reference ourselves after the release. Approving once CI is green.

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

xgp commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review and the merge.

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.

get_recommendations: dismiss a recommendation

2 participants