Platform Role Seats — Setup Guide
The platform has 5 swappable AI-or-human role seats sitting above the writer fleet:
| Seat | What it does | Autonomous? |
|---|---|---|
| 🎭 Platform Director | Strategy. Holds platform goals; messages CM. | Yes (auto-message Manager) |
| 🎬 Content Manager | Operations. Runs the writer fleet (approve/promote/scaffold/review/publish). | Yes (5 action scopes) |
| 📈 Reader Lead | Post-publication. Featuring, rotation, surfacing. | Yes (3 action scopes) |
| 📣 Marketing Advisor | Growth bets. Weekly brief. | No — advisory-only by schema |
| 🛠️ Infrastructure Monitor | Error-log triage. Mute/resolve. | Yes (2 action scopes) |
Each seat can be held by an AI persona OR a human, with the swap atomic
- audit-preserving. This guide walks through one-time activation. For the architectural deep dives see the relevant sections of ../ai/INDEX.md (every endpoint, migration, and file path is catalogued there).
1. Apply the migrations
From the repo root:
supabase db push
The 5-seat system spans migrations 234 → 245:
| # | What it adds |
|---|---|
| 234 | content_manager_role + decisions + goals |
| 235 | content_manager_invites |
| 236 | platform_director_role + platform_role_messages (cross-role chat) |
| 237 | platform_director_decisions + platform_director_goals |
| 238 | platform_director_invites |
| 239 | reader_engagement_role + decisions + goals |
| 240 | marketing_advisor_role + decisions + goals (advisory-only schema — no scope flags) |
| 241 | infra_monitor_role + decisions + goals |
| 242 | reader_engagement_invites |
| 243 | marketing_advisor_invites |
| 244 | infra_monitor_invites |
| 245 | 4 union views (v_platform_roles, v_platform_role_decisions, v_platform_role_goals, v_platform_role_invites) — virtual unification for cross-role admin pages |
Verify a few of the role tables:
psql "$DATABASE_URL" -c "\dt content_manager_role platform_director_role reader_engagement_role marketing_advisor_role infra_monitor_role"
psql "$DATABASE_URL" -c "\dv v_platform_roles v_platform_role_decisions v_platform_role_goals v_platform_role_invites"
2. Set env vars
All env vars live on the backend (Railway in prod, .env in
apps/backend locally). Each seat has its own master switch — turn on
the ones you want active. Disabled seats stay in advisory-only mode
even if a persona is assigned (the brief generator doesn't fire and
the autonomous executor doesn't run).
| Var | What it controls | Cost cap |
|---|---|---|
CONTENT_MANAGER_ENABLED | Daily CM brief + autonomous executor (within scope flags + budget) | ~$0.001/day + per-action OpenAI cost |
PLATFORM_DIRECTOR_ENABLED | Daily Director brief + optional auto-message to CM | ~$0.001/day |
READER_ENGAGEMENT_ENABLED | Daily Reader Lead brief + autonomous executor | ~$0.001/day |
MARKETING_ADVISOR_ENABLED | Weekly Marketing brief (6-day idempotency window) | ~$0.001/week |
INFRA_MONITOR_ENABLED | Daily Infra Monitor brief + autonomous mute/resolve executor | ~$0.001/day |
All briefs are bundled into the runContentManagerBriefLoop tick
in . The
CM env flag is the master gate for the loop itself — if it's off, none
of the seat briefs fire. Each individual env flag gates its own seat's
brief within the loop.apps/backend/src/index.ts
Recommended ramp:
- Start with
CONTENT_MANAGER_ENABLED=true+PLATFORM_DIRECTOR_ENABLED=true. Let the CM ↔ Director chat loop run for a few days; observe the briefs in/dashboard/admin/ai-overview. - Add
INFRA_MONITOR_ENABLED=trueonce you trust the Monitor's recommendations (start with autonomous mode OFF — see §4). - Add
READER_ENGAGEMENT_ENABLED=trueonce there's enough engagement signal inseason_engagement_eventsfor the brief to be useful (low-traffic platforms get "no data" briefs). - Add
MARKETING_ADVISOR_ENABLED=truelast; weekly cadence so it builds up the slowest.
3. Assign personas to seats
Two ways to fill a seat: AI persona or human. AI personas are the default; humans take over via the invite/claim flow (see §5).
For each seat:
- Open the seat's dashboard:
/dashboard/admin/platform-director/dashboard/admin/content-manager/dashboard/admin/reader-engagement/dashboard/admin/marketing-advisor/dashboard/admin/infra-monitor
- Click Assign. Pick a persona (defaults to the AI tab).
- Constraints:
- Content Manager requires the persona to already be a platform
writer (
is_platform_writer = true). Promote one on/dashboard/admin/ai-personasfirst if needed. - All other seats accept any active persona — no platform-writer prerequisite, since these seats pick/advise rather than write.
- Content Manager requires the persona to already be a platform
writer (
- The seat is now AI-held but with scope flags + budget all at zero. Briefs will start filing on the next tick (once the env flag is on) but no autonomous actions fire until you opt in.
For a quick overview of all 5 seats at once visit /dashboard/admin/all-roles — shows holder, latest brief, pending recommendations, active goals, and outstanding invites per seat.
4. Opt into autonomous mode (scope flags + budget)
Most seats default to advisory-only — the brief generates recommendations but nothing executes without your click. To enable autonomous action:
- On the seat's dashboard, find the Scope & budget panel.
- Flip the relevant
can_*toggles ON. Each scope flag is independently reversible — flipping it off doesn't undo prior actions, it just stops new ones. - Set
daily_action_budgetto ≥ 1. Zero budget means the seat stays advisory regardless of scope flags. Start with 1 or 2 actions/day and ramp up once you trust the seat's choices. - Save.
Safety guarantees per seat:
| Seat | Autonomous actions | Reversibility |
|---|---|---|
| 🎬 CM | approve / promote / scaffold / review / publish | Each path has an inverse (e.g. approve ↔ unapprove). Reverse from the decisions log. |
| 🎭 Director | message_to_manager / set_manager_goals | Messages are advisory text; goals can be edited/paused. |
| 📈 Reader Lead | rotate_snippet / feature_cafe_book / feature_critique | Prior graphene_featured_snippet state captured in rationale tail for precise restore; toggles flip back. |
| 🛠️ Infra Monitor | mute_group / resolve_group | Mutes have explicit 7-day windows; resolved groups re-surface on next occurrence. Extra live re-checks: auto-mute only when the tuple has prior mute history; auto-resolve only when last occurrence is ≥48h old. |
| 📣 Marketing | (none — advisory by schema) | N/A — no executor service will ever ship for this seat |
Marketing Advisor is intentionally locked to advisory. The schema
has no scope flags or daily_action_budget column. AI-authored
outbound (emails, social posts, ads) has the highest blast radius +
worst reversibility on the platform; the human stays in the loop on
every outbound asset.
5. Human handoff via invite/claim
Every seat supports swapping the holder from AI to human (or human to human, or human back to AI). The mechanism is identical across all 5:
- Open the seat's dashboard. Find the Hand off to a human panel.
- Click ✉ Mint invite URL. Optionally add a note for the invitee (shown on the landing page) + an expiry (default 7 days; 0 = no expiry).
- Copy the URL and send it to the person. The URL is one-time and
robots: noindex. - On claim, the role's
held_by_persona_idis nullified andheld_by_user_idis set atomically. The previous AI holder's audit trail stays attributed to them; new briefs + decisions are attributed to the human going forward.
To audit live invite URLs across all seats: /dashboard/admin/invites — groups by state (active / claimed / rotated / expired) with copy buttons on active ones.
6. Where to operate day-to-day
| Page | Use case |
|---|---|
/dashboard/admin/all-roles | At-a-glance health for every seat |
/dashboard/admin/ai-overview | Chat with every AI seat from one page (hierarchical layout) |
/dashboard/admin/decisions-timeline | Reverse-chronological feed of every action across all 5 seats |
/dashboard/admin/goals | Every goal across all 5 seats sorted by urgency |
/dashboard/admin/invites | Every invite URL with state |
/dashboard/admin/<seat> | Per-seat dashboard for assigning, scoping, goal-setting, executing |
7. Cost notes
- Each brief: ~$0.001 on gpt-4o-mini.
- 5 briefs × 365 days = ~$1.83/year per AI-held seat, plus the CM's per-action OpenAI cost (approve = $0 / scaffold = ~$0.02 / etc.).
- Weekly Marketing Advisor: ~$0.05/year.
- Whole 5-seat pipeline running daily at scope-flags-off (advisory mode): ~$8/year of OpenAI.
If a seat's autonomous mode is on and budget is high, the dominant cost becomes the per-action LLM cost (scaffold, image-gen, etc.) — not the brief itself.
8. Disabling
To pause a seat without losing its history:
- Flip the env flag off in Railway (
<SEAT>_ENABLED=false). - Optionally vacate the seat via the Vacate button on its dashboard.
This deletes the role row but preserves decisions + goals + invites
via
ON DELETE CASCADEcleanup (so reassigning to a new persona later starts a fresh audit trail).
To re-enable: flip the env flag back on + assign a new persona.
For architectural detail, see the relevant sections of ../ai/INDEX.md. For per-seat code paths see:
,apps/backend/src/services/content-manager.ts,content-manager-brief.tscontent-manager-executor.ts+ briefplatform-director.ts+ brief + executorreader-engagement.ts+ briefmarketing-advisor.ts+ brief + executorinfra-monitor.ts— cross-role read endpoints powering the 4 admin pages aboveplatform-roles.ts