Plan a weekly social media content calendar for any active brand — Static Mode (14 posts across LinkedIn, Facebook, Instagram) or YouTube-First Mode (one weekly video + platform clips), selected by brand.md Content Strategy. Runs weekly on Sunday cron schedule.
Plan a weekly social media content calendar for any active brand — Static Mode (14 posts across LinkedIn, Facebook, Instagram) or YouTube-First Mode (one weekly video + platform clips), selected by brand.md Content Strategy. Runs weekly on Sunday cron schedule.
allowed-tools
Read, Grep, Glob, Bash, WebSearch
area
Marketing
use_for
Plan a weekly social media content calendar — Static Mode (14 posts across LinkedIn, Facebook, Instagram) or YouTube-First Mode (one weekly video + platform clips), per brand.md Content Strategy. Runs weekly on Sunday cron schedule
Description: Plan a weekly social media content calendar for any active brand — Static Mode (14 posts) or YouTube-First Mode (one weekly video + platform clips), selected by brand.md Content Strategy
Change Log
v2.15.0 — June 09, 2026
Competitor video structure matching added (Step 2a Part 1b). YouTube-First Mode now reads the video_structure JSON from trend_db (written by trend-radar Step 2c) and maps each structural element — credibility hook, pattern interruptor, framework, escalation, reflection, close — to the brand's own angle. The adapted structure is written as a Video Structure table in the calendar. Clips now have a Source Section column tracing each clip to a video section, preventing topic-mismatch bugs. Degrades gracefully when no video structure is available (plans from topic/angle as before).
v2.14.0 — May 29, 2026
YouTube-First Mode added. Step 1 now reads brand.md## Content Strategy (Primary channel, Connected platforms, Clips per video, Cadence) to pick the planning flow. New Step 2a (YouTube-First Mode) plans one weekly YouTube video + a per-platform Clip Release Schedule — publish day/time per clip, Twitter/X support, comment-to-DM CTAs — restricted to connected platforms; the schedule is the handoff to video-repurposer (Phase 4–5). The original 14-post flow is renamed Step 2b (Static Mode). The quality checklist branches per mode. Brands without ## Content Strategy default to Static.
v2.12.0 — May 28, 2026
Planning now closes the learning loop. Step 1b reads two new inputs before the market-research pass: (1) the latest Performance Brief from content-performance-analyst (${BRAND}_PERFORMANCE_DB / outputs/{brand}/strategy/PerformanceBrief_*) to bias the mix toward proven-winning formats/personas/hook archetypes/Directions, and (2) trend-radar candidate topics (${BRAND}_TREND_DB) for 2–3 timely/newsjacking slots. Step 2's Content Angle now names a hook archetype from content-creation/hook-library.md so production and performance analysis share one hook vocabulary.
v2.11.1 — May 22, 2026
Metrics and checklist corrected. Log payload: calendar_status fixed "Published" → "Planned"; post format fixed from hardcoded "static" → "<Post|Story|Carousel>"; content_mix.type fixed from "static" → content-type enum. Quality checklist: added Monday slot checks (LinkedIn Post / Facebook Post / Instagram Post) and Friday LinkedIn = Post. deps.gateway updated from [] to ["FiveAgents (logging)"].
v2.11.0 — May 21, 2026
SlideId column removed (12 → 11 columns) — fb.ai now filters linkedin-post and meta-post by the post's direction override server-side, returning only the matching slide. content-generator no longer passes slide_ids; social-calendar no longer needs to pre-resolve slide labels. Direction is all you need for single-image types.
Direction rotation guidance added — spread A/B/C across same-format posts for variety; avoid long runs of the same Direction in one platform's feed.
SKILL.md — Social Calendar
Before Executing
Read agents/link.md before starting. It defines the active brand, personality, quality checklist, and available tools. Determine the active brand from $DEFAULT_BRAND env var — if not set, ask the user.
You are a senior social media strategist for the active brand. Your job is to plan a 1-week content calendar (next Mon–Sat) across LinkedIn, Facebook, and Instagram — optimized for the brand's primary conversion goals (read from brands/{brand}/product.md).
Runs weekly on Sunday cron schedule. Output is a planning table only — topics, formats, angles, CTAs. The content-generator skill handles actual copy.
Step 1 — Read Brand Context
Read these files before planning:
brands/{brand}/brand.md
brands/{brand}/product.md
brands/{brand}/audience.md
brands/{brand}/competitors.md
After reading brand context, check brands/{brand}/brand.md for ## Content Strategy:
Read Primary channel value: youtube or static
Read Connected platforms — in YouTube-First Mode, only plan clips for the platforms listed here
Read Clips per video — default clip count per platform in YouTube-First Mode
Read Cadence — publishing rhythm
If ## Content Strategy is absent → default to static mode and note the gap
This single field drives which planning flow runs in Step 2.
Step 1b — Read the learning loop, then research
First, read what already worked and what's timely (these bias the whole plan — read them before the market-research pass):
Latest Performance Brief — from content-performance-analyst. Read the newest outputs/{brand}/strategy/PerformanceBrief_*.md (or query ${BRAND}_PERFORMANCE_DB if the file is absent). Use its "double down / stop-fix / next-calendar guidance" to bias the content mix toward winning formats, personas, hook archetypes, and Directions, and away from underperformers. If no Brief exists yet (first weeks), note it and proceed on market research alone.
Trend Radar candidates — query ${BRAND}_TREND_DB for Status="Candidate" rows seen in the last 7 days (written by trend-radar). Earmark 2–3 timely/newsjacking slots for the strongest, most relevant candidates; mark the ones you use Status="Planned".
Then run /link-skills:research-strategy for market context. Focus the research on:
Viral content formats — what's getting high engagement on LinkedIn, Facebook, Instagram right now in the brand's niche? (e.g. carousel threads, short-form video, meme formats, hot takes, before/after)
Trending topics — what industry conversations, news, or pain points are blowing up this week?
Competitor social content — what did competitors post recently that got high engagement? What format/hook did they use?
High-performing keywords — from DataforSEO, what search terms are trending in the brand's space?
Read the research output from outputs/{brand}/strategy/ after it completes.
All three Step 1b signals — the Performance Brief (what worked), Trend Radar (what's timely), and this market research — MUST drive the content mix and topic selection in Step 2. Do not default to a fixed content mix. Precedence: the Performance Brief first (your own measured results), then timely trend candidates, then market research.
Step 2a — YouTube-First Mode
Run this step only if Primary channel: youtube in brand.md. Otherwise skip to Step 2b.
Plan one YouTube video for the upcoming week plus a clip release schedule for each connected platform. All content must be informed by Step 1b inputs: Performance Brief, Trend Radar candidates, and competitor research.
Part 1 — YouTube Video Plan
Field
Notes
Title
Hook-led, ≤60 chars — use proven title mechanics: specific outcome, time-pressure hook, or curiosity gap
Publish Day & Time
Mon or Tue evening, 6–9pm (brand timezone from brand.md Locale)
Format
talking-head / screen-share / hybrid
Target persona
One persona slug from audience.md
Trend Radar topic
Candidate from ${BRAND}_TREND_DB it maps to — mark that row Status=Planned
Intro hook
First 30s — what the viewer will learn
Talking points
3–5 key points (one line each)
CTA
Comment-to-DM format: "Comment '[keyword]' and I'll send you [specific resource]" — never generic subscribe asks
Est. length
5–15 min
Part 1b — Apply Competitor Video Structure (when available)
If the Trend Radar candidate used for this video has a video_structure JSON in its Notion page body (written by trend-radar Step 2c), read it and use it to plan the video's internal structure.
Procedure:
Fetch the trend_db page for the candidate marked Status=Planned — use mcp__claude_ai_Notion__notion-fetch with the page ID.
Parse the video_structure JSON from the page body (inside a ```json code fence under the "Video Structure Analysis" heading).
Map each structural element to the brand's own angle:
Competitor Element
Brand Adaptation Rule
credibility_hook
Replace the competitor's credential with the brand's own credibility anchor (from brand.md or brand context — e.g. corporate pedigree, specific experience, unique access). Never copy the competitor's claim.
pattern_interruptor
Keep the same technique (flash-forward, show-don't-tell, etc.) but apply it to the brand's content. If the competitor shows a finished product, the brand shows its finished product.
framework
Teach a framework relevant to the brand's expertise. If the competitor taught a 3-type taxonomy, the brand teaches its own taxonomy from its domain knowledge.
build_with_escalation
Match the escalation pattern (time-based, complexity-based, etc.) but with the brand's own build/journey. Preserve the same number of escalation steps.
reflection_beat
Add a personal/honest reflection from the brand's perspective — what the brand owner learned, what surprised them, what they'd change.
close_cta
Match the CTA mechanic (comment-to-DM, etc.) but with the brand's own resource/offer.
Write the adapted structure into the YouTube Video Plan as a new table:
Video Structure (adapted from competitor)
#
Section
Timestamp Target
Brand's Version
Technique
1
Credibility Hook
0:00–0:30
<brand's adapted hook>
<same technique, brand's credential>
2
Pattern Interruptor
0:30–1:30
<brand's adapted interruptor>
3
Framework
1:30–3:00
<brand's framework>
4
Build with Escalation
3:00–
<brand's build steps>
5
Reflection Beat
near end
<brand's reflection>
6
Close / CTA
final 30s
<brand's CTA>
Also add a Competitor Structure Applied reference:
Ensure clips are derived FROM the video structure. Each clip in Part 2 must map to a specific section from the structure table above. Do not plan clips about topics unrelated to the video — every clip's "Moment to clip" must reference a section number (1–6) or a specific escalation step from section 4.
If no video_structure is available (trend-radar Step 2c was skipped, or the candidate is not a competitor-remix, or Chrome MCP was unavailable): plan the video using the current method (topic + angle + talking points from Step 1b research). Note in the calendar: "No competitor structure available — planned from topic/angle."
Part 2 — Clip Release Schedule
For each platform listed under Connected platforms in brand.md, plan the number of clips specified under Clips per video (default 1–2 if unset). Do NOT plan clips for platforms not listed as connected.
Clip #
Platform
Publish Day
Publish Time
Duration
Source Section
Moment to clip
Caption angle
Hook Archetype
CTA
Where Source Section is the section number (1–6) or escalation step (e.g. "4a: Day 1") from the Video Structure table (Part 1b) that this clip is drawn from.
Validation rule: Every clip's Source Section must reference a valid section from the Video Structure table. If a clip cannot be traced to a video section, it must be removed or rewritten. This prevents clips about topics not covered in the video. (When no video_structure was available and the video was planned from topic/angle, leave Source Section blank and note it.)
Scheduling rules (read timezone from brand.md Locale):
YouTube publishes first (Mon or Tue evening), clips roll out Tue–Fri — never before the YouTube video is live
Never publish two clips on the same platform on the same day
LinkedIn: Tue or Thu, 7:30–8:30am local time
Instagram Reels: Wed 12pm or Thu 9am local time
TikTok: Fri 9am local time
Twitter/X: Thu 12pm local time
Facebook: Wed or Thu, 1–3pm local time
Clip guidelines per platform:
LinkedIn: 60–90s, professional insight or story angle
Instagram: ≤60s Reels, hook in first 3s
TikTok: ≤60s, fastest hook from the video
Twitter/X: ≤30s, sharpest one-liner from the video
Facebook: 60–90s, broader audience angle
Part 3 — Captions
For each clip, write a platform-native caption. Read voice and tone from brand.md. Use comment-to-DM CTAs — never generic subscribe asks.
LinkedIn: 150–300 words, professional + story-led, 3–5 hashtags
Instagram: Hook in line 1 (≤125 chars before "more"), 80–150 words, 3–5 hashtags
All clips set to Status = Planned pending YouTube upload.
Save to Notion — same DB as static mode (${BRAND}_NOTION_DB); if the env var is unset (first-ever run for this brand), create the DB first via the Step 3a bootstrap, then persist its ID. Page title: SocialCalendar_[DDMon]-[DDMonYYYY]. Content: YouTube Video Plan table + Video Structure table (when Part 1b produced one) + Clip Release Schedule table + Captions section. The Video Structure table must be saved when present — content-creation Step 2b reads it back off this page to build the YouTube script skeleton.
After saving, skip Step 2b and proceed to Slack notification.
Step 2b — Static Mode
Run this step only if Primary channel: static in brand.md (or if ## Content Strategy is absent). Otherwise this step was already handled by Step 2a.
Generate exactly 14 posts for the upcoming Mon–Sat week using the fixed slot assignments below.
Fixed Slot Assignments — do not deviate
Day
Platform
Format
Monday
LinkedIn
Post
Monday
Facebook
Post
Monday
Instagram
Post
Tuesday
Facebook
Post
Tuesday
Instagram
Story
Wednesday
LinkedIn
Carousel
Wednesday
Instagram
Post
Thursday
Facebook
Post
Thursday
Instagram
Story
Friday
LinkedIn
Post
Friday
Facebook
Story
Friday
Instagram
Carousel
Saturday
Facebook
Post
Saturday
Instagram
Carousel
For each post output ONLY these fields (one row per post, no extra commentary):
Field
Constraint
Date
DD Mon YYYY
Platform
LinkedIn / Facebook / Instagram
Format
From fixed slot table above.
Topic
Short topic name (≤6 words)
Persona
One of the persona slugs defined in brands/{brand}/audience.md — max 2× per persona per week. Examples: content-mgr, seo-pro, sales-rep, agency-owner, solopreneur, growth-mktr
Content Angle
Hook + key message (1 sentence, ≤20 words). Name the hook archetype it uses (from skills/content-creation/hook-library.md, e.g. Contrarian Correction, Proof / Result) so production and content-performance-analyst share one hook vocabulary. Favor archetypes the latest Performance Brief shows winning for this brand.
CTA
Specific action (≤8 words)
Hashtags
3–5 hashtags
Image Brief
Scene description for image gen (1 sentence). End with: "No text. No logos. No watermarks." For Story posts the raw scene is automatically wrapped in the full-frame composition template (see "Story image_brief wrapping" below) before saving to Notion.
Direction
Template variant for the post — see "Direction selection" below. Required for any format with a matching fb.ai template (IG/FB Carousel, Story, IG/FB single-image, LinkedIn single-image); leave blank for formats with no matching fb.ai template.
Status
Planned
Direction selection — pick the template variant per post
The Direction field tells content-generator which template variant to render at production time. Direction is your responsibility (planning); content-generator just applies what you set.
Rotate Direction for variety — don't let the audience get bored. Pick by content type first (rules below), but when several posts of the same format/platform fit the same type, spread the Direction across A/B/C (and rotate the carousel coverVariant-bodyVariant combos) instead of repeating one. Aim for a varied mix across the week — avoid long runs of the same Direction in a single platform's feed.
For Carousel posts — combine the carousel template's two variant axes as coverVariant-bodyVariant:
type-allnumbers — default. Big typographic cover, all sign slides have large kicker numerals (01/02/03/04). Good for educational carousels with strong listicle structure.
sticker-editorial — Sticker-style cover (badge / playful), editorial body slides (less number-forward). Good for personality-led / opinion / hot-take content.
editorial-mixed — Editorial cover (magazine), mixed body slide treatments. Good for story / case-study carousels where each slide has a different visual rhythm.
Hot take / opinion / personality → sticker-editorial
Case study / customer story → editorial-mixed
For Story posts — pick one of the story template's three direction styles:
A — Spotlight Dark. Brand-led campaigns: eyebrow → headline → divider pill, dark backgrounds, accent color leading. Good for product launches, brand campaigns, urgent CTAs.
B — Editorial Stat. When a single big number is the story (oversized stat as hero). Good for ROI proof, benchmark posts, "X% faster" / "$Y saved" data points.
C — Cream Press. Light, magazine-style. Good for case studies, testimonials, founder posts, customer stories.
Pick by content type:
Brand campaign / launch / direct CTA → A
Stat-driven proof / benchmark → B
Case study / testimonial / founder voice → C
For LinkedIn single-image posts (linkedin-post template) — pick one of three directions:
A — Hook Headline (text-led). Narrative / opinion posts.
B — Stat Hero (one big number). Proof / results posts.
C — Pull Quote (editorial). Testimonials, founder posts, press mentions.
For IG/FB single-image posts (meta-post template) — pick one of three directions:
B — Quote Card (editorial). Testimonials, founder voice, press quotes.
C — Listicle Teaser (3–5 bullets). Value-led / educational posts.
For any format with no matching fb.ai template — leave Direction blank.
If you assign a Direction the brand's template doesn't support, content-generator falls back to the template defaults and logs a warning — so before assigning, check the template's manifest (via fivebucks_get_template) for the direction / coverVariant / bodyVariantoptions the brand's template actually exposes.
Direction reference — fb.ai handles slide selection server-side
fb.ai's renderer filters linkedin-post and meta-post by the post's direction (A/B/C) server-side and screenshots the one matching slide. So for these types just set Direction (above) — there is no separate slide column to fill, and content-generator no longer passes slide_ids. Same for meta-story (_direction → 6 slides) and meta-carousel (renders all 6). Slide selection is fb.ai's job; your job is picking the Direction.
Content mix across 14 posts:
Adapt the mix based on Step 1b research findings. If viral content in the niche is heavy on hot takes and opinion pieces, lean into that. If competitors are winning with educational carousels, plan more of those.
Default mix (use ONLY if research is unavailable):
Educational / How-to: 5 posts
Social Proof / Results: 3 posts
Product Spotlight: 3 posts
Direct CTA: 2 posts
Engagement / Opinion: 1 post
Research-driven adjustments:
If a viral topic is relevant to the brand → dedicate 2-3 posts to ride the trend (different angles per platform)
If competitor content is heavily educational → differentiate with more social proof or opinion content
If a keyword is trending → build posts around that keyword's intent
Always keep at least 2 Direct CTA posts per week (non-negotiable)
Rotation rules:
No persona more than 2× in the week
Rotate across all personas defined in brands/{brand}/audience.md (e.g. content-mgr, seo-pro, sales-rep, agency-owner, solopreneur, growth-mktr)
Direct CTAs at least 3 slots apart per platform
No same topic twice
Instagram Story and Facebook Story must never fall on the same day. The fixed slot table guarantees this (Tue IG, Thu IG, Fri FB — no pair shares a date). If a conflict is somehow detected, abort and report rather than moving fixed slots.
Story image_brief wrapping
Before saving any post row to Notion, apply this transformation to image_brief for Story posts.
STORY_TEMPLATE = (
"Photorealistic, full-bleed vertical portrait image for a 9:16 social media Story. ""{SCENE_DESCRIPTION}. The scene fills the ENTIRE frame from top to bottom — no empty areas, ""no plain backgrounds, no flat colour zones anywhere in the image. Rich environmental detail ""in the upper, middle, AND lower thirds of the frame. Cinematic lighting, editorial quality. ""Shot as if for a magazine cover in portrait orientation. ""Do not include any text, logos, or UI elements."
)
ifformat.lower() == "story":
image_brief = STORY_TEMPLATE.format(SCENE_DESCRIPTION=image_brief)
Rules:
Apply AFTER writing the raw scene description (the inner SCENE_DESCRIPTION) — the template adds composition constraints, not content.
The raw scene description must already end with "No text. No logos. No watermarks." — the template's final sentence supersedes it but the rule still guards earlier drafting.
For LinkedIn posts and all Carousel posts (any aspect ratio), do NOT apply the template — pass image_brief verbatim.
The wrapped string (not the raw scene) is what gets saved to Notion Image Brief and read verbatim by content-generator.
Example:
Raw scene (before wrapping):
A glowing analogue clock face on a clean minimal background, concept of time and urgency. No text. No logos. No watermarks.
Wrapped image_brief (saved to Notion):
Photorealistic, full-bleed vertical portrait image for a 9:16 social media Story. A glowing analogue clock face on a clean minimal background, concept of time and urgency. No text. No logos. No watermarks. The scene fills the ENTIRE frame from top to bottom — no empty areas, no plain backgrounds, no flat colour zones anywhere in the image. Rich environmental detail in the upper, middle, AND lower thirds of the frame. Cinematic lighting, editorial quality. Shot as if for a magazine cover in portrait orientation. Do not include any text, logos, or UI elements.
Step 2c — Verify Format Rules Before Saving
Static Mode only — skipped in YouTube-First Mode.
Run this check on every row before proceeding to Step 3. Fix any failing row in place — do not save until all rows pass.
Golden rule: every row must have Direction populated unless it is LinkedIn Carousel.
Story conflict rule: Instagram Story and Facebook Story must not share the same date. The fixed slot table guarantees this — if a conflict appears, abort and report; do not attempt to move fixed slots.
If any row fails, correct it before Step 3 — do not post a failing row as a known issue.
Step 3 — Save to Notion
Static Mode only — YouTube-First Mode already saved its calendar in Step 2a. (Step 3a's DB bootstrap below is shared: Step 2a reuses it on first runs.)
Use Notion MCP to save the calendar to Notion. The calendar page must live inside the brand's social-calendar database so content-generator Step 1 can find it via ${BRAND}_NOTION_DB.
Page title: SocialCalendar_[DDMon]-[DDMonYYYY]
e.g. SocialCalendar_17Mar-22Mar2026
Step 3a — Ensure the brand's social-calendar DB exists (first-run only)
This step is primarily for the first-ever calendar run for a brand. On subsequent weekly runs, the env var is already set and the DB already exists — fetch and proceed.
Read ${BRAND}_NOTION_DB from .claude/settings.local.json (e.g. FIVEBUCKS_NOTION_DB).
Decision:
IF env var is set:
fetch the DB → if fetch succeeds → DB exists → DO NOT create. Skip to Step 3b.
(only create if fetch returns 404 / not_found, meaning the DB was deleted)
IF env var is NOT set:
→ first-ever run for this brand → create the DB (instructions below).
Use mcp__claude_ai_Notion__notion-fetch:
- id: "${BRAND}_NOTION_DB"
If env var is set and fetch succeeds → skip to Step 3b. Do not create another DB.
Create only when env var is unset (or fetch returns not_found):
If no brand parent page exists yet, notion-search for "{Brand}" first; if nothing is found, create a parent page with notion-create-pages titled "{Brand Name}" at the workspace root, then nest the DB under it.
After creation, persist the new DB ID back to .claude/settings.local.json under env.{BRAND}_NOTION_DB. Read the existing settings file, add the key (preserve all other keys), write back. This makes the DB discoverable by content-generator and by every future weekly social-calendar run.
Notify the user in chat (first-run only):
Created new Notion DB {Brand Name} Social Media Calendar and saved its ID as ${BRAND}_NOTION_DB in .claude/settings.local.json. Future runs will reuse this DB — no re-creation.
Step 3b — Save local backup
Save local backup first: outputs/{brand}/strategy/SocialCalendar_[DDMon]-[DDMonYYYY].md
Step 3c — Find or create this week's calendar page inside the brand DB
Resolve the DB to a data_source_url (needed for scoped search):
Use mcp__claude_ai_Notion__notion-fetch:
- id: "${BRAND}_NOTION_DB"
Extract the collection:// URL from the response — typically data_sources[0].url. Save as data_source_url.
Check if the week's page already exists, scoped to the brand DB only:
Use mcp__claude_ai_Notion__notion-search:
- query: "SocialCalendar_[DDMon]-[DDMonYYYY]"
- data_source_url: <data_source_url from step 1>
- query_type: "internal"
⚠️ Never run a bare workspace-wide notion-search for the page name — it can return another brand's calendar with the same week label. Always pass data_source_url.
If no matching page → create it:
Use mcp__claude_ai_Notion__notion-create-pages:
- parent: { "database_id": "${BRAND}_NOTION_DB" }
- pages: [{
"properties": { "Name": "SocialCalendar_[DDMon]-[DDMonYYYY]", "Status": "Planned", "Posts": 14 },
"content": "<markdown of the calendar — heading + 11-column table + content mix + persona distribution>"
}]
The content field accepts markdown. Use a markdown table with 11 columns — Date, Platform, Format, Topic, Persona, Content Angle, CTA, Hashtags, Image Brief, Direction, Status. The Notion connector converts markdown headings, tables, and paragraphs into the appropriate block types automatically.
replace_content: true clears existing child blocks before appending the new content — avoids duplicate tables stacking up across re-runs.
Capture the returned page URL for the Slack notification.
Brand-header sanity check before Slack: the returned page's parent database title must contain the active brand. If somehow it doesn't (shouldn't happen given the DB-scoped flow), abort with a failed log — do not announce a calendar that landed in another brand's DB.
Before calling slack_send_message, you MUST first call ToolSearch with query "slack_send_message" to load the tool schema. The Slack MCP tool is deferred — calling it without loading the schema first will cause the task to hang.
DM the user via Slack MCP (slack_send_message, channel_id: "$SLACK_NOTIFY_USER"). Use the message that matches the mode you planned in:
YouTube-First: YouTube Video Plan includes publish day/time; Clip Release Schedule includes publish day + time per clip for each connected platform only; comment-to-DM CTAs used; Trend Radar candidate marked Planned if used
Video structure applied — if trend_db candidate had a video_structure JSON, the Video Structure table is present in the calendar with all 6 elements adapted for the brand
Credibility anchor is brand-appropriate — the hook uses the brand's own credential (from brand.md), not the competitor's claim
Clips trace to video sections — every clip's Source Section maps to a valid section in the Video Structure table; no orphan clips about unrelated topics
Competitor Structure Applied reference — source attribution present with competitor channel, video title, and URL
Static: Exactly 14 posts
Formats match fixed slot table
Monday LinkedIn = Post, Monday Facebook = Post, Monday Instagram = Post
Static: Persona rotation applied — no persona used more than 2× across the 14 posts
Content mix is research-driven (Step 1b) — if research was available, mix reflects findings; if research was unavailable, default mix used (5 edu / 3 proof / 3 product / 2 CTA / 1 engage); at minimum 2 Direct CTA posts present
All raw scene descriptions (before wrapping) end with "No text. No logos. No watermarks."
Story image briefs are wrapped in the full-frame composition template before saving to Notion — the saved Image Brief cell must contain "fills the ENTIRE frame"; LinkedIn and Carousel briefs are saved verbatim (no wrapping)
Step 2c verification passed (Static Mode) — every row passed the format-rules table (Direction correct per format type; no row missing Direction except LinkedIn Carousel)
Direction rotated for variety — same-format posts spread across A/B/C (and carousel coverVariant-bodyVariant combos); no long runs of the same Direction in one platform's feed
No same-day Stories — Instagram Story and Facebook Story are on different dates
Notion page URL logged to memory
Agent run logged to dashboard
Final Step — Log to Dashboard
See docs/new_agent_onboarding/metrics-spec.md for the full JSONB contract.
Mode-aware: the payload below is the Static Mode shape. In YouTube-First Mode, set mode: "youtube", drop posts_planned / posts / content_mix / persona_distribution, and instead log videos_planned: 1, clips_planned: [N], and a clips[] array (platform / duration / caption angle); keep calendar_status + notion_url.