A few days ago I had an AI agent rebuild every review cover on one of my self-hosted WordPress sites from real photos, with a hard ban on AI-generated gadgets. That pipeline stuck. What changed this week is the source rule itself.
I used to prefer my own desk photos on the cover. They felt honest. Then I sat four new reviews next to the older set and realised the desk shots were noisier, less consistent and harder to read at thumbnail size than the maker’s polished product or press photos. So I flipped the standing rule, and I had the same agent apply it to those four posts.
The new standing rule
Written down before any upload:
- Prefer the maker’s official product or press photo on the branded cover layout, as long as the terms allow editorial use.
- My own photo stays in the review where there’s room for context. The cover is identification at glance; the article is proof I own the thing.
- Still no AI-generated gadgets. No Midjourney product, no “cleaned up” redraw, no model that invents buttons. The earlier ban from My AI Agent Rebuilt Nine Blog Covers Without a Single AI Image still holds.
- Masking is fine; painting isn’t. Background removal and a clean dark branded frame are allowed. Relighting, extending or “enhancing” the device is not.
- Log every source. Page URL, direct image URL, download date and what processing ran, with the untouched original kept next to the cutout.
That sounds like a small preference change. On a review site it isn’t, because the cover is the first claim readers see about what they’re getting.
Same template, new sources
The agent reused the HTML/CSS cover template from the earlier rebuild: short title, product image, category label, site mark, rendered to 1200×630 in headless Chromium. What changed was the image slot. For each of the four posts it downloaded the maker’s press or product shot from the maker’s own site, recorded it in the sources log, and only then ran the cutout step.
python render.py \
--title "Short Honest Title" \
--image assets/official/device-press.png \
--source-log SOURCES.md \
--badge --category GEAR \
--out output/device-cover --jpg
One cover had to keep the photo whole inside a card instead of cutting it out, because that maker’s press terms say the image can’t be altered. The template already supports that path; the agent picked it without inventing a new layout.
Backups first, always
Before anything touched WordPress, the agent built a backup folder for just these four posts:
- A REST snapshot of each post and its current featured media item
- The live page HTML as served
- The current cover image file, with SHA-256 hashes in a checksums file
- A
manifest.jsonthat maps each post ID to the old featured media ID and source URL - A
revert.pythat puts those featured media IDs back
The revert script is deliberately boring. It never deletes the new media. It only POSTs the previous featured_media id back onto the post. Dry run is the default; --apply is required to write.
python revert.py # prints post -> old featured_media
python revert.py --apply # restores featured_media; old files stay
That’s the same habit as I Gave My AI Agents One Shared Changelog With a Rollback Line: every write ships with an undo line, and the old asset stays until I choose to clean it up.
Approve the set, then swap
Nothing went live as four separate thumbs. The agent built one contact sheet of the new covers next to the old ones, so I could see whether the titles still read and whether any product got lost on the dark background. After I approved the sheet, the apply step for each post was:
- Upload the new JPG through the REST API
- Set alt text and title to match the review
- Set it as the featured image
- Read the post back and confirm the featured media ID
- Fetch the live URL and check that og:image / twitter:image pointed at the new file (this site builds those from the featured image)
Old media items were left in the library on purpose. A revert that needs a deleted file isn’t a revert.
Why flip the rule at all
Own photos still matter. They’re how you prove the review isn’t a press kit rewrite. But a cover has a different job: it has to read at 200 pixels wide on a phone feed, next to ten other cards. Maker press shots are lit for that. Desk photos are lit for honesty inside the article.
Separating those jobs stopped me from forcing one photo to do both. The cover can be the clean identifier. The review body still gets my shot on my desk, with the cables and the mess that come with actually using the thing.
Ship checklist
- Write the source rule down before the agent starts: official press preferred on covers, own photos in the body, no generated gadgets.
- Reuse the HTML template and headless render path so layout stays in code.
- Download press images from the maker’s own site and log page URL, image URL, date and processing.
- Respect press terms that forbid alteration; keep those photos whole in a card.
- Snapshot posts, media, page HTML and hashes, and write
revert.pybefore any upload. - Approve a contact sheet of old vs new, not four files one at a time.
- Upload, set featured image, read back, and verify social tags from the live URL.
- Leave old media in the library so revert is one featured_media POST.
- Log the change in the site changelog with a rollback line.
FAQ
Doesn’t an official press photo make the review look sponsored?
Only if the article reads like a brochure. The cover identifies the product; the review still has to say what broke, what I returned and what I kept. Disclosure stays near the top when affiliate links are involved. A clean product shot isn’t a claim that the maker paid for the post.
Why not generate a “perfect” studio shot with an image model?
Because readers who own the device notice fake ports and melted logos in a second. A press photo is a real photo of the real product. A generated one is a drawing that happens to look similar. On a site that promises honest reviews of gear I own, that gap matters.
Could the whole pipeline stay on local hardware?
Yes. The template, headless Chromium and background removal already run on one box with no cloud image APIs. The AI agent writes and drives the scripts; the pixels stay local. That’s the same pattern as my local LLM homelab stack.
Closest related Builders notes?
My AI Agent Rebuilt Nine Blog Covers Without a Single AI Image is the pipeline this post inherits. 7 Rules for Letting an AI Agent Maintain Your Homelab is why backups come before uploads, and I Had an AI Agent Audit My Self-Hosted Blog for Setup Leaks explains why this note names no sites, servers or product models.
