Editorial Calendar: 100 Business Days, 2-Article Day Cap, Auto-Publish on Schedule
Studio's editorial calendar plans, queues, writes, and publishes content on a deterministic schedule. One 100-topic batch plans 100 business days. A cron every 5 minutes auto-publishes finished articles when their scheduled time arrives.
One of 48 criteria in AEO Rank, the citation-readiness score we run against every site we audit.
By Alex Shortov
Quick Answer
The editorial calendar lives at studio.aeocontent.ai/calendar and is the planning + approval surface for outbound content. Operators bulk-plan from a 100-topic batch into 100 business days, with planner constraints that front-load top scores into the first 20 days and spread service coverage across every 10-day window. Per-domain settings control day-cap (default 2 articles/day), lead time (24h), default publish time (09:00 UTC), and whether to skip weekends. A GitHub Actions cron polls every 5 minutes for due articles and pushes them to the connected CMS through a two-phase claim that only commits when the destination is still active.
Audit Note
In our audits, we've measured Editorial Calendar: 100 Business Days, 2-Article Day Cap, Auto-Publish on Schedule on live sites, we've compared implementations, and we've audited the gaps that keep scores low.
How does Studio plan a content calendar from a topic batch?
Studio plans by running editorial-calendar-planner as a pure function that places each topic on a business day using your domain settings and topic scores.
What constraints does the calendar enforce when distributing topics across days?
The planner front-loads top scores into the first 20 days, requires 4+ promoted services per 10-day window, and limits each cluster to 3 placements per 15 days.
How does auto-publish actually fire on a schedule?
Auto-publish runs as a GitHub Actions cron every 5 minutes, claiming due rows in two phases so a disconnected CMS never triggers a half-publish.
Can I require manual review for some articles and auto-publish others?
Yes, autopublish is tri-state per article, so some pieces ship automatically on their slot while others wait for human review on the same calendar.
What happens if a scheduled article fails to write or publish?
Failed articles surface as explicit states like write_failed or publish_failed instead of silently retrying, so operators see exactly what broke and when.
Summarize This Article With AI
Open this article in your preferred AI engine for an instant summary and analysis.
What this article answers
- How does Studio plan a content calendar from a topic batch?
- What constraints does the calendar enforce when distributing topics across days?
- How does auto-publish actually fire on a schedule?
- Can I require manual review for some articles and auto-publish others?
- What happens if a scheduled article fails to write or publish?
Key takeaways
- One 100-topic batch from /topic-ideas plans 100 business days - the planner is a pure function that runs the same way every time, so the same batch + the same domain settings always produce the same calendar.
- Day-cap (default 2 articles/day), lead-time (24h target), default publish time (09:00 UTC), and skip-weekends are per-domain settings stored in
aeo_domain_settings- operators tune the cadence without touching the calendar code. - Planner enforces 4 hard constraints: freshness topics in week 1, top-score topics in first 20 business days, every 10-day window covers 4+ promoted services, no cluster gets more than 3 placements in any 15-day window.
- Auto-publish is a 5-minute GitHub Actions cron - two-phase claim pulls due rows, then updates only ones where the CMS connection is still active, so disconnect events don’t trigger half-publishes.
- Lifecycle states are explicit:
planned→queued→generating→awaiting_review(orscheduled) →publishing→published. Failure overlays (no_balance,write_failed,publish_failed,missed) surface as separate states so operators see what specifically broke. - Per-article autopublish is a tri-state (on / off / default) - some articles auto-ship, others wait for human review, all on the same calendar without separate workflows.
Why a Calendar Instead of “Generate Whenever”?
A calendar makes publish cadence the source of truth, so AI engines watching recency see consistent authority instead of the chaos of burst-then-silence article batches.
The standard AI-content workflow is “click generate when you remember.” That sounds flexible until you try to run an actual content program against it. You forget for a week, then panic-generate 6 articles in one afternoon, then publish them in a burst that confuses your sitemap freshness signal, then forget again. AI engines that watch publishing cadence (Google AI Overviews, Perplexity, ChatGPT’s recency bias) see chaos instead of consistent authority.
A calendar fixes this by making the publish cadence the source of truth, not the generate moment. You tell the system “publish 2 articles per business day starting Monday” and the system handles writer queuing, scheduling, and CMS push timing so that the cadence is what it claims to be. Articles that fail to generate get retried; articles that succeed wait quietly until their scheduled publish time fires.
The Studio calendar’s job is to make that promise reliable. It is not a fancy view on top of generation - it is the scheduling layer that drives generation and publishing on a deterministic clock.
How Does the Planner Distribute 100 Topics Across 100 Days?
The planner runs a deterministic pure function that spreads topics across business days, prioritizing freshness items in week one and respecting per-domain pacing constraints.
When an operator clicks “Plan from topic batch” in the calendar, the planner runs a pure-function algorithm in editorial-calendar-planner.ts that places each topic on a specific business day. The same batch + same per-domain settings always produces the same placement - the planner is deterministic and re-runnable.
Five constraints shape the placement:
1. 100 business days from one 100-topic batch. The planner spreads across business days only - weekends are excluded by default and can be re-enabled per-domain via skip_weekends. Holiday awareness is on the roadmap; today’s planner treats every weekday as plannable.
2. Freshness topics land in week 1. Topics flagged as time-sensitive (industry news, recent product launches, breaking-market signals) get placed in the first 5 business days. Stale freshness content is worth nothing - a topic that says “the new pricing model just announced” loses value every week it sits in the queue.
3. Top-score topics front-load into the first 20 business days. Topic ideas come ranked by AEO opportunity score (visibility gap × search volume × competitive softness). The highest-scoring topics ship first so the content program demonstrates ROI early - operators see lift within the first month, not three months in.
4. Every rolling 10-business-day window covers 4+ promoted services. If a customer has services A, B, C, D, E and the calendar suddenly puts 10 consecutive articles about service A, AI engines start narrowing the customer’s topical authority to one service. The planner forces breadth - every rolling 2-week window covers at least 4 different services.
5. No cluster gets more than 3 placements inside a 15-business-day window. Avoids over-saturating any single topical cluster too fast (cannibalization risk - see content-cannibalization).
A soft constraint also applies: no more than 2 consecutive topics from the same class when alternatives exist. Prevents the calendar from queuing up “FAQ post, FAQ post, FAQ post” three days in a row when other article types are available.
What Per-Domain Settings Drive the Cadence?
The aeo_domain_settings table holds the policy controls for one customer’s content cadence. Five fields shape calendar behavior:
| Setting | Default | What it does |
|---|---|---|
autopublish_enabled | false | Domain-level autopublish switch. Per-article override (brief.autopublish) wins when set. |
lead_time_hours | 24 | Worker-claim target - article gets queued for writing 24 hours before scheduled publish time. |
default_publish_time | '09:00' | UTC time of day that new slots default to. Per-article override possible. |
max_articles_per_day | 2 | Hard cap. Drag-drop, manual create, bulk-plan all check via assertDayCapacity. |
skip_weekends | false | Auto-distribute skips Sat/Sun when on. |
Lead-time is a target, not a gate. If an operator on Tuesday afternoon plans an article for Wednesday at 09:00, the candidate scheduled_for lands in the past - the worker claims immediately rather than failing the job. The minimum save-to-publish gap is governed separately by ARTICLE_WRITER_BUFFER_MINUTES = 120 - so the earliest a freshly-saved slot can publish is 2 hours from save time.
Day-cap is enforced at every entry point: drag-drop, manual create, mass-schedule from topic batch, and schedule-from-preview (when scheduling an already-written article onto the calendar). Operators can never exceed the cap silently.
What Are the Lifecycle States and What Triggers Each?
Every cluster article moves through a state machine. The phase enum is what the UI cards render and what auto-publish polls against.
| Phase | Trigger | UI display |
|---|---|---|
planned | Slot reserved, nothing started | Empty card with title + publish date |
queued | Operator confirmed, writer hasn’t claimed yet | ”Queued for writer” |
generating | Writer worker is running | ”Writing…” with progress |
awaiting_review | Article written, autopublish=off | ”Ready to review” |
approved | Article written, autopublish=on, no future publish_at | ”Ready to publish” |
scheduled | Article written, scheduled_publish_at is in the future | ”Publishing at 09:00” |
publishing | CMS push in flight | ”Publishing…” |
published | Live on the destination | URL + published time |
Failures live as overlay markers on the existing phase rather than terminal states - so operators see both “what stage” and “what specifically broke”:
no_balance- worker found 0 article credits at claim time. Customer needs top-up or upgrade.write_failed- generation failed (rare; the AEOPageRank repair loop catches most quality issues before this state).publish_failed- CMS push errored (auth issue, repo unreachable, validation rejected).missed- scheduled publish window passed without firing (shouldn’t happen; if it does, manual intervention needed).
Operators see failure overlays in red on the calendar card and a top-right toast on login surfaces recent no_credits failures specifically.
Each article moves through five lifecycle states in the editorial calendar, each driven by a different trigger.
| State | Trigger | Next Step |
|---|---|---|
| Planned | Topic added to calendar | Awaits generation slot |
| Generating | Daily slot opens | Writer pipeline runs |
| Drafted | Pipeline completes | Awaits review or auto-publish |
| Published | Auto-publish cron or manual | Live on client CMS |
| Skipped | Day cap reached or block | Rolls to next slot |
How Does the Auto-Publish Cron Actually Fire?
A GitHub Actions workflow hits the Studio cron endpoint every 5 minutes, running a two-phase claim that selects due rows and updates only CMS-authorized articles.
The cron is a GitHub Actions workflow at .github/workflows/cron-auto-publish.yml that runs every 5 minutes. It hits a Studio endpoint at /api/content/calendar/cron/auto-publish-articles with a constant-time Bearer $CRON_SECRET header. The endpoint is rate-limited by the cron itself - no concurrent runs.
Inside the endpoint, the auto-publish runs as a two-phase claim:
-
Phase 1 - SELECT due rows. Pull cluster articles with
execution_id IS NOT NULL(article written) ANDscheduled_publish_at <= NOW()(publish time arrived) ANDdisplay_status != 'published'(not already shipped). This is a read-only inventory of what is theoretically due. -
Phase 2 - UPDATE only CMS-active rows. Of the due rows, filter to ones where the domain still has an active CMS connection. The UPDATE clears
scheduled_publish_at(idempotency - if the cron runs twice for the same row, only the first wins) and triggers the publish through the standard publish path.
The two-phase shape is important because disconnecting a CMS connection between Phase 1 and Phase 2 doesn’t strand articles in a half-published state. The article stays in scheduled phase, its scheduled_publish_at keeps pointing at the past, and when the operator reconnects the CMS the next cron tick picks it up correctly.
Manual dry-run is supported via ?dry=1 on the endpoint - returns what would be published without actually firing the CMS push. Useful when debugging schedule misses.
Can I Mix Auto-Publish and Manual Review?
Autopublish is tri-state per article (true, false, null), so customers can ship most articles automatically while routing legal-adjacent topics through explicit human review.
Yes - autopublish is a tri-state per article, not a binary domain switch:
brief.autopublish = true- article ships automatically on its scheduled publish time, regardless of domain defaultbrief.autopublish = false- article waits for human review even if the domain default is autopublish=onbrief.autopublish = null(default) - uses the domain’sautopublish_enabledsetting
This means a customer can have most articles auto-ship for speed, but flag specific articles (sensitive topics, brand announcements, anything legal-adjacent) for explicit review without changing the domain-level cadence. The calendar card shows the effective autopublish state so operators always know which articles are about to fire vs which are waiting.
Per-domain default toggles in CalendarToolbar so an operator can flip the whole site between “ship everything automatically” and “review everything” in one click. The setting is reflected immediately in newly-planned articles; existing planned articles keep their per-article override.
How Does the Calendar Connect to the Rest of the Pipeline?
The calendar is the planning surface, but it does not write or publish articles itself. It hands off to two existing systems:
Writer queue (how-aeo-articles-are-built). When a calendar slot transitions from planned to approved, enqueueArticleJobs inserts a row into aeo_article_jobs. The terminal-server article queue claims it 15 seconds later and runs the full phased generation pipeline. The resulting execution_id syncs back to aeo_cluster_articles.execution_id so the card can switch from “queued” to “generating” to “awaiting_review.”
CMS publisher (how-aeo-publishes-to-your-cms). When auto-publish fires (or operator clicks Publish manually), the CMS publish route runs. The same 4-path publisher used everywhere else - github-content, nextjs-github, WordPress admin, WordPress plugin - is what the cron invokes. No special calendar publish path exists.
This separation is intentional. The calendar handles when and how often. The writer handles how. The publisher handles where. Each layer is independently testable and replaceable.
What This Means for AEO Score
Consistent cadence lifts Freshness Signals, Sitemap Completeness, Topic Coherence, and Cluster Internal Linking, all weighted criteria in the AEO Rank that compound over time.
Consistent publishing cadence directly affects multiple AEO criteria:
- Freshness Signals (Trust & Authority pillar) - the AI Discovery and Trust pillars reward sites with steady recent publishes. A cold sitemap signals abandonment.
- Sitemap Completeness (AI Discovery pillar) - the publish path regenerates sitemap.xml in the same commit as the article, so new content is in the sitemap at the moment of publish.
- Topic Coherence (Answer Readiness pillar) - the planner’s “4+ services per 10-day window” constraint maintains topical breadth without losing focus.
- Cluster Internal Linking (Trust & Authority pillar) - the cluster-aware scheduling means related articles publish in proximity, which the article pipeline uses to seed internal links between them.
Customers running the calendar with autopublish on typically see AEO Site Rank movement within 30-60 days of consistent publishing - the compound effect of fresh content, expanding sitemap, and improving topical authority.
External Resources
- AEO Topic Ideas - https://www.aeocontent.ai/knowledge/automated-cluster-builder
- GitHub Actions cron syntax - https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
- Schema.org Article datePublished - https://schema.org/datePublished
- Studio Editorial Calendar - https://studio.aeocontent.ai/calendar
Related topics
Key takeaways
- One 100-topic batch from /topic-ideas plans 100 business days - the planner is a pure function that runs the same way every time, so the same batch + the same domain settings always produce the same calendar.
- Day-cap (default 2 articles/day), lead-time (24h target), default publish time (09:00 UTC), and skip-weekends are per-domain settings stored in aeo_domain_settings - operators tune the cadence without touching the calendar code.
- Planner enforces 4 hard constraints: freshness topics in week 1, top-score topics in first 20 business days, every 10-day window covers 4+ promoted services, no cluster gets more than 3 placements in any 15-day window.
- Auto-publish is a 5-minute GitHub Actions cron - two-phase claim pulls due rows, then updates only ones where the CMS connection is still active, so disconnect events don't trigger half-publishes.
- Lifecycle states are explicit: planned → queued → generating → awaiting_review (or scheduled) → publishing → published. Failure overlays (no_balance, write_failed, publish_failed, missed) surface as separate states so operators see what specifically broke.
- Per-article autopublish is a tri-state (on / off / default) - some articles auto-ship, others wait for human review, all on the same calendar without separate workflows.