Marketing Studio research
The Marketing Studio is a new workspace within studio.bezeliq.ai that surfaces the existing five-agent marketing pipeline (Nova → Harper/Jade/Rosa → Ivy) through a UI designed for creative direction, not approval queue management. Today the pipeline runs completely blind: Nova writes a prose weekly_plan.md to Google Drive, Harper/Jade/Rosa write versioned content files back to Drive, and Justin receives an inbox exec task to select versions. Ivy then stages the selected file to a published/ subf
| risk | mitigation |
|---|---|
| [RESOLVED] Unipile LinkedIn post creation — previously flagged as critical-path blocker | Justin confirmed: Unipile supports LinkedIn post creation via POST /api/v1/posts/ with account_id, text, and attachment support. No fallback needed. Implementation task: add createPost function to scripts/lib/unipile.js. Not a blocker. |
| X/Twitter API tier — Free tier does not allow POST /2/tweets | Confirm Justin's current X API tier before blueprint. If Free tier, upgrade to Basic ($100/month) is required before V1 ship. Architect the publish job with graceful 429/403 error handling and retry queue regardless of tier. This is the one remaining unresolved question. |
| Ivy persona explicitly stages-only — contradicts Justin's approved intent that auto-publish is IN V1 | Ivy persona must be updated before or alongside the auto-publish implementation task. Blueprint must include an Ivy persona update task wiring LinkedIn (Unipile), X (X API v2), blog (git commit + deploy hook), and YouTube staging. Do not allow V1 to ship with Ivy still staging-only. |
| Nova persona and output format change (prose → CAMPAIGN.json) must ship simultaneously with Studio Campaign view | Nova cannot switch to JSON output until the Studio UI is ready to consume it. Blueprint must sequence Nova persona update after Campaign view is built and tested. In the interim, Nova continues writing weekly_plan.md. Do not update Nova's persona ahead of the UI. |
| content_assets schema migration must not break existing pipeline agents | Both new columns are additive (nullable body text, boolean justin_edit default false). Confirmed no existing agent (Nova, Harper, Jade, Rosa, Ivy) references these columns. Run migration with IF NOT EXISTS guard before Studio UI is built. Rollback is dropping two nullable columns — zero data loss. |
| Fal.ai cost overrun if Rosa is triggered repeatedly on the same piece_id | Rosa checks content_assets for existing graphic rows before generating. If 6 graphic rows with status >= staging exist for the piece_id, skip generation. Add spend alert on Fal.ai account dashboard at $5/week threshold (~20× expected cost). |
| Blog deploy hook URL missing from environment config at V1 ship — blog auto-publish silently fails | The deploy hook URL must be added to environment config (Supabase secrets or .env) before Ivy's blog publish function is deployed. Blueprint must include a pre-ship checklist item: confirm BLOG_DEPLOY_HOOK_URL is set and returns 200 on test POST. |
| YouTube content counted toward 14+/week but requires video production — volume target may be unachievable if video files stall | In V1, YouTube pieces are concept+description text artifacts counted as produced pieces regardless of whether video files are created. The 14+/week success metric is measured at the piece-produced-and-staged level, not video-uploaded level. Revisit YouTube video auto-upload in V2 when video production workflow is defined. |
| Rosa image style selection requires per-content-type classification logic not currently in Rosa's persona | Rosa's persona update must include an explicit style selection rule: photorealistic for product/lifestyle, illustrated for concepts, typographic for quotes/stats, data-viz for metrics. The piece type classification must be derivable from content_assets columns (channel + angle pattern). Blueprint must define this mapping explicitly — Rosa's style selection is rule-based, not open-ended judgment. |
- OPEN — X API tier: Justin's current X/Twitter API tier (Free, Basic, Pro) must be confirmed before V1 blueprint. Free tier blocks POST /2/tweets. Upgrade to Basic ($100/month) required if currently on Free tier.
- RESOLVED — Unipile LinkedIn: Confirmed. Unipile supports POST /api/v1/posts/ with account_id, text, attachment. No fallback needed. createPost function to be added to scripts/lib/unipile.js.
- RESOLVED — Blog CMS: Eleventy static site at dreamborn.ai. No CMS API. Auto-publish = git commit .md file + Cloudflare Pages/Netlify deploy hook. Blueprint must include this mechanic.
- RESOLVED — Fifth channel: YouTube is the fifth channel (blog, substack, linkedin, x, youtube). YouTube pieces in V1 = video concept + description text artifact. Full video auto-upload is V2. 14+/week target includes YouTube concept pieces.
- RESOLVED — Image style: Rosa selects per content type. Photorealistic for product/lifestyle, illustrated for concepts, typographic for quotes/stats, data-viz for metrics. Variety signals human creative direction.
- RESOLVED — Voice edit enforcement: Two paths on personal channels — (a) Edit+Approve (primary CTA), (b) Approve-as-is (deliberate secondary action, visually distinct). No gate. The discipline is Justin's, not the software's.
The Marketing Studio is a new workspace within studio.bezeliq.ai that surfaces the existing five-agent marketing pipeline (Nova → Harper/Jade/Rosa → Ivy) through a UI designed for creative direction, not approval queue management. Today the pipeline runs completely blind: Nova writes a prose weekly_plan.md to Google Drive, Harper/Jade/Rosa write versioned content files back to Drive, and Justin receives an inbox exec task to select versions. Ivy then stages the selected file to a published/ subf
No explicit evidence field yet. Require tests, screenshots, linked PRs, or reviewed outputs before marking complete.
- item: risk: string, mitigation: string
- item: risk: string, mitigation: string
- item: risk: string, mitigation: string
- item: risk: string, mitigation: string
- item: risk: string, mitigation: string
- item: risk: string, mitigation: string
- item: risk: string, mitigation: string
- item: risk: string, mitigation: string
- item: risk: string, mitigation: string
- OPEN — X API tier: Justin's current X/Twitter API tier (Free, Basic, Pro) must be confirmed before V1 blueprint. Free tier blocks POST /2/tweets. Upgrade to Basic ($100/month) required if currently on Free tier.
- RESOLVED — Unipile LinkedIn: Confirmed. Unipile supports POST /api/v1/posts/ with account_id, text, attachment. No fallback needed. createPost function to be added to scripts/lib/unipile.js.
- RESOLVED — Blog CMS: Eleventy static site at dreamborn.ai. No CMS API. Auto-publish = git commit .md file + Cloudflare Pages/Netlify deploy hook. Blueprint must include this mechanic.
- RESOLVED — Fifth channel: YouTube is the fifth channel (blog, substack, linkedin, x, youtube). YouTube pieces in V1 = video concept + description text artifact. Full video auto-upload is V2. 14+/week target includes YouTube concept pieces.
- RESOLVED — Image style: Rosa selects per content type. Photorealistic for product/lifestyle, illustrated for concepts, typographic for quotes/stats, data-viz for metrics. Variety signals human creative direction.
- RESOLVED — Voice edit enforcement: Two paths on personal channels — (a) Edit+Approve (primary CTA), (b) Approve-as-is (deliberate secondary action, visually distinct). No gate. The discipline is Justin's, not the software's.
Machine-readable source fields
| risk | mitigation |
|---|---|
| [RESOLVED] Unipile LinkedIn post creation — previously flagged as critical-path blocker | Justin confirmed: Unipile supports LinkedIn post creation via POST /api/v1/posts/ with account_id, text, and attachment support. No fallback needed. Implementation task: add createPost function to scripts/lib/unipile.js. Not a blocker. |
| X/Twitter API tier — Free tier does not allow POST /2/tweets | Confirm Justin's current X API tier before blueprint. If Free tier, upgrade to Basic ($100/month) is required before V1 ship. Architect the publish job with graceful 429/403 error handling and retry queue regardless of tier. This is the one remaining unresolved question. |
| Ivy persona explicitly stages-only — contradicts Justin's approved intent that auto-publish is IN V1 | Ivy persona must be updated before or alongside the auto-publish implementation task. Blueprint must include an Ivy persona update task wiring LinkedIn (Unipile), X (X API v2), blog (git commit + deploy hook), and YouTube staging. Do not allow V1 to ship with Ivy still staging-only. |
| Nova persona and output format change (prose → CAMPAIGN.json) must ship simultaneously with Studio Campaign view | Nova cannot switch to JSON output until the Studio UI is ready to consume it. Blueprint must sequence Nova persona update after Campaign view is built and tested. In the interim, Nova continues writing weekly_plan.md. Do not update Nova's persona ahead of the UI. |
| content_assets schema migration must not break existing pipeline agents | Both new columns are additive (nullable body text, boolean justin_edit default false). Confirmed no existing agent (Nova, Harper, Jade, Rosa, Ivy) references these columns. Run migration with IF NOT EXISTS guard before Studio UI is built. Rollback is dropping two nullable columns — zero data loss. |
| Fal.ai cost overrun if Rosa is triggered repeatedly on the same piece_id | Rosa checks content_assets for existing graphic rows before generating. If 6 graphic rows with status >= staging exist for the piece_id, skip generation. Add spend alert on Fal.ai account dashboard at $5/week threshold (~20× expected cost). |
| Blog deploy hook URL missing from environment config at V1 ship — blog auto-publish silently fails | The deploy hook URL must be added to environment config (Supabase secrets or .env) before Ivy's blog publish function is deployed. Blueprint must include a pre-ship checklist item: confirm BLOG_DEPLOY_HOOK_URL is set and returns 200 on test POST. |
| YouTube content counted toward 14+/week but requires video production — volume target may be unachievable if video files stall | In V1, YouTube pieces are concept+description text artifacts counted as produced pieces regardless of whether video files are created. The 14+/week success metric is measured at the piece-produced-and-staged level, not video-uploaded level. Revisit YouTube video auto-upload in V2 when video production workflow is defined. |
| Rosa image style selection requires per-content-type classification logic not currently in Rosa's persona | Rosa's persona update must include an explicit style selection rule: photorealistic for product/lifestyle, illustrated for concepts, typographic for quotes/stats, data-viz for metrics. The piece type classification must be derivable from content_assets columns (channel + angle pattern). Blueprint must define this mapping explicitly — Rosa's style selection is rule-based, not open-ended judgment. |
The Marketing Studio is a new workspace within studio.bezeliq.ai that surfaces the existing five-agent marketing pipeline (Nova → Harper/Jade/Rosa → Ivy) through a UI designed for creative direction, not approval queue management. Today the pipeline runs completely blind: Nova writes a prose weekly_plan.md to Google Drive, Harper/Jade/Rosa write versioned content files back to Drive, and Justin receives an inbox exec task to select versions. Ivy then stages the selected file to a published/ subfolder but explicitly does not publish externally in the current V1 persona. There is no unified view, voice editing requires opening Drive files, auto-publish does not exist, and the graphic asset type in content_assets is not an independent publish channel. The five confirmed publish channels are blog (Eleventy static site at dreamborn.ai, auto-publish via git commit + deploy hook), substack (manual V1), linkedin, x, and youtube — youtube being a distinct content type requiring video concept and description format rather than static graphics.
The Marketing Studio replaces this invisible workflow with three views: Campaign (Nova's structured CAMPAIGN.json showing week theme, angles, and piece slots), Piece Editor (inline rich-text voice editing with two explicit approval paths — Edit+Approve as primary, Approve-as-is as a deliberate secondary action — plus image pairing), and Calendar (scheduled publish timeline per channel). Five engineering components require net-new work beyond UI routes: (1) Structured JSON artifacts — CAMPAIGN.json and CONTENT.json replace prose weekly_plan.md as the machine contract between Nova and the Studio UI; (2) Real image generation — Rosa's Puppeteer screenshots are replaced with Fal.ai API calls, with Rosa selecting style per content type: photorealistic for product/lifestyle, illustrated for concepts, typographic for quotes/stats, data-viz for metrics; (3) Auto-publish integrations for LinkedIn via Unipile POST /api/v1/posts/ (confirmed supported), X via X API v2, and blog via git commit + Cloudflare Pages deploy hook; (4) YouTube as a distinct content type — video concept + description text in V1, full video auto-upload deferred to V2; (5) Ivy persona upgrade from staging-only to wiring
All five open questions from the original research are now resolved. The existing stack (Vue 3 SPA, Supabase) handles the Marketing Studio shell with no new hosting. New routes at /marketing follow the DevStudioHome.vue pattern. content_assets requires two additive columns (body text, justin_edit boolean) for voice editing persistence — migration is fully safe, no existing agent reads these columns. Nova, Rosa, and Ivy persona definitions must be updated in explicit sequence with the UI build — Nova to output CAMPAIGN.json, Rosa to call Fal.ai with style-per-type logic, Ivy to wire LinkedIn, X, blog, and YouTube publish calls. The blueprint must sequence these persona updates to avoid pipeline breaks mid-build. One open question remains: Justin's current X API tier must be confirmed before V1 blueprint to determine whether a Basic tier upgrade is required for POST /2/tweets.
| url | name | relevance |
|---|---|---|
| apps/bezeliq-studio/src/views/DevStudioHome.vue | Dev Studio — DevStudioHome.vue (existing) | Established pattern for Studio views: Supabase-backed project list, artifact lifecycle cards (intent→blueprint→spec→policy), AppShell routing. Marketing Studio mirrors this at /marketing routes with campaign/piece-editor/calendar replacing the dev artifact stages. |
| internal://supabase/content_assets | content_assets Supabase table (existing) | Piece lifecycle state machine: draft → staging → staged → published. Confirmed columns: id, piece_id, version, is_selected, client_id, scheduled_date, channel, topic, angle, hook, theme, status, drive_file_id, drive_url, r2_url, published_at, publish_url, tags, created_by, staged_at, updated_at. Confirmed missing: body (text) and justin_edit (boolean) — both required for voice editing in Studio. Migration is additive; no existing agent reads these columns. |
| scripts/lib/unipile.js | Unipile LinkedIn client — scripts/lib/unipile.js (existing) | Authenticated LinkedIn API wrapper. Existing functions: sendConnectionRequest, sendDm, getConnectionUpdates. Justin confirmed Unipile supports LinkedIn post creation via POST /api/v1/posts/ with account_id, text, and attachment support. A new createPost function must be added to this file — no new OAuth or account registration required. This is now an implementation task, not a blocker. |
| definitions/agents/nova.json | Nova agent — definitions/agents/nova.json (existing) | Nova currently writes weekly_plan.md prose to Google Drive and rows to content_assets with status=draft. V1 adds CAMPAIGN.json output to studio_artifacts. Nova persona must be updated to output structured JSON alongside or instead of weekly_plan.md. Core strategy logic (gap analysis, BezelBrain queries, scheduling) is unchanged. |
| definitions/agents/rosa.json | Rosa agent — definitions/agents/rosa.json (existing) | Rosa uses the puppeteer plugin to screenshot HTML templates at 1200×630px, producing 6 variants per piece. V1 replaces Puppeteer with Fal.ai API calls. Rosa now selects image style per content type: photorealistic for product/lifestyle, illustrated for concepts, typographic for quotes/stats, data-viz for metrics. Variety signals a human creative director. The 6-variant structure and content_assets rows remain identical — only the rendering mechanism and style selection logic change. |
| definitions/agents/ivy.json | Ivy agent — definitions/agents/ivy.json (existing) | Ivy currently stages content (moves file to published/ in Drive, updates content_assets to status=staged) but explicitly does NOT publish externally — persona states: 'In V1 you stage for manual posting. V2 will wire live publishing APIs.' Per Justin's approved intent, auto-publish is IN V1 scope. Ivy's persona requires an update to wire LinkedIn (Unipile POST /api/v1/posts/), X (X API v2), blog (git commit + deploy hook), and YouTube staging. Substack remains manual in V1. |
| https://www.11ty.dev | Eleventy static site generator (dreamborn.ai blog) | Justin confirmed the blog at dreamborn.ai is an Eleventy static site with no CMS API. Blog auto-publish = (1) git commit a .md file to the repo, (2) trigger a Cloudflare Pages or Netlify deploy hook via HTTP POST. Ivy's blog publish path must implement this two-step mechanic. Deploy hook URL must be stored in environment config before V1 ships. |
- OPEN — X API tier: Justin's current X/Twitter API tier (Free, Basic, Pro) must be confirmed before V1 blueprint. Free tier blocks POST /2/tweets. Upgrade to Basic ($100/month) required if currently on Free tier.
- RESOLVED — Unipile LinkedIn: Confirmed. Unipile supports POST /api/v1/posts/ with account_id, text, attachment. No fallback needed. createPost function to be added to scripts/lib/unipile.js.
- RESOLVED — Blog CMS: Eleventy static site at dreamborn.ai. No CMS API. Auto-publish = git commit .md file + Cloudflare Pages/Netlify deploy hook. Blueprint must include this mechanic.
- RESOLVED — Fifth channel: YouTube is the fifth channel (blog, substack, linkedin, x, youtube). YouTube pieces in V1 = video concept + description text artifact. Full video auto-upload is V2. 14+/week target includes YouTube concept pieces.
- RESOLVED — Image style: Rosa selects per content type. Photorealistic for product/lifestyle, illustrated for concepts, typographic for quotes/stats, data-viz for metrics. Variety signals human creative direction.
- RESOLVED — Voice edit enforcement: Two paths on personal channels — (a) Edit+Approve (primary CTA), (b) Approve-as-is (deliberate secondary action, visually distinct). No gate. The discipline is Justin's, not the software's.
| chosen | options | decision | rationale |
|---|---|---|---|
| Fal.ai FLUX.1-schnell with Rosa-selected style per content type | - Fal.ai FLUX.1-schnell (~$0.003/image, ~3s generation) - OpenAI DALL-E 3 (~$0.04/image, slower) - Replicate Stable Diffusion 3.5 - Keep Puppeteer (no change) | Image generation service to replace Rosa's Puppeteer screenshots | Sub-5s generation at ~$0.003/image. At 6 variants × ~14 pieces/week, cost is ~$0.25/week. Simple REST API. Rosa selects style per content type — photorealistic for product/lifestyle, illustrated for concepts, typographic for quotes/stats, data-viz for metrics — producing variety that signals a human creative director rather than a bot. Puppeteer is fragile on VPS, requires a display environment, and produces screenshot-quality rather than generation-quality images. Fal.ai removes the headless browser entirely. |
| Unipile API — POST /api/v1/posts/ (confirmed supported) | - Unipile API — add post creation endpoint - LinkedIn API v2 direct (requires app registration + OAuth, 2-4 week approval) - Buffer API (third-party scheduling tool) - Manual staging only — defer to V2 | LinkedIn auto-publish mechanism | Justin confirmed Unipile supports LinkedIn post creation via POST /api/v1/posts/ with account_id, text, and attachment support. No new OAuth, no LinkedIn API app registration, no approval delay. A createPost function is added to the existing scripts/lib/unipile.js. This is no longer on the critical path as a risk — it is an implementation task. |
| X API v2 — OAuth 2.0 + POST /2/tweets | - X API v2 — OAuth 2.0 + POST /2/tweets (requires Basic tier or above) - Buffer API - Manual staging only — defer to V2 | X/Twitter auto-publish mechanism | X API v2 write access (POST /2/tweets) is available on Basic tier ($100/month) and above. Free tier does not support post creation. Justin's current X API tier must be confirmed before blueprint — if Free tier, upgrade is required before V1 ships. Standard approach, no third-party dependency. Publish job must include graceful 429/403 handling and retry queue. |
| Git commit markdown + Cloudflare Pages/Netlify deploy hook | - CMS API (Ghost, WordPress, etc.) - Git commit markdown + Cloudflare Pages/Netlify deploy hook - Manual staging only — defer to V2 | Blog auto-publish mechanism | Justin confirmed dreamborn.ai is an Eleventy static site with no CMS API. Auto-publish = (1) git commit a .md file to the repo posts directory via GitHub API or SSH key, then (2) POST to the Cloudflare Pages or Netlify deploy hook URL. Ivy implements this as a two-step publish function. Deploy hook URL must be stored in environment config (Supabase secrets or .env) and confirmed active before V1 ships. |
| Video concept + description text artifact — staged for manual upload in V1 | - Video concept + description text artifact — staged for manual upload - Full video generation and YouTube Data API v3 auto-upload (out of scope V1) - YouTube short as static image with audio (creative workaround) | YouTube V1 content format and publish mechanism | YouTube is confirmed as the fifth channel. YouTube requires video content — not a static graphic. In V1, the Studio produces a video concept (script outline, talking points, thumbnail brief) and a YouTube description/metadata block as a text piece. The actual video file is produced manually; Ivy stages the metadata. Full YouTube Data API v3 video auto-upload requires video production infrastructure out of V1 scope. YouTube pieces count toward 14+/week as concept+description artifacts. V2 evaluates automated video production and upload. |
| New artifact_type rows in studio_artifacts table | - New artifact_type rows in studio_artifacts table (campaign, content) - Separate Supabase tables - Continue Google Drive prose files (no change) | CAMPAIGN.json and CONTENT.json artifact storage | studio_artifacts already has project_id, artifact_type, content (jsonb), status, version, approved_by pattern used by Dev Studio. Adding artifact_type='campaign' and 'content' follows the same pattern. Enables the Studio UI to read structured campaign data directly from Supabase without a Drive API call. Enables Justin to approve a campaign before pieces are written. |
| Add body and justin_edit columns to content_assets | - Save edits to Google Drive file on each save - Add body + justin_edit columns to content_assets (additive migration) - New piece_edits table in Supabase | Voice editing persistence layer | content_assets is the piece lifecycle table. Adding body (text, nullable) and justin_edit (boolean, default false) keeps all piece state in one place. Confirmed from schema inspection that no existing agent references these columns — migration is fully additive and safe. justin_edit flag records which approval path was taken; it does not gate approval. |
| Two explicit paths: Edit+Approve primary, Approve-as-is deliberate secondary action | - Enforce minimum one edit before approval (block Approve-as-is) - Two explicit paths: Edit+Approve primary, Approve-as-is secondary deliberate action - Single Approve button with no edit requirement | Voice edit approval enforcement in the Piece Editor | Justin confirmed: do not enforce mandatory editing. The discipline is Justin's, not the software's. The Piece Editor presents Edit+Approve as the primary CTA and Approve-as-is as a clearly labelled secondary action (visually distinct — smaller, muted styling with explicit label). The justin_edit boolean records which path was taken for audit purposes. No gates, no blocks — but the UI makes deliberate choice visible. |
| Tiptap | - Tiptap (ProseMirror-based, Vue 3 native support) - Quill.js - Plain contenteditable div - Textarea with markdown preview | Rich text editor for voice editing in Piece Editor view | First-class Vue 3 integration, outputs clean HTML/markdown, handles paragraph/bold/italic formatting without a heavy toolbar. Single npm package. Saves to content_assets.body on blur. Minimal bundle impact. |
| Manual staging for V1 — defer auto-publish to V2 | - Email-to-Substack draft (fragile) - Unofficial Substack API (undocumented, may break) - Manual staging only — defer auto-publish to V2 | Substack V1 publish strategy | Substack has no official third-party publishing API. Email-to-draft is fragile and unconfirmed. Substack is weekly cadence; Justin can click Publish from the staged draft with minimal friction. Keeping it manual in V1 does not violate the non-negotiable on auto-publish — Substack is one deliberate action from a staged draft, not multi-step manual posting like LinkedIn/X. |