Developer Stories10 min read

We Built a Creative Feed Testing Agent with OpenClaw for Workflow Automation

By Maya Alston

TL;DR

We built a production creative feed testing agent that generated, validated, and rotated ad variants across Meta and Google Ads using OpenClaw. The system cut media team toil by 14 hours per week, reduced obvious duplicate uploads to zero, and lifted return on ad spend by nine percent in six weeks. The core idea was simple: treat creative like data with contracts, then automate the handoffs. This case study shows the run graph, the guardrails, and how we kept workflow automation predictable under real ad platform constraints.

The brief that kicked this off

Our performance team had a specific problem. Static creative was fatiguing every ten to twelve days, manual swaps were happening late on Fridays, and the learning phase kept resetting at the wrong time. We were asked to ship an agent that could propose variants, enforce brand rules, publish in controlled slices, and promote winners without blasting budgets. The targets were clear: stop duplicate postings, keep policy rejections under two percent, and show a measurable lift by quarter end.

We built the agent on OpenClaw because we already relied on it for orchestration, audit trails, and safe rollouts. The hosted OpenClaw assistant inside ButterGrow gave us runners, secrets, and approval gates that we could reason about with the media and brand teams.

Architecture at a glance

At the highest level we split the system into five stages: ingest, generate, validate, publish, and learn. Each stage emitted typed events so we could replay failures without guessing state. The run graph below is a simplified excerpt from our first working version.

# openclaw playbook excerpt
version: 1
contracts:
  Asset:
    fields:
      id: string
      hash: string
      caption: string
      brand_ok: boolean
      channel_targets: array
  PublishJob:
    fields:
      asset_id: string
      channel: enum[meta, google]
      campaign_id: string
      status: enum[pending, sent, confirmed, failed]

run:
  - name: generate_variants
    uses: llm.captioner.v2
    in: {product_brief, base_creative}
    out: [Asset]
  - name: brand_gate
    uses: rules.brand_policy
    in: [Asset]
    out: [Asset]
  - name: dedupe
    uses: util.p_hash_check
    in: [Asset]
    out: [Asset]
  - name: publish
    uses: channel.executor
    in: [Asset]
    out: [PublishJob]
  - name: learn
    uses: bandit.promoter
    in: {metrics_stream, PublishJob}
    out: decisions

That contract first approach proved to be the lever. If an upstream tool produced an Asset that did not satisfy the schema, the run stopped before touching an ad account. Validation happened where bugs were cheap.

Why creative feeds mattered to us

Our brands had moved to feed based buying where campaigns pull a stream of images and captions that rotate through placements. It unblocked scale but it also made low grade duplication easy. One small crop or a slightly different background slipped through human review and burned budget without learning anything. The agent’s job was not to be clever. It only needed to be consistent, fast, and traceable.

We anchored async communication on two facts. First, creative fatigue tends to show up as a slow slide in click through rate and rising cost per add to cart. Second, most rejections come from the same handful of policy misses and off brand captions. With that, the project plan focused on making these two feedback loops short and boring.

Step 1Model the inputs and the contracts

We wrote the Asset contract with fields that the whole team could debate. The brand team asked for minimum logo size and safe areas. Media wanted per channel metadata like placement hints. Engineering pushed for stable IDs and perceptual hashes. The compromise lived in a compact set of fields and a few computed properties that the validators filled.

Two choices paid off.

  • We stored the perceptual hash as a short string and used Hamming distance to catch near duplicates. That removed guesswork when someone exported a new PNG with the same layout.
  • We treated captions as structured objects with tone and message tags instead of raw text. That let the brand gate reason about intent before it looked at pixels.

This section answered a long tail question we got a lot: how to automate creative testing across Meta and TikTok without rewriting everything per channel. Contracts made the adapters thin.

Step 2Build the variant generator with guardrails

The generator mixed two sources. It pulled product facts from the catalog and it asked a captioner to propose two to three alternatives per asset. The captioner prompt was templated with style notes and a forbidden term list. We also let it request a crop and background treatment from an image tool when the SKU had room for it. The output was a set of candidate Asset objects that looked complete to the rest of the system.

We learned fast that the safest speed up was not more variants. It was better first passes. A small change in the prompt to anchor benefits to concrete nouns cut later rejections by half. We also added an early readability check for minimum font size on mobile crops. That saved time on reviews.

Step 3Enforce brand and policy rules before the network call

This stage did all the boring work on purpose. It checked logo position, safe area, contrast ratios, and the caption tone tags. It also scanned for a short forbidden term list that legal maintained. Assets that passed got a brand_ok true and we stamped a content credential when possible. Our legal team liked that the agent could attach provenance badges using the content credentials spec because it simplified audit conversations.

We wrote the gate as a reusable function. It took an Asset, returned an Asset, and surfaced explainable errors with trace IDs. That made debugging a real collaboration between engineering and brand without long screen shares.

Step 4Publish to channels and respect limits

Publishers were channel adapters with their own token buckets. We had a Meta path and a Google Ads path, both sharing the same executor skeleton. The executor looked at campaign mappings and placement hints, then wrote a PublishJob record with a pending status. A worker streamed those jobs to the ad APIs at a controlled pace.

We encoded limits as policy variables so the team could tune them without code. For example, we set a small overnight burst cap for new assets and a lower midday cap while the media team was in the account. The runners respected both the burst and the sustained rate, which kept us inside platform rules and avoided flurries of errors. When a limit or network error did happen, exponential backoff kicked in and the job either recovered or landed in a dead letter queue for replay.

For context on what we were guarding against, see the official guidance on Facebook Graph API rate limiting and the Google Ads API rate limits best practices. The exact numbers vary by app and account, but the pattern is the same.

Step 5Learn from outcomes and promote winners

Once the variants were live, a small bandit picked which creative to show more often. Early signals came from click through rate and add to cart events. Downstream signals came from our warehouse, where we computed ROAS by variant daily. The promoter waited for enough evidence before it promoted or paused a variant. Decisions were logged with the priors and the credible interval that triggered the action.

We considered a rules only approach, but a bandit held up better when audience size changed or when a sale temporarily spiked all metrics. The promoter also respected a quiet period after large edits so that the ad platform learning phase could settle.

Step 6Operate it in production

A few OpenClaw features made the day to day feel boring in a good way. We used draft runs for risky changes, and we relied on approvals before any schema change. Priority queues ensured that scheduled publishes went out even when backfills were running. Observability gave us trace level debugging when a job went missing. The result was a system that handled happy paths and rough edges with the same calm rhythm.

We documented two easy to forget habits. First, never schedule a publish window right on top of a warehouse load. Second, group uploads by campaign and placement so that an outage cannot leave one ad group imbalanced while the others move on. These two rules prevented a cluster of subtle bugs that we did not want to relive.

What went wrong and how we fixed it

We had three notable incidents.

  • The captioner tried a playful line that used a banned term for a health product. The brand gate caught it, but we realized our forbidden terms list did not cover slang. We added a small expansion step that generated slang equivalents and checked those too.
  • Our perceptual hash threshold was too strict for a holiday template. The agent flagged near duplicates that humans considered fine. We added a campaign level override for the threshold and logged which overrides were active during a run so our analysis stayed honest.
  • A platform side outage caused a wave of timeouts during a high stakes launch. The dead letter queue worked, but our backfill replay created an hour of collisions with a different scheduled batch. We fixed it by adding a replay window and a collision check against future reservations.

These were normal bugs for a system with moving parts. The important part was that the run graph made each fix local. We did not rewrite the agent. We corrected a small component, added one or two tests, and shipped.

Results after six weeks

We ran the agent in parallel with a human only workflow for the first two weeks, then let it manage two medium spend campaigns while the team watched. After six weeks, we recorded the following outcomes.

  • Duplicate asset uploads dropped to zero across both channels.
  • Policy rejections fell to one point six percent, down from five point three percent.
  • Return on ad spend improved by nine percent for the two test campaigns.
  • Media team time on asset prep and swaps fell by 14 hours per week.

We also noticed a softer win. People stopped rushing Friday swaps. The agent handled rotations on a schedule and the bandit promoted winners at 10 a.m. local time, not at midnight.

Implementation details other teams ask about

Two long tail topics come up in demos: build a brand safe ad creative generator, and automating ad creative rotation without fighting learning phases.

On brand safety, the logo placement check was surprisingly simple. We used a lightweight detector trained on historic print guidelines. It produced a bounding box that we checked against a safe margin. Combined with caption tone tags, this blocked almost all off brand pieces.

On rotation, we found that a slow ramp worked better than big switches. The promoter never jumped a variant from 10 percent allocation to 80 percent. It climbed in steps and never crossed a budget boundary close to a reporting cut off. That kept the metrics steady and avoided suspicion that the agent was fighting the ad platform itself.

If you are exploring this pattern, the article on creative feeds as a performance lever gives helpful context on why feeds changed how teams plan tests in the first place.

What we would do differently next time

We would invest earlier in a shared test matrix that the media and brand teams both owned. We modeled this in our warehouse after the fact, but a small UI in front of it would have reduced back and forth. We would also push provenance tagging deeper into the pipeline. Stamping content credentials at generation time would have made later audits even simpler.

Finally, we would pre bake a library of tone prompts per vertical instead of per campaign. The best variants often reused a few proven turns of phrase that we learned the hard way. Capturing them would help new campaigns hit the ground running.

References

Our full feature set for marketers is covered on the AI marketing automation features page. If you want to try a version of this agent without writing code first, you can explore ButterGrow and then get started in minutes. For setup details and plan questions, the answers to common questions section is the fastest path.

Frequently Asked Questions

How did the agent avoid publishing duplicate creatives across Meta and Google Ads?+

We fingerprinted every asset with perceptual hashing and stored the hash in a small KV table keyed by campaign and channel. The OpenClaw playbook checked this table before publish, which blocked near-duplicates even when file names or subtle crops changed.

What metrics decided when a variant should be promoted or paused?+

We used a simple uplift model that combined early CTR, cost per add to cart, and downstream ROAS from the data warehouse. A Thompson sampling bandit selected winners per audience, and OpenClaw scheduled promotions or pauses based on credible intervals to keep decisions stable.

How were ad platform rate limits handled without stalls?+

We used channel specific executors with token buckets and backoff. Limits were encoded as policy variables in OpenClaw and the runners applied burst and sustained caps. We also cached recent responses to reduce redundant lookups when the same creative was queried repeatedly.

How did you enforce brand safety and policy compliance before an upload?+

We combined a small image classifier for logo placement, a prompt constrained caption generator, and a rule set that checked disallowed terms and minimum font sizes. The policy gate ran before any network call, which reduced platform rejections and shortened review cycles.

What happens when a downstream service is down during a scheduled publish window?+

OpenClaw retried with exponential backoff and then parked the job in a dead letter queue with context. We used event backfill to replay only the affected assets after the outage, which avoided double posting and kept the test matrix intact.

How can another team reproduce this approach without our private models?+

Swap in any captioner that supports style prompts and keep the same contracts. The important pieces are the asset schema, the brand rules, and the publish executor. The rest is wiring, which the OpenClaw run graph handles once the contracts are in place.

Ready to try ButterGrow?

See how ButterGrow can supercharge your growth with a quick demo.

Book a Demo