Writer Fleet — Setup Guide
The Writer Fleet is an AI-persona-as-platform-writer + real-writer recruitment system. Promoted personas autonomously scaffold + author multi-chapter seasons on Graphene; humans can be invited (one-time URL) to inherit a persona's voice, body of work, and audience.
This guide walks through the one-time setup to turn the system on in local dev or prod. Once configured, day-to-day operation happens at /dashboard/admin/writer-fleet and /dashboard/admin/ai-personas.
For the architectural deep dive see the Writer Fleet section of ../ai/INDEX.md (every endpoint, migration, and file path is catalogued there).
1. Apply the migrations
Three migrations land the fleet schema. From the repo root:
supabase db push
The relevant migrations:
230_ai_persona_platform_writer.sql—is_platform_writerflag onai_personas+ partial index. Independent ofis_active.231_season_review_state.sql—review_stateenum onstory_seasons(unreviewed/changes_requested/approved) + reviewer audit columns + partial index for the review queue.232_ai_persona_invite.sql— invite-token columns (invite_token/invite_minted_at/invite_minted_by/invite_used_at/claimed_by_user_id/claimed_at) + unique partial index on the token.
If you also haven't applied 229_publish_promotes_draft_to_active.sql,
do that too — without it, persona seasons can be marked
is_published_to_graphene=true but stay status='draft' and never
appear on /graphene's "Other creators" row for anonymous visitors.
Verify the columns:
psql "$DATABASE_URL" -c "\d ai_personas" | grep -E 'is_platform_writer|invite_token|claimed_by'
psql "$DATABASE_URL" -c "\d story_seasons" | grep -E 'review_state|reviewed_by'
2. Set env vars
All env vars live on the backend (Railway in prod, .env in
apps/backend locally). The frontend reads nothing fleet-specific
beyond the standard NEXT_PUBLIC_API_URL / NEXT_PUBLIC_SITE_URL
already required by the app.
| Var | Default | What it controls |
|---|---|---|
WRITER_FLEET_ENABLED | unset (off) | Master switch for the autonomous scaffold cron. Set to true to turn the hourly tick on. With this off, scaffolding only happens via the admin "📖 Scaffold" button on /dashboard/admin/ai-personas. |
WRITER_FLEET_MIN_INTERVAL_HOURS | 24 | Fleet-wide cooldown — at most one new scaffold per this many hours across the entire roster. Cap on total spend. |
WRITER_FLEET_PERSONA_COOLDOWN_DAYS | 7 | Per-persona cooldown — each persona scaffolds at most once per this many days. Fairest-rotation across the roster. |
OPENAI_API_KEY | (required already) | Used by planPersonaSeason (writePersonaChapter ( |
Worst-case spend at the defaults: 1 scaffold per 24 h = $0.60/month fleet-wide (just planning; prose-fill + audio remain admin-gated).
Tune the cooldowns down if you want more output; tune up if you want to keep the slate sparse while you iterate on quality.
3. Promote your first persona
- Open
/dashboard/admin/ai-personas(signed in as a super-admin). - Pick a persona that already has some journal entries (the planner anchors on the last 5 entries for voice).
- Click ✦ Promote. The row gets a fuchsia "✦ Platform Writer"
chip; behind the scenes
PATCH /api/ai-personas/:id/platform-writerflips the flag.
Promotion is independent of is_active — a persona can keep
sim-writing journal entries (journal/mood/sleep/buzz/odessa/drops loop)
or be paused on that side without affecting the platform-writer role.
4. Generate the first season
You can either fire the autonomous cron or trigger manually:
Manual (recommended for the first run):
- On the promoted persona's row, click 📖 Scaffold.
- New tab opens at
/seasons/[id]showing the scaffolded 9-chapter plan (title, premise, genre, setting, per-chapter title + spine). Review state is👀 Unreviewed; publish is blocked until approved. - In the Writer Fleet review bar above the chapter list, click
✍️ Fill prose (~$0.09). Parallel per-chapter GPT-4o write
runs (
writePersonaChapter× 9). Wait 30–60 seconds. - Read through. If happy, click ✓ Approve — publish gate clears. If not, click ↻ Request changes, add a note, save; the season stays in the queue until you re-review.
- Once approved, use the existing publish strip to flip it live on /graphene.
Autonomous (after the manual flow is comfortable):
# On Railway (or your prod env), set:
WRITER_FLEET_ENABLED=true
Within ~1 hour of the env change being applied, the cron will pick
the most-eligible persona and scaffold for them. Track it at
/dashboard/admin/writer-fleet (status panel at the top shows the
next pick + cooling banner) or /dashboard/admin/system-health
(look for the writer_fleet_tick heartbeat label).
5. Recruit a real writer (optional, when the fleet is producing
content worth handing off)
- Open the promoted persona's row at
/dashboard/admin/ai-personas. - Click 🎫 Invite. A modal renders the one-time URL —
${SITE_URL}/invite/persona/${token}. - Copy the URL and send it to a candidate human writer (email, DM, whatever channel — it's just a URL).
- The candidate's flow:
- Lands on
/invite/persona/[token](robots:noindex, public). - Sees the persona's profile + body of work as posters.
- Clicks ✨ Claim this voice → routes through
/auth/signup?next=...&claim=1. - After signup, lands back on the invite page with the confirmation modal.
- Clicks ✦ Claim it → atomically transfers seasons
(
story_seasons.owner_user_id), earnings (creator_earnings.creator_user_id), follows (writer_follows.writer_user_id), and swaps the writer-hub handle (profiles.username) onto the claimant's account. - Redirects to
/writers/[inherited-handle]— now owned by the human, with the persona's full body of work surfaced.
- Lands on
The persona row gets claimed_by_user_id + claimed_at set,
is_platform_writer=false, is_active=false. The invite URL stops
working (410 Gone for the next visitor).
6. Daily ops
- Review queue:
/dashboard/admin/writer-fleet— tab filters (All / Pending review / Approved). Click any row to open the season page where the review bar carries the actions. - Cron health:
/dashboard/admin/system-health→writer_fleet_tickheartbeat. Status:okwhen the tick ran,errorif the planner or DB write failed. Metadata fields:status(scaffolded / fleet_cooling / no_eligible / no_personas / error),persona,season_id,cost_usd. - Cost tracking: the fleet-status panel at the top of
/dashboard/admin/writer-fleetshows scaffolds-this-week + the estimated $ spent. Add prose-fill + audio render costs on top (those land when an admin clicks the buttons).
7. Disabling / pausing
- Pause one persona:
/dashboard/admin/ai-personas→ ✦ Demote (flipis_platform_writer=false) or ⏸ Pause (flipis_active). - Pause the whole fleet cron: set
WRITER_FLEET_ENABLED=false(or unset) on the backend env + restart. Existing seasons keep working; no new scaffolds fire autonomously. The admin manual scaffold button still works. - Tighten spend during quality iteration: bump
WRITER_FLEET_PERSONA_COOLDOWN_DAYS=30and/orWRITER_FLEET_MIN_INTERVAL_HOURS=72so the cron fires sparingly while you tune the planner prompts.
Troubleshooting
- Admin clicks Scaffold and gets "Persona must be promoted":
the persona doesn't have
is_platform_writer=true. Open/dashboard/admin/ai-personas, click ✦ Promote on that row, retry. - Publish button greyed out / 403 from PATCH /:id/published:
the season is in the approval pipeline (
review_stateisunreviewedorchanges_requested). Use the review bar above the chapter list to Approve first. - "Other creators" row on /graphene doesn't show the persona's
season: migration 229 isn't applied. Without it, publish flips
is_published_to_graphene=truebut leavesstatus='draft', and the public slate query filters onstatus IN ('active', 'completed'). Apply 229 and republish. - Invite URL returns 410 Gone: the token was already used, the persona was demoted, or the persona was claimed by someone else. Mint a fresh invite from the admin row.