TL;DR
We built an inventory aware ad agent that listens to product updates, computes safe actions, and adjusts budgets in minutes. The primary problem was wasted spend and bad experiences when ads drove to out of stock pages, especially during product drops and flash sales. By combining typed playbooks, transition gates, and a small Bayesian allocator, we kept spend efficient and decisions explainable for channel managers who needed clear justifications. This is a developer story about turning inventory signals into action with workflow automation, guardrails, and measurable outcomes that held up during messy real world launches.
The moment the problem became real
At 7:42 a.m. on a Friday drop, our DTC apparel client sold through two sizes of a new colorway in nine minutes. Performance looked great until the ad comments rolled in. Shoppers clicked a retargeting ad, landed on an out of stock size selector, and bounced. Creative still promised same day shipping. Budget kept burning. Support tickets spiked. Refund probability rose. The brand manager wrote one line in Slack: please stop selling what we do not have.
We had already moved the client onto ButterGrow's platform for orchestration and approvals, but inventory signals were still passive. They sat in data warehouses and daily feeds while ads ran on hourly budgets. We needed an agent that could see stock transitions as they happened, then act with the same urgency ads spend with. We run the agent on OpenClaw so playbooks, approvals, and observability live in the same runtime the team already trusts.
Goals and constraints we set on day one
- Protect the shopper experience by avoiding funnels that end on out of stock pages.
- Reduce wasted paid clicks while keeping top sellers funded.
- Avoid rate limit spikes and platform policy violations.
- Make every decision explainable and reversible within minutes.
- Fit inside the team’s existing approval and change windows.
A few constraints shaped the design. Catalogs change a lot during drops, so we could not make one update per product per minute without hitting API limits. Merch teams sometimes restock within an hour, so flapping between pause and unpause had to be dampened. Finally, performance teams wanted pooled budgets to shift toward what remained in stock rather than just shutting everything off.
Architecture at a glance
We implemented a small set of services around product updates and ad actions. At the core was a state machine that computed transitions like in stock to low or low to out of stock. Typed playbooks described what to do on each transition. A policy layer added timing guards, for example suppress actions during back office import windows. A decision layer reallocated pooled budgets across categories using a Bayesian sampler, but only among categories with sufficient stock. In practice, the workflow orchestrator turned noisy product changes into automated workflows that were auditable end to end.
Key building blocks we leaned on:
- Product event ingestion with a compact, idempotent schema.
- A cache of last known stock states with TTL and audit history.
- Transition detectors that fire only on meaningful changes.
- Mappers from SKUs and collections to ad entities across platforms.
- Executors that perform status changes, budget edits, or creative swaps.
- Observability with traces, decision logs, and human readable justifications.
If you are new to the product side of this pattern, the real time product feed is worth understanding. We documented it in the real time product feed guide, which explains how to move from hourly CSVs to streaming changes without a full replatform.
For readers curious about capabilities beyond this story, the AI marketing automation features page walks through the building blocks our teams reuse across agents.
The state model that made actions predictable
Free form rules make systems brittle. We chose a small vocabulary that every engineer and marketer could read on a whiteboard:
- in_stock
- low_stock
- out_of_stock
- restocking
Transitions are edges between those states. Each edge carries an action policy. For example, in_stock to low_stock may throttle budgets by 10 percent while low_stock to out_of_stock may pause specific ad groups and swap creative to steer toward alternatives. restocking to in_stock may wait 30 minutes before unpausing to avoid flapping.
We stored the last state per SKU and per collection so that collection ads could continue even when individual SKUs dropped out. Every transition generated an idempotency key of the form sku:from->to:date:window so retries across executors remained safe.
Step 1Define the typed playbook
The playbook describes the transitions, actions, and throttles. Here is a trimmed version of what we shipped for the apparel client:
version: 1
entity: inventory_state_machine
states:
- in_stock
- low_stock
- out_of_stock
- restocking
transitions:
- from: in_stock
to: low_stock
actions:
- type: budget_throttle
scope: adgroup_by_sku
amount_pct: 10
floor_daily: 200
- from: low_stock
to: out_of_stock
actions:
- type: pause_entities
scope: adgroup_by_sku
- type: creative_swap
scope: adsets_by_collection
template: "Highlight back in stock alerts"
- from: restocking
to: in_stock
actions:
- type: delayed_unpause
delay_minutes: 30
guards:
- name: import_window_mitigation
if_time_in: "02:00-03:00"
suppress_actions: true
- name: sanity_check
if_percent_catalog_changed_gt: 40
require_manual_approval: true
Step 2Build a mapper from inventory to ad entities
We maintain a table that maps SKUs and collections to ad entities across platforms. For paid search, the link is often through product groups. For paid social, the link is usually ad sets or catalog based dynamic creatives. The mapper exposes a small API to fetch which entities should be touched for a given transition.
type EntityRef = { platform: "google_ads" | "meta"; type: "ad_group" | "ad_set"; id: string };
async function entitiesForSkuTransition(sku: string, from: string, to: string): Promise<EntityRef[]> {
const key = `${sku}:${from}->${to}`;
return await mapper.get(key);
}
Step 3Prevent wasting ad spend on out of stock products
When the transition detector emits low_stock to out_of_stock, we fan out status changes to the mapped entities. The executor batches mutations per platform to stay under QPS and applies idempotency keys to each action. We also mark related creatives for swap so collection ads point to alternatives. This single step is where the spend protection shows up within minutes.
Step 4Reallocate budgets with a small Bayesian loop
Rather than freezing all spend when inventories tighten, we shift pooled budgets to categories that still have depth. We started with Thompson sampling across a handful of category level pools. The sampler respects budget floors and caps, and it ignores any pool that is not healthy on stock. This lets high intent campaigns keep learning while constrained categories cool down.
Step 5Explain every action in plain language
Every action writes a justification like: paused 17 ad groups for 9 SKUs in the Denim collection after low to out_of_stock transitions, next review in 30 minutes. These justifications appear in the decision log and in chat notifications so channel managers can audit without digging into traces.
Policy gates that saved us from nightmares
The first week taught us that the happy path is not enough. A catalog import arrived with corrupted stock counts and briefly set everything to zero. Without guards, the agent would have paused the world.
We added three gates:
- Import window suppression. During scheduled import windows, transitions still log but actions are held until the window closes.
- Sanity check on the percent of the catalog that changed in one slice. If more than 40 percent flips to out_of_stock inside five minutes, a human must approve.
- Holdbacks for new rules. When we add a new transition policy, it launches to 10 percent of traffic first so we can compare against a control pool.
These saved us twice in early testing. We also exposed toggles in the approvals UI so channel owners could override in an emergency without editing code.
What actually changed in performance
On the first drop after launch, the agent paused 212 ad groups across three platforms within 11 minutes of inventory crossing thresholds. Bounce rate on product detail pages for paused SKUs fell by 31 percent compared to the previous drop. Clicks that would have gone to those SKUs shifted toward substitutes in the same collections. Net effect on revenue was neutral during the drop window, but the brand avoided thousands of frustrated sessions and dozens of angry comments. Over four weeks, blended CAC improved by 18 percent because spend migrated toward consistently in stock products.
We did not chase perfection. Restocks still happen mid day and sometimes stock counts lag carriers. The bar was simple: do no harm, cut obvious waste, and keep performance teams in the loop.
Tradeoffs we accepted
- Latency versus stability. We chose minute level response times instead of seconds to avoid flapping and rate spikes.
- Budget reallocation simplicity over complex uplift models. A simple sampler with clear constraints outperformed our heavier models in early tests because it never bid into empty shelves.
- Catalog coverage over granular creative logic. Collection level swaps covered the majority of cases while SKU specific creative rules added complexity with diminishing returns.
Failure modes and how we fixed them
Failure mode 1. Double pauses on retries. We saw duplicate actions when a network hiccup caused an executor retry while the first mutation silently succeeded. Fix: idempotency keys persisted per transition and a platform side check before issuing mutations.
Failure mode 2. Race conditions during large imports. A burst of updates caused low_stock to out_of_stock to restocking sequences inside minutes. Fix: debounce windows per SKU and a 30 minute cool down before unpausing.
Failure mode 3. Mapper drift. New campaigns shipped without entries in the mapper, so some SKUs never paused. Fix: a nightly diff that compares catalog entities with ad platform inventories and opens tickets when coverage falls below 95 percent.
Failure mode 4. Human trust. Channel managers feared the agent would change budgets at the wrong time. Fix: decision logs with plain language justifications and a Saturday test window with approvals required.
A tiny comparison of actions we tried
| Action type | When we used it | Result we observed |
|---|---|---|
| Pause entities | Low to out_of_stock | Fastest spend protection for SKU level campaigns |
| Creative swap | Low to out_of_stock | Preserved collection traffic while steering to alternatives |
| Budget throttle | in_stock to low | Smoothed curves and slowed waste without abrupt drops |
Where this pattern fits and where it does not
This approach is most valuable for fast moving catalogs, frequent drops, and high paid social mix. It also works for search shopping ads when product groups map cleanly to SKUs or collections. It is less helpful for made to order products or long lead time B2B catalogs where communicating lead time in creative may beat pure pauses. Think of it as inventory aware marketing automation for ecommerce where the agent knows when to steer traffic to what is available.
If you want background on the feed side of this pattern, we recommend skimming the real time product feed guide and then exploring more from the ButterGrow blog to see adjacent implementations from other teams.
Implementation notes that saved us time
- Make the state machine the source of truth. Everything derives from state plus transitions. It simplifies audits and rollbacks.
- Keep the mapper under version control. When campaign structures change, reviewers can see exactly which SKUs map to which entities.
- Budget floors prevent total dark periods. Even when inventory tightens, floors kept learning active on evergreen campaigns.
- Notifications matter. A short justification in chat built trust and reduced the need for live walkthroughs.
For teams adopting similar patterns inside their organizations, the best starting point is a small pilot on one category. The approvals and gate policies are the hardest parts to get right. The data plumbing tends to be straightforward once the product feed is incremental. This is also a practical path if you are searching for how to build an ad pausing agent for Shopify without rewriting your entire marketing stack.
For a deeper dive into capabilities you can reuse out of the box, the AI marketing automation features page covers playbooks, approvals, and observability. If you are ready to try it with your team, you can follow the onboarding flow to configure your first catalog and ad mappings in minutes.
We built this on the same orchestration runtime we use for other agents, including offer consistency and incident response. The pattern repeats because the primitives are similar: events in, state transitions, guarded actions out.
ButterGrow is opinionated about safe automation, and that helped. The platform enforces approvals, change windows, and decision logs by default so teams can scale without losing control.
If you want more first hand build notes, we occasionally annotate repo diffs in internal chats and turn them into posts when they stand on their own. This was one of those weeks.
If you are curious about the underlying ad platform operations, official docs are worth reading. We list a few references below to ground the terms we used here.
To see how other teams wire similar pieces, check out the real time product feed guide and then browse more from the ButterGrow blog for adjacent case studies.
Across all of this, our north star was simple: keep shoppers out of dead ends and keep budgets learning where inventory is healthy.
If you want to understand where this fits among other capabilities, scan the features overview and then try a pilot with your highest churn category.
ButterGrow runs on the agent framework that powers production grade automations at brands of all sizes. The primitives here will feel familiar if you have built incident or approval flows before.
You can reach out with your catalog quirks. We enjoy the weird ones.
To see what setup looks like end to end, the features page and the ButterGrow blog are good starting points.
If you prefer a hands on path, use the onboarding flow and apply these guardrails to one category for the next drop.
ButterGrow customer teams often add their own flavor on top, like using creative swaps that promote back in stock alerts or bundling slow movers with evergreen items.
We kept this story narrow so the lessons translate across stacks.
ButterGrow is happy to trade notes with your team.
This was a build worth keeping.
Our hope is that the patterns here save you the painful Friday we started with.
If you want to explore adjacent systems that keep offers aligned as catalogs move, there is also a case study on offer consistency that pairs well with inventory aware actions.
Remember that shoppers do not care about clever agents if the product is not available. The best agent is the one that quietly prevents bad experiences.
Ready when you are.
The rest is footnotes and links.
Let us know what you build next.
In the meantime, happy shipping.
ButterGrow is ready to help.
If this story maps to your reality and you want to try the pattern, ButterGrow makes it straightforward with playbooks, approvals, and observability included. You can follow the onboarding flow to configure your catalog and ad mappings, then pilot the agent on one category before rolling out.
References
- Shopify Admin API inventory data - Official docs on inventory items, locations, and adjusting inventory levels.
- Google Ads API mutate operations for campaign status - How to pause or enable entities programmatically with retries and fault handling.
- Thompson sampling - Primer on the Bayesian method we used for budget allocation.
Frequently Asked Questions
How does the agent detect low inventory without flooding APIs?+
We subscribe to incremental product updates and maintain a lightweight cache keyed by SKU. The cache stores stock status and thresholds so we only query the ad platforms when a transition occurs, for example from in stock to low or low to out of stock. This keeps calls predictable under QPS limits.
What is the simplest way to pause ads for out of stock SKUs?+
Map each SKU or collection to its ad entities, such as Google Ads ad groups or Meta ad sets, and issue a status mutation when the inventory state crosses a threshold. Use idempotency keys per transition so retries never double apply. Provide a delayed unpause to avoid flapping when stock briefly returns.
How do you allocate budget when inventory is uneven across categories?+
We used Thompson sampling across category level pools. The sampler only considers pools with available and healthy stock, then shifts budget toward higher posterior performance. When inventory tightens, the sampler naturally defunds constrained pools until replenishment is detected.
What safeguards prevent accidental global pauses during catalog imports?+
We gate actions behind a circuit breaker that requires corroborating signals, including a minimum percent of products updated and a sanity check against historical inventory volatility. If a massive drop occurs during a catalog import window, actions are suppressed and a human is paged for review.
How do you backfill missed events after an outage without re pausing everything?+
Run a bounded backfill window and recompute transitions with the latest state. Because we store last known inventory states and use idempotency keys per SKU transition, replays become no ops unless the state truly changed. This avoids duplicate pauses.
Can this approach work for B2B catalogs with long lead times?+
Yes, but tune thresholds to reflect purchase cadence and set longer cool downs for unpausing. For made to order items, prefer creative swaps that steer to alternatives instead of hard pauses, and communicate lead times clearly in ad copy to reduce cancellations.
Ready to try ButterGrow?
See how ButterGrow can supercharge your growth with a quick demo.
Book a Demo