This is a submission for the Hacktoberfest Open-Source AI Challenge: Slow and Steady.
I set a timer and did the thing I keep putting off: I came up with five DEV post ideas, each with a title, tags and a rough outline.
It took 67 minutes.
I'm a data science student. I want to post regularly, but between coursework and everything else, an hour of staring at a blank "what should I write?" list is exactly the hour I don't have. So I built something that does that hour for me while I'm asleep, and then tells me honestly which ideas are worth my time.
Sometimes the honest answer is "none of them. Go back to sleep."
What I Built
Slow Post is a local app that plans next week's DEV posts overnight.
- I type a short brief (what I want to write about) and either click Start or schedule it for tonight.
-
Gemma 3 4B, running on my laptop through Ollama, drafts five posts: title, tags, cover idea, outline, reading time and a suggested day and hour.
-
TabPFN-3.5, also running locally, scores each draft: what's the chance a post like this gets any reactions? It learned that from 44,428 real DEV posts.
- Drafts that aren't Strong get one rewrite from Gemma, and TabPFN decides whether the rewrite is actually better.
- In the morning I open the app to "Good morning. Your run is ready." and a short verdict: how many strong ideas this week, or "Nothing stands out this week. Go back to sleep."

Four steps, and the one number I had to earn before I was allowed to show it.
The rule I built everything around: Gemma writes, the data decides. The language model never gets to grade its own homework.
Demo
A one-minute walkthrough: the backtest, the Instagram test case, a real run, and the morning plan.
The first dataset had nothing to learn, and I'm glad it said so
Slow Post didn't start as a DEV tool. My first idea was to plan Instagram posts from an account's own history, since that's the kind of work I do. A real account's export wasn't available to me, so I used a public Instagram dataset from Kaggle and picked one account with 1,599 posts.
Before building anything that makes predictions, I made the app answer one question first: does this history actually predict anything? It runs a time-ordered backtest. It trains on older posts, tests on newer ones, and compares the result with simply guessing the most common outcome.
For the Instagram account, the answer was no.

The verdict is computed, not scripted. ROC AUC was 0.526, and no single feature showed a clear difference once I corrected for testing many at once.
That could have been the end of the project. Instead it became the most important feature: a prediction tool that tells you when it can't predict. I kept the Instagram account in the app as a permanent test case, and went looking for data that actually had a pattern in it.
The obvious candidate was right in front of me: DEV itself.
44,428 DEV posts, and a modest, real signal
Slow Post fetches posts through DEV's documented public API (/api/articles). It makes about one request per second, only fetches posts at least 14 days old (so the reaction counts have settled), and keeps just 8 fields: id, time, title, tags, reading time, whether there's a cover, reactions and comments. No author names or usernames are stored. The data stays on my laptop in a git-ignored folder, and this article only shows totals.
Most DEV posts get zero reactions. Only 15.6% of the 44,428 posts got any. So the question Slow Post asks is simple: will this post get at least one reaction?
The features are things you know before you publish, computed by plain code: weekday, hour, reading time, number of tags, main tag, cover image (yes or no), title length, and whether the title has a question or a number.
TabPFN is trained on 1,000 older posts and tested on 2,000 posts from the newest week. Here's what it found:
Result (test week) |
Value |
Overall share with any reactions
21.6%
Share among TabPFN's top 10% picks
36.0%
Lift, top 10%
1.67× (95% CI 1.37–1.97)
Lift, top 20%
1.38× (95% CI 1.18–1.56)
ROC AUC
0.588 (0.5 is chance)
Balanced accuracy
0.544 vs 0.500 for the baseline
That's a weak but real signal. It's good for ranking ideas, not for promising anyone results. The app says exactly that, in those words.
Did TabPFN actually earn its place?
Fair question. Any model can beat "always guess no". So the Backtest page puts TabPFN next to two ordinary models, using the same 1,000 training posts and the same test posts.

The app writes its own conclusion, including the part that isn't flattering.
-
Against gradient boosting, TabPFN clearly wins: AUC +0.059, with a paired 95% interval of +0.034 to +0.084.
-
Against logistic regression, it's a tie: AUC +0.014, with an interval of −0.007 to +0.035. Logistic regression even has slightly higher balanced accuracy (0.555 vs 0.544).
One caveat: the simple models use ordinary default settings, with no tuning. TabPFN had no tuning either. With zero training loop on my side and only 1,000 rows, it beat gradient boosting and matched logistic regression, and it had the best top-10% lift (1.67× vs 1.53× and 1.35×), though the intervals overlap.
I'd rather show you a tie than hide one.
Three more checks, because one number isn't enough
1. The honest split, not the flattering one. On a random 80/20 split, TabPFN looks much better: AUC 0.647 and a top-10% lift of 1.96×. But that lets the model learn from the same weeks it's tested on. A plan is written before the week starts, so the time-ordered result (0.588) is the one I report.
2. Calibration. When the model says 20%, does 20% happen?

Not quite. On average it predicted 16% and 22% really got reactions. So the percentages are a ranking, not odds, and the UI says so.
3. Is "add a cover image" real, or is it just long posts? Cover images and longer reading times overlap, so I checked each one while holding the other steady.

The cover gap holds in every reading-time group (odds ratio 2.21 unadjusted, 2.12 adjusted). The reading-time gap shrinks once cover is accounted for (1.39 → 1.25).
This is still correlation. A cover image doesn't cause reactions. Posts with covers might simply come from people who put more care in. That's why the app never says "add a cover and you'll get more reactions." More on that wording below.
How a night's run works

Two open models, one rule: Gemma never scores anything.
1. Brief. I describe what I want to write. I can click Start, or click "Schedule for tonight", which creates a one-time Windows Task Scheduler job. It only does that after I confirm.

Pick a date and time, read the exact command it will run, tick the box. Nothing is scheduled until I confirm, and nothing is ever posted for me.
2. Gemma drafts. Gemma 3 4B returns five drafts as structured output, validated against a schema. Gemma suggests the content. Plain code computes the features, so Gemma can't, say, claim a draft has a cover when it doesn't.
3. TabPFN scores. Each draft gets a chance of reactions and a label, and both thresholds come from the backtest:
-
Strong: at or above the model's top-20% line (0.200). In the test week, 29.8% of those posts got reactions vs 21.6% overall.
-
Too close to call: in between. 21.1% got reactions, which is basically average, so I renamed it from "Okay" to say that plainly.
-
Weak: below the training base rate (0.141). 18.1% got reactions.
4. What-if, split in two. For each draft, the app changes one thing at a time (day, hour, reading time, cover, main tag) and rescores it. The results go into two groups:

"Backed by the data" only counts a change if the real history agrees with the model's direction. Everything else is labelled "The model's guess".
This split exists because of a mistake I caught during the build. Early on, every draft's top suggestion was "add a question mark to the title." The model liked it, but the real data showed no clear effect once I corrected for testing many features. That's the model's opinion, not a fact, so now it's labelled as a guess. The wording also avoids causation: "In the data, posts like this with a Thursday post got reactions more often (17% vs 16%)", not "post on Thursday".
5. Gemma revises, TabPFN judges. Every draft that isn't Strong gets one rewrite. Gemma sees the draft, its score and the data-backed evidence with the real rates. Code recomputes the features, TabPFN rescores, and whichever version scores higher is kept.

Before and after, side by side. The drafts in my screenshots came from test briefs about Slow Post itself, so yes, it planned posts about itself.
6. The verdict. If at least one draft is Strong: "N strong ideas this week." If none are:

The brief: an internal changelog for a private build tool, a 25-minute read, written only for the maintainers. Slow Post's advice: don't bother.
For a tool built for lazy people, "you don't need to do anything this week" is the best possible output. It's also the one most tools would never give you, because they're built to always hand you something.
I tested the overnight part for real
A scheduled run isn't worth much if it only works when I'm watching. So I created a real one-time task a few minutes ahead with a 2-draft brief, then left it alone. It started on time, wrote its run folder (backtest, drafts, plan CSV, log), finished in 268 seconds with "2 strong ideas this week", and the Plan page opened it under "Good morning." Then I deleted the task.
The Lazy Payoff
|
By hand |
With Slow Post |
My time
67 minutes (timed)
About 2 minutes to type a brief
Machine time
—
7–11 minutes across my real runs (399–668 s), while I do something else
What I get
5 ideas, no idea which are good
5 drafts, each labelled, with evidence, plus a verdict
On a CPU-only laptop (16 GB RAM, Intel Iris Xe, no GPU), a full 5-draft run took 668 seconds: 254 drafting, 112 backtest, 38 scoring, 219 revising and 44 rescoring. Overnight, none of that is my time.
How I Built It
The open-source AI at the core:
-
Gemma 3 4B (
gemma3:4b via Ollama) drafts and revises. In my first test it produced a schema-valid draft on the first try, in 63.8 seconds, on CPU.
-
TabPFN-3.5, Prior Labs' tabular foundation model, run locally, never through the cloud client. You give it rows with known outcomes, and it predicts new rows with no training loop. On my laptop, fitting on 1,000 rows took 0.5 seconds and predicting 300 took about a minute, so it scores in batches.
The stack: Python 3.14, Streamlit, scikit-learn for the comparison models, and Windows Task Scheduler for the overnight runs.
How the work was organised: I built Slow Post with Claude Code as my coding agent, following a research → PRD → technical design → AGENTS.md workflow. Then I built it in small milestones: plan, approve, build, verify. A few rules in AGENTS.md mattered a lot:
- Never use the hosted TabPFN client. Everything stays local.
- Never run Gemma and TabPFN at the same time. With 16 GB of RAM and a browser open, they would fight for memory.
- No post-publication information in the features. Reactions are only ever the label.
The repo has 104 fast tests, covering things like "no future rows in training" and "thresholds come from training rows only".
What changed after I saw results (all of it is logged). I tried to fix the analysis choices in advance, but some changed once I saw numbers, and you should know which ones. docs/analysis-log.md tags every choice as fixed in advance, chosen from the data's distribution, or changed after seeing results. The ones in the last group:
- At the default 0.5 cutoff, the model answered "no reactions" for every post, so I moved the cutoff to the training base rate (0.141).
- The test window changed from the newest 1,500 posts (only two days) to a random 2,000 from the newest 7 days.
- The verdict rule was written after the first backtest, so it uses AUC as well as balanced accuracy.
- The "Strong" line was set on the same test posts it's measured on, so the 29.8% for Strong drafts is probably a little optimistic.
The outcome itself ("at least one reaction") was picked from the label distribution, not from model results. With three bands, the top band held only 1–4% of posts.
Code
Slow Post learns from a post history, drafts next week's posts overnight on your laptop with Gemma, and flags the drafts that are likely to underperform with TabPFN. It reports honestly how well that works: every prediction is backed by a time-ordered backtest against a "most common outcome" baseline, and if a history holds no signal, the app says so.
Everything runs locally: no cloud AI, no telemetry. Built for the DEV Hacktoberfest challenge. The demo source is real DEV posts; a public Instagram sample is the "no signal" test case.

Walkthrough (Data, Backtest, Brief, Plan, Download): docs/slowpost-walkthrough.gif. More screenshots are in docs/screenshots/.
Headline results
All numbers come from a time-ordered backtest: train on a random sample of up to 1,000 older posts, test on up to 2,000 posts sampled from the newest 7 days. Intervals are 95% bootstrap intervals.
DEV posts (44,428 public posts from…