A friend and I recently started Aeternum↗, a small software studio. A new company needs to show up somewhere, and short-form video is where people spend their time. The problem is simple: between my apprenticeship and building DriveFlow, nobody on our side was going to open Canva every evening and make a post by hand.
So I automated TikTok carousel posts instead. Twice a day, a script reads the tech news, picks a story, writes six or seven slides with a source for every claim, renders them in our brand style and drops them into iCloud. I open the folder on my phone and post. That part works. This post covers how it's built, the mistakes it made along the way, and the numbers, which are not great.
What the TikTok carousel generator does#
The generator is a Node.js project with four steps. Each one is a separate command, so I can rerun a single step without paying for the others again.
- Fetch news. Seven RSS feeds (TechCrunch, The Verge, Ars Technica, MIT Technology Review, the Hugging Face blog, OpenAI News and the Google AI blog), the top 30 Hacker News stories, GitHub Trending and Anthropic's news page. Everything lands in a local SQLite database. A normal run stores or refreshes around 150 items.
- Curate. A language model picks the strongest stories and writes the carousel copy. It only gets items from the last seven days that haven't been used yet, capped at 80 and balanced across sources so one busy feed can't take over.
- Render. sharp↗ composes the slides: a 1080 × 1440 PNG for TikTok and a 1080 × 1350 JPEG for Instagram, because Instagram's publishing API only accepts JPEG and nothing taller than 4:5.
- Export. The reviewed batch is copied to
iCloud Drive/Aeternum Posts/<batch-id>/along with acaption.txt.
None of the news sources need an API key. The only paid part is the curation model, which is DeepSeek's deepseek-v4-flash running at temperature 0.3 in JSON mode. Publishing stays manual on purpose. I still want a human to press the button.
The schedule lives outside the repo. A Codex automation on my MacBook runs at 08:00 and 18:00 Zurich time. Before choosing a story, it checks TikTok's Creator Search Insights to see what people are searching for. After the scripts run, it looks at every rendered slide before exporting anything. The morning and evening posts are not allowed to cover the same company, product or search query.
Making the AI cite a source for every slide#
The first batch, on September 4, came from a prototype without any fact checking. The folder names tell you everything: openai-declares-the-agi-era-is-here, apple-s-new-ceo-promises-a-huge-launch. The copy read like a tabloid. A company account that invents drama is worse than no account.

The fix was to take quoting away from the model. The script splits each article into numbered passages of about 600 characters and numbers every image on the page. The model has to answer with IDs, not text:
{
"title": "ChatGPT Images 2.5 is up to 50% faster",
"hero_image_id": 2,
"evidence": [
{ "field": "title", "passage_ids": [0] },
{ "field": "slide_outline.0", "passage_ids": [3] }
]
}The script then turns those IDs back into the real passages and checks that they exist in the article. The model can't paraphrase its own evidence or make up an image URL, because it never writes either one. Every visible field needs an entry: the title, the hook, the caption and every body slide.
On top of that, plain code enforces the rules a model forgets:
- Three to five body slides, a title under 110 characters, body copy under 260.
- No em dashes, en dashes or exclamation marks anywhere in the visible text.
- Each image used once per deck at most.
- Exactly five unique hashtags in the caption, with exactly one
#aeternumor#aeternumsoftware.
If a draft fails validation, the model gets one repair attempt with the error message. After that, a second model call audits the draft against the article, looking for wrong units, changed dates, lost uncertainty and claims the source doesn't make. If the audit fails, there's one revision. If that fails too, the story is skipped. A skipped slot is better than a wrong post.
Rendering carousel slides without a design tool#
Every slide is built in code. The background is a generated noise field, the same artwork we use on the Aeternum website, ported to run on the CPU. Its seed comes from a hash of the article URL, so the same story always gets the same look and a rerender doesn't change anything I already approved.
Text is measured instead of guessed. The renderer tries the headline at 104 px, then 96, and keeps stepping down to 64 px until it fits the space. If it still doesn't fit at 64, rendering stops with an error telling me to shorten the copy. That's on purpose: shrinking text until it's unreadable on a phone would be worse.
Two rules keep article images from looking cheap. Images are never enlarged, so a small screenshot stays sharp and small instead of turning blurry. And a landscape press photo never gets stretched into a full-screen portrait cover. It's placed whole above the headline. A full-bleed cover only happens when the source image is at least 1080 × 1440.
Every batch writes a manifest.json with SHA-256 hashes of every file. The export command needs the exact batch ID and checks the hashes before copying. If a run fails halfway, it can never send yesterday's slides to my phone by mistake.
What broke in the first week#
The pipeline ran every day in its first week, and most of what I learned came from the runs that went wrong.
A 12-star repository made it into a deck. On September 7, Creator Search Insights showed huge demand around "AI memory tools" (4.77 million search popularity). The agent found a matching GitHub project and built a deck about it. The project had 12 stars, and the headline numbers came from its own README. The deck was attributed correctly, but "people search for this topic" doesn't mean "this repo deserves an audience". GitHub projects now need at least 500 stars, 100 Hacker News points or coverage from a real publication before they qualify.
The agent counted searches it never ran. Creator Search Insights has a search box. The agent typed a query, read the page and recorded the results, but it never pressed Enter, so the results belonged to the previous query. Now it opens the search URL directly and only counts a query once the page shows Results for "<query>" and the rows actually change.
Some days produced nothing. On September 6, 154 fetched items turned into zero drafts. OpenAI's page returned HTTP 403 to the script. The best Spotify story sat outside the 80-item candidate pool. One draft failed the evidence check even after a repair, and a Hugging Face demo page had too little readable text. I added a fallback text reader for pages that block direct requests, plus a POST_URL option that lets the agent pin a specific story it has already checked.
TikTok logged us out. On three runs in a row, Creator Search Insights redirected to a login page. The agent doesn't try to work around that. It records that search data was unavailable and picks stories on source quality alone.
The results, honestly#
On September 9, the agent pulled our TikTok numbers before choosing that evening's story. The account had seven posts:
| Post | Views |
|---|---|
| Astra code review benchmark | 1,239 |
| Sony XM4 headphones | 855 |
| Spotify's Portal token savings | 848 |
| DaVinci Resolve with Claude and Codex | 375 (after about 24 hours) |
| ChatGPT Images 2.5 | 283 (after about 9 hours) |
The seven-day overview showed 3.3K views, 45 likes, 14 comments, 31 shares and 25 profile views. That's a small sample from a brand-new account, so I don't read much into which topics won. Consumer products did seem to travel further than niche developer repositories, which is why the automation now leans toward recognizable products.
One detail stuck with me. The best post by a wide margin had the worst caption: "Astra's code review results." It sounds like a report heading. Afterwards I made the prompt write short, casual reactions like "Sony XM4 headphones are back", and none of those posts has beaten it so far. With seven posts that proves nothing, but the caption probably isn't the lever.
The real issue is simpler. These posts aren't original. Every slide summarizes an article that thousands of other accounts can summarize too. 25 profile views out of 3.3K views means people watched a carousel and moved on. They had no reason to care who made it. The automation solved "we don't have time to post", and it turns out that wasn't our actual problem. Our actual problem is that nobody knows who we are yet.
What I'd change next#
I'm keeping the pipeline. The research, fact checking and rendering save a lot of time, and a deck that has cited its sources is a better starting point than a blank page. What changes is what goes on the slides.
- Our own work over other people's news. DriveFlow and experiments like this one are things only we can post about. The renderer doesn't care where the text comes from.
- An opinion on every slide. "OpenAI says X" is a summary. "We tried X on a real project and here's what happened" is a reason to follow.
- Using the news feed as research, not as the post. Surfacing the day's stories and checking the facts is still useful for deciding what to talk about.
If you're building something similar, the order matters. Get the facts right first, because a fast pipeline that makes things up is a liability with your logo on it. Then work out why anyone would follow you, because no amount of automation will answer that for you. We're working on that second part now at Aeternum↗.







