Ship a Repo Explainer Pipeline
Build a 9-agent AI pipeline that turns any GitHub repo into video.
Introduction
30 Second Summary
Ever tried to explain a complex codebase to someone? You dig through README files, trace architecture decisions, and try to make it all make sense visually.
In this project, you will build Parallax, a nine-agent content foundry that takes any public GitHub repo URL and automatically produces a polished explainer video and carousel deck. Nine specialist AI agents analyze, script, visualize, compose, and review the output before delivery.
What You'll Build
A multi-agent orchestration pipeline that dispatches nine specialist AI agents in parallel waves to analyze, script, visualize, and compose reviewed explainer artifacts from any public GitHub repository.
By the end of this project, you'll have:
- A nine-agent orchestrator that dispatches parallel subagent waves with typed JSON contracts and a six-phase state machine.
- A complete visual and audio pipeline using Remotion animations, Mermaid diagrams, Azure Neural TTS voiceover, and ffmpeg composition.
- A quality council gate with adversarial fact-checking that ensures every output passes nine agent reviews before delivery.
- Secret Mission: Run long-mode to produce a chaptered YouTube-ready MP4 with dual-voice narration, chapter markers, and SRT captions.
Are there any prerequisites?
This project requires an active Claude Desktop Max subscription with Claude Code access, an OpenAI Pro subscription with Codex CLI installed, and an Azure account with a Speech Services resource.
You should also be comfortable running terminal commands on Windows. Step 1 handles automated verification and installation for all other tools.
Verify and Install the Tool Chain
Parallax depends on nine different tools working together. If any single tool is missing or outdated, the pipeline will fail silently or produce broken artifacts.
Instead of manually checking each tool one by one, you will instruct Claude Code to audit your entire environment in one pass, install anything missing, and scaffold the project skeleton. By the end of this step, every tool is verified and your directory structure is ready.
In this step, get ready to:
- Audit your environment for all required tools and versions.
- Install or update anything that is missing or outdated.
- Create the full project skeleton with all directories and a CLAUDE.md conventions file.
Run the environment audit
Parallax requires specific minimum versions for each tool. Node.js must be v18.19+ or v20+. npm must be v10+. The mermaid-cli requires its own peer dependency (Puppeteer ^23) installed separately or it will silently fail at render time.
- Open Claude Code.
- Enter the following prompt to run the full environment audit:
Audit my Windows environment for the Parallax pipeline. Check each of these tools and report their version (or "MISSING") in a table:
1. Node.js (need v18.19+ or v20+): run `node --version`
2. npm (need v10+): run `npm --version`
3. Git CLI: run `git --version`
4. ffmpeg (must be on PATH): run `ffmpeg -version`
5. @mermaid-js/mermaid-cli (need v11.14.0): run `mmdc --version`
6. Puppeteer (peer dep for mermaid-cli, need ^23): check `npm list -g puppeteer`
7. Remotion: check if any local project has @remotion/cli installed
8. Azure CLI: run `az --version`
9. Azure Speech Services resource: run `az cognitiveservices account list --query "[?kind=='SpeechServices']" -o table`
10. OpenAI Codex CLI: run `codex --version`
For each tool, report: tool name | required version | found version | status (PASS/FAIL/MISSING).
Do NOT install anything yet. Just report.
What does this prompt do?
Claude Code runs each version check command in your terminal and collects the output. It produces a summary table showing which tools pass, which are outdated, and which are completely missing.
By separating the audit from the install, you get a clear picture of your starting state before anything changes.
Claude Code will ask for permission to run terminal commands. Allow it.
You should see a formatted table with 10 rows. Each row shows PASS, FAIL, or MISSING.
Claude Code not running the commands?
Make sure you are in Claude Code (inside your terminal), not Claude Desktop chat. Claude Code needs terminal access to execute version checks.
If Claude Code asks which shell to use, select PowerShell.
Help me get Claude Code running commands in my terminal.
Install anything missing
Now that you know exactly what is missing or outdated, Claude Code can install everything in one pass and verify each install before moving on.
- Enter this prompt in Claude Code to install and verify all missing tools:
Based on the audit results above, install every tool marked FAIL or MISSING. Follow these rules:
1. Node.js: If missing or below v18.19, install via winget (`winget install OpenJS.NodeJS.LTS`). Verify with `node --version`.
2. npm: Comes with Node.js. If below v10, run `npm install -g npm@latest`. Verify.
3. Git: If missing, install via winget (`winget install Git.Git`). Verify.
4. ffmpeg: If missing or not on PATH, install via winget (`winget install Gyan.FFmpeg`). Verify with `ffmpeg -version`.
5. mermaid-cli: Install globally at v11.14.0: `npm install -g @mermaid-js/mermaid-cli@11.14.0 puppeteer`. Both packages in one command (Puppeteer ^23 is a required peer dependency). Verify with `mmdc --version`.
6. Remotion: Create a temp folder, run `npx create-video@latest --template=hello-world`, then `npx remotion render HelloWorld out.mp4` to confirm rendering works. Delete the temp folder after.
7. Azure CLI: If missing, install via winget (`winget install Microsoft.AzureCLI`). Then confirm a Speech Services resource exists with `az cognitiveservices account list --query "[?kind=='SpeechServices']"`. If none exists, create one in East US (free tier F0): `az cognitiveservices account create --name parallax-speech --resource-group parallax-rg --kind SpeechServices --sku F0 --location eastus`.
8. Codex CLI: Should already be installed. Just verify `codex --version` returns a version string.
After ALL installs, re-run the full audit table and confirm every row shows PASS.
What is happening here?
Claude Code works through the list sequentially. For each tool, it runs the install command, then immediately verifies the version. If verification fails, it retries with an alternative approach.
The mermaid-cli install includes Puppeteer in the same command because mermaid-cli v11.14.0 will silently fail without Puppeteer ^23 as a peer dependency.
Why does the Remotion test use a temp folder?
Remotion scaffolds a full project with dependencies. You only need to confirm rendering works. The real Remotion project will be created later inside the Parallax skeleton with custom compositions.
This step may take several minutes depending on how many tools need installing. Claude Code will show progress as it works through each one.
At the end, you should see the re-run audit table with all 10 rows showing PASS.
What about the Azure Speech Services free tier?
Azure Neural TTS offers 0.5 million characters per month free on the F0 (free) SKU. This is an always-free tier, not a 12-month trial. A typical 60-second script uses roughly 2000-3000 billable characters including SSML overhead, so you can run approximately 160-250 pipeline executions per month at zero cost.
Seeing errors during installs?
If winget is not recognized, open a new PowerShell window as Administrator. Windows App Installer (which provides winget) may need a fresh shell after Windows updates.
If the Azure resource creation fails with a subscription error, make sure you are logged in with az login first. Claude Code can run this for you if you ask.
Help me troubleshoot tool installation failures on Windows.
Create the project skeleton
With all tools verified, the next step is creating the directory structure that all nine agents will write into. Every agent needs to know exactly where to find inputs and deposit outputs.
- Enter this prompt in Claude Code to scaffold the full project directory and conventions file:
Create the Parallax project skeleton at C:\pipelines\parallax\ with this exact structure:
Directories (create all of these):
- .claude\agents\
- src\remotion\
- src\mermaid\
- src\carousel\
- src\tts\
- src\ffmpeg\
- schemas\
- config\
- assets\brand-kit-defaults\
- assets\intro-end-frames\
- assets\music-bed\
- assets\sfx\
- assets\broll-bank\
- bin\
- output\
- validation\council-receipts\
- validation\runtime-logs\
Then create CLAUDE.md at the project root (C:\pipelines\parallax\CLAUDE.md) with these sections:
1. Project Overview: "Parallax is a nine-agent content foundry. It takes a GitHub repo URL and mode flag (short/long/both) and emits reviewed explainer artifacts."
2. Agent Naming: Agents are numbered A1-A9. Definition files live at .claude/agents/A{N}-{slug}.md. Each uses YAML frontmatter.
3. JSON Contract Rules: Every agent has an input schema and output schema at schemas/A{N}-input.schema.json and schemas/A{N}-output.schema.json. Agents MUST validate output against schema before returning.
4. State Machine Phases: INTAKE, DISTILLED, STORYBOARDED, VISUAL_READY, COMPOSED, COUNCIL_PASS. State is tracked in output/{repo}/{sha}/state.json.
5. Tool Versions: Node.js v18.19+/v20+, npm v10+, mermaid-cli v11.14.0, Puppeteer ^23, Remotion v4.x, ffmpeg (system), Azure TTS F0, Codex CLI.
6. Output Structure: All run artifacts go to output/{repo}/{sha}/. Council receipts go to validation/council-receipts/. Runtime logs go to validation/runtime-logs/.
After creating everything, run `dir C:\pipelines\parallax /s /b` and show me the full tree.
What is CLAUDE.md?
CLAUDE.md is a project-level conventions file that Claude Code reads automatically when working in this directory. It sets ground rules that every future prompt inherits: naming patterns, directory conventions, version requirements.
Think of it as the project constitution. Every agent definition and orchestrator prompt you write later will operate within these boundaries without you needing to repeat them.
You should see a full directory listing with all 17 subdirectories and the CLAUDE.md file at the root.
- Verify the skeleton by asking Claude Code to show the contents of CLAUDE.md:
Show me the full contents of C:\pipelines\parallax\CLAUDE.md
Confirm you see all six sections: Project Overview, Agent Naming, JSON Contract Rules, State Machine Phases, Tool Versions, and Output Structure.
Directory creation failed?
If Claude Code reports a permissions error on C:\pipelines\, you may need to create the parent folder first. Ask Claude Code to run mkdir C:\pipelines as Administrator, or choose an alternative path like C:\Users\YourName\pipelines\parallax\.
Help me fix directory permissions for the Parallax project skeleton.
Your tool chain is verified and your project skeleton is in place. Next up, you will design the state machine and JSON contracts that control how data flows between all nine agents.
Design the State Machine and JSON Contracts
Your tool chain is verified and your project skeleton is ready at C:\pipelines\parallax\. Now you need to design the control system that tells your nine agents when to run, what to expect, and what to produce.
Without a formal contract system, agents would drift. One agent might output a field called files while another expects load_bearing_files. In this step, you will define a state machine that tracks pipeline progress, typed JSON Schema contracts for every agent handoff, and a dependency graph that controls parallel dispatch.
In this step, get ready to:
- Define the six-state machine that governs pipeline progression.
- Author 18 JSON Schema contracts (input + output for each of the nine agents).
- Wire the phase dependency graph that controls parallel agent dispatch.
Define the state machine
The pipeline needs to know where it is at all times. If a run crashes halfway through, the state machine tells the orchestrator exactly which phase completed and which outputs are available for resumption.
- In Claude Code, instruct it to create schemas/state-machine.json by giving it this prompt:
Create schemas/state-machine.json defining six named states for the Parallax pipeline.
The six states in order:
1. INTAKE - repo cloned at pinned SHA, README + docs extracted
2. DISTILLED - A1, A2, A4-brand all complete
3. STORYBOARDED - A3, A7 complete
4. VISUAL_READY - A4 diagrams rendered, carousel HTML ready
5. COMPOSED - Remotion render + TTS audio + ffmpeg mux complete
6. COUNCIL_PASS - A8 + A9 both pass, receipt written
Each state object must record:
- current_phase (string): the state name
- timestamp (ISO 8601 string): when the state was entered
- completed_agents (string[]): which agents finished in this phase
- outputs_produced (object): map of output type to file path
- gate_conditions (object): conditions required to advance to next state
Format as a JSON object with a "states" array. Each state has "name", "description", "records", and "gate_to_next" fields.
What does this state machine do?
The six states represent pipeline checkpoints. Each state captures a snapshot of progress: which agents ran, what files they produced, and what must be true before advancing.
Gate conditions prevent premature advancement. For example, the DISTILLED gate requires that core_nodes is non-empty and load_bearing_files contains at least one entry. Without these, later agents cannot do their work.
Claude Code creates the file and returns its content. Verify the output contains all six states with gate conditions.
- Next, instruct Claude Code to create the JSON Schema that validates the runtime state.json file by giving it this prompt:
Create schemas/state.schema.json as a JSON Schema (draft 2020-12) that validates the state.json file the orchestrator writes between phases.
Required properties:
- pipeline_id (string, UUID format)
- repo_url (string, URI format)
- git_sha (string, 40-char hex)
- mode (enum: "short", "long", "both")
- current_state (enum: "INTAKE", "DISTILLED", "STORYBOARDED", "VISUAL_READY", "COMPOSED", "COUNCIL_PASS")
- phases_completed (array of objects, each with: phase_number, state_name, started_at, completed_at, agents_dispatched[], agents_completed[], outputs{})
- errors (array of objects: agent_id, error_type, message, timestamp, retried boolean)
- created_at (string, date-time)
- updated_at (string, date-time)
All date fields use ISO 8601 format. Set additionalProperties: false at root level.
Why a JSON Schema for state.json?
The orchestrator writes state.json after every phase transition. The schema acts as a contract between the orchestrator and any tooling that reads state (the council agents, the retry logic, the warm-start cache).
Setting additionalProperties: false prevents agents from silently adding fields that no downstream consumer expects. Strictness now prevents debugging nightmares later.
- Confirm both files exist by running this in Claude Code:
ls schemas/state-machine.json schemas/state.schema.json
You should see both file paths listed without errors.
Files not appearing in schemas/?
Make sure Claude Code's working directory is C:\pipelines\parallax\. If you see a path error, ask Claude Code to check its current directory and navigate there first.
help me debug why Claude Code created files in the wrong directory
Author inter-agent contracts
Each agent receives a typed JSON input and must produce a typed JSON output. These contracts guarantee that Agent A3 (the Story Architect) can always find the fields it needs from Agent A1's output, regardless of how A1's prompt evolves over time.
- Start with A1 (Repo Anatomist) as the reference contract. Instruct Claude Code with this prompt:
Create schemas/A1-input.schema.json and schemas/A1-output.schema.json as JSON Schema (draft 2020-12).
A1-input.schema.json - what A1 receives:
- repo_url (string, URI format, required)
- git_sha (string, 40-char hex pattern, required)
- readme_content (string, required)
- docs_content (string, may be empty string if no docs folder)
- additionalProperties: false
A1-output.schema.json - what A1 produces:
- load_bearing_files (array of strings, minItems: 1, each is a relative file path)
- core_nodes (array of objects: {name: string, role: string, connections: string[]}, minItems: 3, maxItems: 5)
- system_boundary (string, one-sentence description of what the system does)
- entry_points (array of strings, minItems: 1, each is a file path)
- additionalProperties: false
Both schemas must have $schema, $id, title, description, type: "object", required array, and properties.
What do these contracts enforce?
The input schema defines exactly what the orchestrator must provide before dispatching A1. The output schema defines exactly what A1 must return. If validation fails, the orchestrator retries with correction context instead of passing garbage downstream.
The minItems: 3, maxItems: 5 constraint on core_nodes enforces the pipeline's 5-node cap on diagrams. A1 cannot flood the system with 20 nodes that would make diagrams unreadable.
- Now instruct Claude Code to generate the remaining eight agent contract pairs (A2 through A9). Give it this prompt:
Create input and output JSON Schema files for agents A2 through A9, following the same format as A1's schemas. Place all files in schemas/.
A2 - System Distiller:
Input: {repo_url, git_sha, a1_output (ref to A1-output schema)}
Output: {system_model: string, data_flow: string[], key_abstractions: object[], complexity_rating: enum(low,medium,high)}
A3 - Story Architect:
Input: {a1_output, a2_output, a7_output, mode: enum(short,long)}
Output: {title: string, hook: string, scenes: array of {scene_number, duration_seconds, narration, diagram_prompt, visual_notes}, total_duration_seconds: number}
A4 - Visual Director:
Input: {a1_output, a2_output, a3_output, mode: enum(short,long)}
Output: {brand_kit: {primary_color, secondary_color, font_family, logo_url?}, diagrams: array of {scene_number, mermaid_source, render_config}, carousel_slides?: array of {slide_number, heading, diagram_ref, body_text}}
A5 - Motion Engineer:
Input: {a3_output, a4_output, a6_output, mode: enum(short,long)}
Output: {composition_id: string, remotion_props: object, render_config: {width, height, fps, codec}, output_path: string}
A6 - Voice Director:
Input: {a3_output, mode: enum(short,long)}
Output: {ssml_segments: array of {scene_number, ssml: string, voice_id: string, duration_estimate_ms: number}, pronunciation_lexicon: object, pacing_preset: enum(short-pace, long-pace)}
A7 - Hook Strategist:
Input: {a1_output, a2_output, mode: enum(short,long)}
Output: {cold_open: string, value_promise: string, retention_hooks: string[], target_audience: string}
A8 - Quality Auditor:
Input: {state_json (full state), all_agent_outputs: object, final_artifacts: {video_path, carousel_paths?}}
Output: {checks: array of {check_id, check_name, passed: boolean, evidence: string}, overall_pass: boolean, codex_verification: {prompt_used, codex_response, agreement: boolean}, blocklist_violations: string[]}
A9 - Compliance Gate:
Input: {state_json, a8_output, final_artifacts, brand_kit}
Output: {license_check: {passed: boolean, issues: string[]}, rights_confirmed: boolean, brand_kit_correct: boolean, caption_accuracy: {whisper_transcript, alignment_score: number, passed: boolean}, final_verdict: enum(pass, veto), veto_reasons?: string[]}
All schemas: additionalProperties: false, proper required arrays, $schema draft 2020-12.
Why so many schema files?
Eighteen files (9 input + 9 output) sounds like overhead. In practice, they eliminate an entire class of pipeline bugs. When you iterate on agent prompts later, validation catches contract breaks immediately instead of surfacing as cryptic errors three phases downstream.
- Verify all 18 schema files were created by running:
ls schemas/A*-input.schema.json schemas/A*-output.schema.json | Measure-Object -Line
You should see a count of 18 (9 input files + 9 output files).
Count less than 18?
Claude Code sometimes generates files in batches and may miss one or two agents in a large prompt. Ask it to list the agents it completed, then request the missing ones explicitly by name (e.g. "You missed A5 and A6. Create schemas/A5-input.schema.json and schemas/A5-output.schema.json...").
help me find which agent schemas are missing
Wire the dependency graph
The orchestrator needs to know which agents can run simultaneously and which must wait for prior phases to complete. This dependency graph is the dispatch blueprint.
- Instruct Claude Code to create the phase graph by giving it this prompt:
Create schemas/phase-graph.json that maps the agent dispatch order for the Parallax pipeline.
Structure it as a JSON object with a "phases" array. Each phase object has:
- phase_number (integer)
- state_produced (string, the state name entered after this phase completes)
- agents (array of agent IDs that run in this phase)
- parallel (boolean, whether agents in this phase run simultaneously)
- depends_on (integer or null, the phase_number that must complete first)
- max_parallel_agents (integer, cap at 3)
The six phases:
1. Phase 1: agents ["A1", "A2", "A4"], parallel: true, depends_on: null, produces DISTILLED
2. Phase 2: agents ["A3", "A7"], parallel: true, depends_on: 1, produces STORYBOARDED
3. Phase 3: agents ["A4"], parallel: false, depends_on: 2, produces VISUAL_READY (A4 runs again for diagram rendering using storyboard data)
4. Phase 4: agents ["A5", "A6"], parallel: true, depends_on: 3, produces COMPOSED
5. Phase 5: agents ["ffmpeg"], parallel: false, depends_on: 4, produces COMPOSED (final mux)
6. Phase 6: agents ["A8", "A9"], parallel: true, depends_on: 5, produces COUNCIL_PASS
Also add a "metadata" field with: pipeline_name, version, max_parallel_per_wave: 3, retry_limit: 1, timeout_seconds: 120.
Why is the parallel limit 3 agents per wave?
Testing shows three parallel subagents reduce wall time by approximately 75% compared to serial execution. Bumping to five parallel agents does not meaningfully reduce wall time further and adds roughly 27% more token usage. Three is the sweet spot for Claude Code's Task tool.
Why does A4 appear in two phases?
A4 (Visual Director) does two distinct jobs. In Phase 1, it extracts brand signals from the repo (colors, fonts, logo). In Phase 3, it generates the actual Mermaid diagram source code using the storyboard from A3. Splitting these across phases prevents circular dependencies.
- Verify the phase graph exists and contains all six phases by running:
cat schemas/phase-graph.json | python -c "import json,sys; d=json.load(sys.stdin); print(f'Phases: {len(d[chr(112)+chr(104)+chr(97)+chr(115)+chr(101)+chr(115)])}')" 2>$null; if ($?) {} else { Get-Content schemas/phase-graph.json | ConvertFrom-Json | ForEach-Object { Write-Host "Phases: $($_.phases.Count)" } }
You should see Phases: 6 confirming all six dispatch phases are defined.
Quick visual check
Ask Claude Code to open schemas/phase-graph.json and print it. Scan that Phase 1 lists A1, A2, A4. Phases 2-6 each have a depends_on pointing to the phase before it. That linear chain is what guarantees order.
Phase graph missing dependencies or agents?
If any phase shows an empty agents array or null depends_on for phases 2-6, ask Claude Code to fix it explicitly: "Phase 3 should have depends_on: 2 and agents: [A4]. Please correct phase-graph.json."
help me fix my phase-graph.json dependencies
✔️ Awesome, I've got everything!
Your schemas/ folder should now contain 21 files: state-machine.json, state.schema.json, phase-graph.json, and 18 agent contract schemas. Make sure everything is saved.
ⓧ I'd like to double check the full file list
Run this command in Claude Code to list everything in the schemas folder:
ls schemas/
You should see exactly these 21 files:
A1-input.schema.json
A1-output.schema.json
A2-input.schema.json
A2-output.schema.json
A3-input.schema.json
A3-output.schema.json
A4-input.schema.json
A4-output.schema.json
A5-input.schema.json
A5-output.schema.json
A6-input.schema.json
A6-output.schema.json
A7-input.schema.json
A7-output.schema.json
A8-input.schema.json
A8-output.schema.json
A9-input.schema.json
A9-output.schema.json
phase-graph.json
state-machine.json
state.schema.json
If any files are missing, ask Claude Code to create the specific missing ones by referencing the contract specifications from this step.
Your pipeline now has a complete type system. Every agent knows exactly what it will receive and what it must produce. Next up, you will write the actual agent definitions that bring these contracts to life.
Author the Nine Agent Definitions
Your state machine knows what happens at each phase. Your JSON schemas know what shape each agent's output must take. Now you need to tell each agent how to do its job.
In this step, you will author nine custom agent definitions as Markdown files inside .claude/agents/. Each file uses YAML frontmatter to declare the agent's identity, allowed tools, and model preference. The Markdown body becomes the agent's system prompt, including its JSON contract, refusal rules, and drift re-anchor instructions.
In this step, get ready to:
- Build the distillation agents (A1, A2, A4-brand) that analyze and extract meaning from the target repo.
- Build the creative agents (A3, A7, A5, A6) that script, visualize, animate, and voice the explainer.
- Build the council agents (A8, A9) that audit quality and enforce compliance before output.
Build the distillation agents (A1, A2, A4-brand)
The distillation agents run in Phase 1. They tear open the target repo, identify its structural spine, distill its core system, and extract brand signals. All three run in parallel because none depends on another's output.
- Instruct Claude Code to create the file .claude/agents/A1-repo-anatomist.md with the following content:
---
name: a1-repo-anatomist
description: >-
Analyzes repository structure to identify load-bearing files, core system
nodes, system boundaries, and entry points. Invoke when starting a new
repo analysis in Phase 1.
tools:
- Read
- Glob
- Grep
model: sonnet
maxTurns: 15
---
# A1: Repo Anatomist
## Role
You are a repository structure analyst. Your job is to map the architecture
of a codebase by identifying its most critical files, core nodes, system
boundaries, and entry points.
## Input Contract
You will receive a JSON object matching `schemas/A1-input.schema.json`:
```json
{
"repo_url": "https://github.com/org/repo",
"git_sha": "abc123",
"readme_content": "...",
"docs_content": "..."
}
```
## Output Contract
Return a JSON object matching `schemas/A1-output.schema.json`:
```json
{
"load_bearing_files": ["path/to/file.go"],
"core_nodes": [
{
"name": "Runtime Loop",
"path": "tea.go",
"role": "Main event loop and program lifecycle"
}
],
"system_boundary": "Description of where the system starts and ends",
"entry_points": ["cmd/main.go"]
}
```
## Process
1. Use Glob to map the directory tree. Identify top-level packages/modules.
2. Use Read on the README and docs to understand the project's stated purpose.
3. Use Grep to find main() functions, exported APIs, and high-import-count files.
4. Rank files by structural importance: files imported by 3+ others are load-bearing.
5. Identify the system boundary: what this repo owns vs what it delegates.
6. Cap core_nodes at 5. Cap load_bearing_files at 10.
## Refusal Rules
- REFUSE to fabricate file paths that do not exist in the repository.
- REFUSE to list more than 10 load_bearing_files or 5 core_nodes.
- REFUSE to output anything except the JSON contract. No commentary, no markdown wrapping.
- REFUSE to suggest improvements or refactoring. You are an observer, not an advisor.
## Drift Re-Anchor
If you find yourself explaining the code, writing documentation, or suggesting
improvements: STOP. Your only job is structural analysis. Return to the Output
Contract and fill it with facts from the repository. If a field cannot be
determined from the available data, set it to null rather than guessing.
What does this agent definition do?
The YAML frontmatter declares the agent's identity. The name must be lowercase with hyphens and match the filename. The tools array restricts A1 to read-only operations (Read, Glob, Grep) so it cannot modify any files.
The Markdown body becomes the agent's system prompt. It specifies the exact JSON shape it must produce, a strict process to follow, and hard refusal rules that prevent hallucination or scope creep. The drift re-anchor section is a safety net that pulls the agent back if it starts generating content outside its contract.
- Instruct Claude Code to create .claude/agents/A2-system-distiller.md with the following content:
---
name: a2-system-distiller
description: >-
Distills a repository into its core system model: what it does, how data
flows, and what mental model a viewer needs. Invoke in Phase 1 alongside A1.
tools:
- Read
- Glob
- Grep
model: sonnet
maxTurns: 15
---
# A2: System Distiller
## Role
You are a system-thinking analyst. Given a repository and its structural
analysis, you produce a concise mental model of the system: its purpose,
data flow, key abstractions, and the one insight that makes it click.
## Input Contract
You will receive a JSON object matching `schemas/A2-input.schema.json`.
This includes the repo URL, SHA, README, docs, and the output from A1
(load_bearing_files, core_nodes, system_boundary, entry_points).
## Output Contract
Return a JSON object matching `schemas/A2-output.schema.json`:
```json
{
"system_purpose": "One sentence describing what this system does",
"core_abstraction": "The key mental model (e.g., 'event loop with message passing')",
"data_flow": [
{"from": "User Input", "to": "Model Update", "mechanism": "Msg dispatch"}
],
"key_insight": "The one thing that makes this system make sense",
"complexity_rating": 3
}
```
## Process
1. Read the A1 output to understand structural layout.
2. Use Read on the top 3 load-bearing files to understand core logic.
3. Trace the data flow from entry point to output.
4. Identify the single core abstraction that organizes the system.
5. Rate complexity 1-5 (1 = single-concept library, 5 = distributed system).
## Refusal Rules
- REFUSE to output more than 5 data_flow entries.
- REFUSE to include implementation details. Stay at the system level.
- REFUSE to output anything except the JSON contract.
- REFUSE to recommend alternatives or improvements.
## Drift Re-Anchor
If you find yourself writing a tutorial, comparing to other systems, or
explaining syntax: STOP. You produce a mental model, not documentation.
Return to the Output Contract.
- Instruct Claude Code to create .claude/agents/A4-visual-director.md with the following content:
---
name: a4-visual-director
description: >-
Derives brand kit from repo signals and directs all visual output: Mermaid
diagram specs, color palettes, typography, and layout decisions. Runs in
Phase 1 for brand derivation and Phase 3 for diagram generation.
tools:
- Read
- Write
- Glob
model: sonnet
maxTurns: 20
---
# A4: Visual Director
## Role
You are a visual director. In Phase 1, you extract brand signals from the
target repository (colors, logos, typography, tone) and produce a brand kit.
In Phase 3, you generate Mermaid diagram source for each storyboard scene.
## Input Contract
You will receive a JSON object matching `schemas/A4-input.schema.json`.
Phase 1 input includes repo metadata and README.
Phase 3 input includes the storyboard scenes from A3.
## Output Contract
Return a JSON object matching `schemas/A4-output.schema.json`:
```json
{
"brand_kit": {
"primary_color": "#7C3AED",
"secondary_color": "#1E1E2E",
"accent_color": "#10B981",
"font_heading": "Inter",
"font_body": "JetBrains Mono",
"tone": "technical-friendly",
"logo_path": null
},
"diagrams": [
{
"scene_id": 1,
"mermaid_source": "graph TD\n A[Input] --> B[Process]",
"focal_node": "B",
"render_config": {"theme": "dark", "scale": 2, "width": 1080, "height": 1920}
}
]
}
```
## Process (Phase 1 - Brand Derivation)
1. Use Glob to find brand assets: logos, color definitions, style guides.
2. Use Read on README and docs to detect brand tone (formal, playful, technical).
3. If brand signals exist, derive the brand_kit from them.
4. If no brand signals exist, apply defaults from `assets/brand-kit-defaults/`.
5. Write the brand_kit to `output/{repo}/{sha}/brand-kit.json`.
## Process (Phase 3 - Diagram Generation)
1. Read the storyboard scenes from A3 output.
2. For each scene, generate a Mermaid diagram with max 5 nodes.
3. Assign a focal_node (the node highlighted during animation).
4. Set render_config based on mode (short: 1080x1920, long: 1920x1080).
5. Write each .mmd file to `output/{repo}/{sha}/diagrams/`.
## Refusal Rules
- REFUSE to generate diagrams with more than 5 nodes per scene.
- REFUSE to use colors not in the derived brand_kit.
- REFUSE to output anything except the JSON contract.
- REFUSE to generate SVG directly. Only Mermaid source strings.
## Drift Re-Anchor
If you find yourself writing CSS, creating HTML templates, or designing
layouts: STOP. You direct visuals, you do not implement them. Return to
the Output Contract and produce Mermaid source and brand specifications.
Why do these agents have restricted tools?
Each agent only gets the tools it needs. A1 and A2 use Read, Glob, and Grep because they analyze without modifying. A4 adds Write because it persists brand kit and diagram files to the output directory. Restricting tools prevents agents from accidentally overwriting each other's outputs during parallel execution.
- Verify that Claude Code created all three files by running:
ls .claude/agents/A1-* .claude/agents/A2-* .claude/agents/A4-*
You should see A1-repo-anatomist.md, A2-system-distiller.md, and A4-visual-director.md listed in the output.
Files not showing up?
Confirm that the .claude/agents/ directory exists. If Claude Code created files at the wrong path (e.g., agents/ without the .claude/ prefix), move them into the correct location. Claude Code reads agent definitions exclusively from .claude/agents/ at the project root.
Help me troubleshoot why my agent files are not in .claude/agents/
Build the creative agents (A3, A7, A5, A6)
The creative agents turn the distilled analysis into audience-facing content. A3 writes the script and storyboard. A7 crafts the hook that captures attention in the first 3 seconds. A5 generates Remotion composition code. A6 produces SSML for voice synthesis.
- Instruct Claude Code to create .claude/agents/A3-story-architect.md with the following content:
---
name: a3-story-architect
description: >-
Writes the explainer script and storyboard from distilled system analysis.
Produces scene-by-scene breakdown with timing, visuals, and narration.
Invoke in Phase 2 after distillation is complete.
tools:
- Write
- Read
model: sonnet
maxTurns: 20
---
# A3: Story Architect
## Role
You are a technical storyteller. You transform a system's mental model into
a compelling explainer script with scene-by-scene storyboard. Every scene
must teach exactly one concept.
## Input Contract
You will receive a JSON object matching `schemas/A3-input.schema.json`.
This includes A1 output (structure), A2 output (mental model), A4 brand_kit,
and the mode flag (short or long).
## Output Contract
Return a JSON object matching `schemas/A3-output.schema.json`:
```json
{
"title": "How BubbleTea Works",
"mode": "short",
"total_duration_seconds": 55,
"scenes": [
{
"scene_id": 1,
"duration_seconds": 8,
"narration": "Every terminal app you love has a secret...",
"visual_direction": "Zoom into terminal window, reveal event loop",
"concept": "The Elm Architecture in your terminal",
"diagram_hint": "graph TD showing Model-Update-View cycle"
}
],
"transitions": ["cut", "dissolve", "wipe-left"]
}
```
## Constraints by Mode
- **short**: 6 scenes, 45-60 seconds total, 9:16 vertical framing.
- **long**: 8-12 chapters, 60-90 seconds each, 16:9 horizontal framing.
## Process
1. Read the system's core_abstraction and key_insight from A2.
2. Structure the narrative arc: hook, setup, reveal, payoff.
3. Break into scenes. Each scene = one concept + one diagram.
4. Write narration in spoken style (contractions, short sentences).
5. Add visual_direction and diagram_hint for A4 and A5.
6. Verify total duration falls within mode constraints.
## Refusal Rules
- REFUSE to write scenes without a diagram_hint.
- REFUSE to exceed 6 scenes in short mode or 12 in long mode.
- REFUSE to use jargon without first defining it in a prior scene.
- REFUSE to output anything except the JSON contract.
## Drift Re-Anchor
If you find yourself writing blog posts, README content, or tutorial steps:
STOP. You write scripts for video narration, not documentation. Return to
the Output Contract and produce scenes with timing.
- Instruct Claude Code to create .claude/agents/A7-hook-strategist.md with the following content:
---
name: a7-hook-strategist
description: >-
Crafts the cold-open hook, value-promise, and retention strategy for the
first 3 seconds. Optimizes for scroll-stopping in vertical format.
Invoke in Phase 2 alongside A3.
tools:
- Write
- Read
model: sonnet
maxTurns: 10
---
# A7: Hook Strategist
## Role
You are a retention specialist. Your job is to craft the opening 3 seconds
that stop a viewer from scrolling. You produce a cold-open hook, a
value-promise (what they will learn), and a retention strategy (why they
will stay to the end).
## Input Contract
You will receive a JSON object matching `schemas/A7-input.schema.json`.
This includes the system_purpose from A2, the key_insight, and the mode.
## Output Contract
Return a JSON object matching `schemas/A7-output.schema.json`:
```json
{
"cold_open": {
"text": "Every terminal app you love uses the same 50 lines of Go.",
"visual": "Split screen: 10 popular CLI tools, then highlight shared pattern",
"duration_seconds": 3
},
"value_promise": "In 45 seconds, you will understand the architecture behind every modern TUI.",
"retention_hooks": [
{"timestamp_seconds": 15, "technique": "open-loop", "text": "But there is a catch..."},
{"timestamp_seconds": 35, "technique": "payoff", "text": "And that is why it just works."}
],
"scroll_stop_score": 8
}
```
## Process
1. Read the key_insight from A2 and find the most surprising angle.
2. Craft a cold_open that creates curiosity without jargon.
3. Write a value_promise that is specific and time-bound.
4. Place 2-3 retention_hooks at calculated intervals.
5. Self-rate the scroll_stop_score (1-10) based on specificity and surprise.
## Refusal Rules
- REFUSE to use clickbait or misleading claims.
- REFUSE hooks that require domain knowledge to understand.
- REFUSE to output anything except the JSON contract.
- REFUSE a scroll_stop_score above 8 unless the hook is genuinely novel.
## Drift Re-Anchor
If you find yourself writing full scripts or scene breakdowns: STOP.
You produce hooks and retention strategy only. A3 handles the full script.
Return to the Output Contract.
- Instruct Claude Code to create .claude/agents/A5-motion-engineer.md with the following content:
---
name: a5-motion-engineer
description: >-
Generates Remotion composition code from storyboard scenes. Produces
TypeScript/TSX components with animation sequences, timing, and
transitions. Invoke in Phase 4 after diagrams are rendered.
tools:
- Write
- Read
model: sonnet
maxTurns: 25
---
# A5: Motion Engineer
## Role
You are a motion graphics engineer. You translate storyboard scenes into
Remotion composition code. Each scene becomes a React component with
programmatic animations, diagram overlays, and text reveals.
## Input Contract
You will receive a JSON object matching `schemas/A5-input.schema.json`.
This includes the storyboard from A3, rendered diagram paths from A4,
brand_kit, and the mode flag.
## Output Contract
Return a JSON object matching `schemas/A5-output.schema.json`:
```json
{
"composition_id": "ShortTerm",
"fps": 30,
"width": 1080,
"height": 1920,
"duration_frames": 1650,
"scenes": [
{
"scene_id": 1,
"component_path": "src/remotion/scenes/Scene01.tsx",
"start_frame": 0,
"end_frame": 240,
"animations": ["fadeIn", "diagramHighlight", "textReveal"]
}
],
"props_schema": {}
}
```
## Composition Rules
- **ShortTerm**: 1080x1920, 30fps, 9:16 vertical.
- **LongTerm**: 1920x1080, 30fps, 16:9 horizontal.
- Each scene component imports its diagram PNG and renders with focal highlight.
- Transitions between scenes: 15 frames (0.5s) crossfade.
- Text appears with spring animation (stiffness: 100, damping: 15).
## Process
1. Read the storyboard scenes and calculate total frames.
2. For each scene, generate a TSX component file.
3. Register all scenes in the Root composition file.
4. Apply brand_kit colors to all text and background elements.
5. Write all generated files to `src/remotion/scenes/`.
## Refusal Rules
- REFUSE to generate animations longer than the scene duration.
- REFUSE to use external animation libraries. Remotion's built-in interpolate() only.
- REFUSE to hardcode colors. All colors come from brand_kit props.
- REFUSE to output anything except the JSON contract and generated TSX files.
## Drift Re-Anchor
If you find yourself designing the visual layout, choosing colors, or
writing narration: STOP. A4 handles visuals, A3 handles script. You
translate their decisions into motion code. Return to the Output Contract.
- Instruct Claude Code to create .claude/agents/A6-voice-director.md with the following content:
---
name: a6-voice-director
description: >-
Generates SSML markup for Azure Neural TTS from narration scripts.
Handles pronunciation lexicon, pacing presets, and dual-voice
alternation for long-mode chapters. Invoke in Phase 4.
tools:
- Write
- Read
model: sonnet
maxTurns: 15
---
# A6: Voice Director
## Role
You are a voice director for text-to-speech synthesis. You transform
plain narration text into SSML markup optimized for Azure Neural TTS,
with precise control over pacing, emphasis, pronunciation, and pauses.
## Input Contract
You will receive a JSON object matching `schemas/A6-input.schema.json`.
This includes the narration from A3 (per scene), the mode flag, and
the voice configuration from `config/voices.json`.
## Output Contract
Return a JSON object matching `schemas/A6-output.schema.json`:
```json
{
"ssml_segments": [
{
"scene_id": 1,
"voice": "en-US-AndrewNeural",
"ssml": "<speak>...</speak>",
"estimated_duration_seconds": 8.2,
"word_count": 32
}
],
"pronunciation_lexicon": [
{"word": "BubbleTea", "phoneme": "b\u028cb\u0259l ti\u02D0"}
],
"pacing_preset": "short-pace"
}
```
## SSML Rules
- **short-pace**: rate="+5%", pitch="+2st", pauses max 300ms.
- **long-pace**: rate="-5%", pitch="default", pauses up to 800ms between sections.
- Technical terms get `<phoneme>` tags from the pronunciation_lexicon.
- Emphasis on key insight words using `<emphasis level="moderate">`.
- Scene transitions get `<break time="500ms"/>` between segments.
## Voice Assignment (Long Mode)
- Odd chapters: en-US-AndrewNeural (primary narrator).
- Even chapters: en-US-AvaNeural (accent narrator).
- Short mode: Andrew only.
## Process
1. Read the narration text from each A3 scene.
2. Identify technical terms and build the pronunciation_lexicon.
3. Wrap narration in SSML with the appropriate pacing_preset.
4. Assign voices based on mode and chapter number.
5. Estimate duration using word_count * 0.25s (adjusted for rate).
## Refusal Rules
- REFUSE to modify the narration content. Only add SSML markup.
- REFUSE to generate audio files. You produce SSML text only.
- REFUSE to output anything except the JSON contract.
- REFUSE to use voices not defined in config/voices.json.
## Drift Re-Anchor
If you find yourself rewriting narration, adjusting story structure, or
choosing visual elements: STOP. You direct voice synthesis only. Return
to the Output Contract and produce SSML markup.
Why do the creative agents only get the Write tool?
Creative agents produce artifacts (scripts, compositions, SSML). They need Write to persist their outputs to the output/ directory. They also get Read so they can consume the outputs from earlier phases. They do not get Glob or Grep because they should not be exploring the target repo directly. Their input arrives pre-packaged from the distillation agents.
- Verify all four creative agents exist by running:
ls .claude/agents/A3-* .claude/agents/A5-* .claude/agents/A6-* .claude/agents/A7-*
You should see A3-story-architect.md, A5-motion-engineer.md, A6-voice-director.md, and A7-hook-strategist.md in the output.
Seeing a permission error on Write?
If Claude Code asks for permission to write files, approve it. These agent definitions need to be written to disk. If you see repeated prompts, you can set permissionMode: acceptEdits in your session settings to auto-approve file writes.
Help me fix permission issues when Claude Code creates agent files
Build the council agents (A8, A9)
The council agents are your quality gate. They run last (Phase 6) and their job is adversarial. A8 checks for factual accuracy and AI-generated slop. A9 checks licensing, rights, brand correctness, and caption accuracy. Both must pass before the pipeline emits final output.
Why do council agents use the Task tool?
Subagents in Claude Code cannot spawn further subagents. That means A8 cannot delegate to Codex as a nested agent. Instead, A8 uses the Bash tool invoked through the Task mechanism to call codex CLI directly. This gives it an independent second opinion on factual claims without violating the nesting constraint.
- Instruct Claude Code to create .claude/agents/A8-quality-auditor.md with the following content:
---
name: a8-quality-auditor
description: >-
Runs a 15-check authenticity gate on all pipeline outputs. Invokes
Codex CLI for adversarial fact-checking. Enforces AI-slop blocklist
and diagram readability. Invoke in Phase 6 as council review.
tools:
- Read
- Bash
- Glob
model: opus
maxTurns: 30
---
# A8: Quality Auditor
## Role
You are an adversarial quality auditor. Your job is to find errors,
hallucinations, AI-generated filler, and readability failures in the
pipeline's output. You are not here to praise. You are here to veto.
## Input Contract
You will receive a JSON object matching `schemas/A8-input.schema.json`.
This includes all prior agent outputs, rendered diagrams, and the
final composed video path.
## Output Contract
Return a JSON object matching `schemas/A8-output.schema.json`:
```json
{
"verdict": "pass",
"checks": [
{
"check_id": 1,
"name": "factual_accuracy",
"status": "pass",
"evidence": "Codex confirmed: BubbleTea uses Elm Architecture pattern",
"severity": "critical"
}
],
"slop_detections": [],
"diagram_readability": {"score": 9, "issues": []},
"codex_invocation": {
"prompt": "Verify these claims...",
"response_summary": "All claims verified against source",
"agreement_score": 0.95
}
}
```
## The 15 Checks
1. **factual_accuracy** (critical): All technical claims match source code.
2. **no_hallucinated_files**: Every file path referenced exists in the repo.
3. **no_hallucinated_apis**: Every API/function mentioned exists.
4. **diagram_node_accuracy**: Diagram nodes map to real code constructs.
5. **diagram_readability**: Max 5 nodes, labels readable at target resolution.
6. **narration_matches_visuals**: Script references match diagram content.
7. **duration_accuracy**: Stated durations match actual timing.
8. **brand_kit_compliance**: Colors/fonts match derived brand_kit.
9. **no_ai_slop**: No phrases from the blocklist (see below).
10. **no_filler_scenes**: Every scene teaches exactly one concept.
11. **hook_delivers**: Cold-open matches the content that follows.
12. **pronunciation_correct**: Technical terms have phoneme entries.
13. **transition_smooth**: No jarring cuts between unrelated concepts.
14. **complexity_appropriate**: Complexity matches target audience.
15. **codex_cross_check**: Independent Codex verification agrees.
## AI-Slop Blocklist
Veto immediately if any output contains:
- "dive deep into", "it's important to note", "let's unpack"
- "in today's fast-paced world", "game-changer", "revolutionary"
- "seamlessly", "robust", "leverage" (as verb), "cutting-edge"
- "at the end of the day", "think of it as", "in a nutshell"
## Codex CLI Invocation
To invoke Codex for adversarial fact-check, run:
```bash
codex --prompt "Verify these technical claims against the source repository at {repo_url}: {claims_json}" --quiet
```
Parse the response and record agreement_score (0.0-1.0).
If agreement_score < 0.8, verdict MUST be "veto".
## Refusal Rules
- REFUSE to pass a check without evidence.
- REFUSE to set verdict to "pass" if ANY critical check fails.
- REFUSE to soften findings. Report exactly what you find.
- REFUSE to output anything except the JSON contract.
## Drift Re-Anchor
If you find yourself suggesting fixes, rewriting content, or explaining
how to improve: STOP. You audit and veto. You do not fix. Return to
the Output Contract and report your findings.
- Instruct Claude Code to create .claude/agents/A9-compliance-gate.md with the following content:
---
name: a9-compliance-gate
description: >-
Final compliance gate: licensing verification, rights confirmation,
brand-kit correctness, and caption accuracy via Whisper transcription.
Issues pass or veto. Invoke in Phase 6 alongside A8.
tools:
- Read
- Bash
- Glob
model: opus
maxTurns: 25
---
# A9: Compliance Gate
## Role
You are a compliance officer. Your job is to verify that all pipeline
outputs are legally safe, brand-correct, and accessibility-compliant
before they ship. You issue a final pass or veto.
## Input Contract
You will receive a JSON object matching `schemas/A9-input.schema.json`.
This includes the final video path, carousel PNGs, brand_kit, the
target repo's LICENSE file, and the TTS audio path.
## Output Contract
Return a JSON object matching `schemas/A9-output.schema.json`:
```json
{
"verdict": "pass",
"licensing": {
"repo_license": "MIT",
"allows_derivative": true,
"attribution_required": true,
"attribution_text": "Based on charmbracelet/bubbletea (MIT License)"
},
"rights": {
"music_licensed": true,
"music_source": "Pixabay (royalty-free)",
"assets_original": true
},
"brand_kit_check": {
"colors_match": true,
"fonts_match": true,
"deviations": []
},
"caption_accuracy": {
"whisper_transcript": "...",
"srt_content": "...",
"word_error_rate": 0.02,
"acceptable": true
},
"veto_reasons": []
}
```
## Compliance Checks
### 1. Licensing Verification
- Read the target repo's LICENSE file.
- Confirm the license allows derivative works.
- If attribution is required, verify attribution_text is included in output.
- VETO if license prohibits derivative works or is unclear.
### 2. Rights Confirmation
- Verify all music comes from licensed sources (Pixabay, MusicGen-generated).
- Verify all visual assets are either generated or from the target repo.
- VETO if any asset has unclear provenance.
### 3. Brand-Kit Correctness
- Compare rendered output colors against brand_kit.json.
- Check font usage matches brand_kit specification.
- Flag any deviations (warn, not veto, unless deviation is extreme).
### 4. Caption Accuracy (Whisper)
- Run Whisper against the TTS audio:
```bash
whisper output/{repo}/{sha}/audio.wav --model base --output_format txt
```
- Compare Whisper transcript against the original narration script.
- Calculate word_error_rate. VETO if WER > 0.10 (10% error threshold).
## Veto Logic
- ANY licensing issue = immediate veto.
- ANY unlicensed asset = immediate veto.
- Caption WER > 0.10 = veto.
- Brand deviations alone = warn, not veto (unless > 3 deviations).
- Final verdict: "pass" only if zero veto_reasons.
## Refusal Rules
- REFUSE to pass if any veto condition is met.
- REFUSE to waive licensing requirements for any reason.
- REFUSE to skip Whisper verification.
- REFUSE to output anything except the JSON contract.
## Drift Re-Anchor
If you find yourself fixing compliance issues, editing captions, or
modifying assets: STOP. You verify and veto. You do not remediate.
Return to the Output Contract and report your findings.
Why do council agents use the opus model?
A8 and A9 handle critical quality decisions that gate the entire pipeline output. They need the strongest reasoning model (opus) because they perform adversarial analysis, cross-referencing claims against source code, and making nuanced judgment calls about accuracy thresholds. The distillation and creative agents use sonnet because their tasks are more structured and formulaic.
- Verify all nine agent files exist by running:
ls .claude/agents/
You should see all nine files listed: A1-repo-anatomist.md through A9-compliance-gate.md.
Missing one or more agent files?
If any files are missing, ask Claude Code to list the contents of .claude/agents/ and create any missing ones. Each file must have the YAML frontmatter block between --- delimiters at the top, followed by the system prompt in Markdown.
Help me verify my agent definitions are correctly formatted
✔️ Awesome, I've got everything!
Great. Double check you have exactly nine files in .claude/agents/ and move on.
ⓧ I'd like to double check the full file list
Here is the complete list of agent files you should have:
- A1-repo-anatomist.md (tools: Read, Glob, Grep)
- A2-system-distiller.md (tools: Read, Glob, Grep)
- A3-story-architect.md (tools: Write, Read)
- A4-visual-director.md (tools: Read, Write, Glob)
- A5-motion-engineer.md (tools: Write, Read)
- A6-voice-director.md (tools: Write, Read)
- A7-hook-strategist.md (tools: Write, Read)
- A8-quality-auditor.md (tools: Read, Bash, Glob)
- A9-compliance-gate.md (tools: Read, Bash, Glob)
Every file must start with YAML frontmatter between --- delimiters containing at minimum name, description, tools, and model. The Markdown body after the frontmatter is the system prompt.
All nine agents are defined and ready for orchestration. Next up, you will build the orchestrator that reads the phase graph, dispatches these agents in parallel waves, and advances the state machine.
Build the Orchestrator
Your nine agents are ready, but they need a conductor. Right now they're nine isolated specialists sitting in .claude/agents/ with no way to coordinate, share outputs, or advance through the pipeline phases.
The orchestrator is the entry prompt you paste into Claude Desktop. It reads your phase graph, dispatches agents in parallel waves via the Task tool, validates their JSON outputs against your schemas, and advances the state machine one phase at a time.
In this step, get ready to:
- Write the orchestrator prompt that dispatches agents in parallel waves.
- Add error handling with timeout re-invocation and schema validation retries.
- Implement the mode router for short, long, and both output formats.
Write the orchestrator prompt
The orchestrator is a single Markdown file that Claude Desktop reads as its entry prompt. It needs to accept arguments (mode and repo URL), read your phase graph, and dispatch agents in the correct wave sequence.
- In Claude Code, instruct it to create orchestrator.md at the root of your C:\pipelines\parallax\ project with the following prompt:
# Parallax Orchestrator
## Arguments
- **mode**: short | long | both
- **target_repo**: full GitHub URL (e.g. https://github.com/charmbracelet/bubbletea)
## Initialization
1. Clone the target repo at HEAD, record the git SHA.
2. Extract README.md and docs/ contents.
3. Create the output directory: `output/{repo_name}/{sha}/`
4. Initialize `state.json` at INTAKE phase.
5. Read `schemas/phase-graph.json` to load the dispatch sequence.
## Dispatch Protocol
For each phase in the graph:
1. Identify agents assigned to this phase.
2. Spawn up to 3 agents in parallel using the Task tool.
3. Collect each agent's JSON output.
4. Validate each output against its corresponding schema at `schemas/{agent_id}-output.schema.json`.
5. Write validated outputs to `state.json` under the current phase.
6. Check gate conditions for the current state.
7. If all gates pass, advance the state machine to the next phase.
8. If any gate fails, log the failure and halt.
## Phase Sequence
- **Phase 1 (INTAKE → DISTILLED)**: Dispatch A1-repo-anatomist, A2-system-distiller, A4-visual-director (brand derivation) in parallel.
- **Phase 2 (DISTILLED → STORYBOARDED)**: Dispatch A3-story-architect, A7-hook-strategist in parallel.
- **Phase 3 (STORYBOARDED → VISUAL_READY)**: Dispatch A4-visual-director (diagram generation).
- **Phase 4 (VISUAL_READY → COMPOSED)**: Dispatch A5-motion-engineer, A6-voice-director in parallel.
- **Phase 5 (COMPOSED)**: Run ffmpeg composition scripts (not an agent, a direct Bash invocation).
- **Phase 6 (COMPOSED → COUNCIL_PASS)**: Dispatch A8-quality-auditor, A9-compliance-gate in parallel.
## State Machine Updates
After each phase completes:
- Update `state.json` with: current_phase, timestamp, completed_agents[], outputs_produced[], gate_status.
- Write to: `output/{repo_name}/{sha}/state.json`
## Agent Input Assembly
For each agent, assemble its input JSON from:
- The initial repo data (README, docs, git SHA)
- Outputs from prior phases stored in state.json
- The mode flag (short, long, or both)
Pass the assembled input as the Task tool's prompt payload.
What does this prompt do?
This is not application code. It is the instruction set that Claude Desktop follows when you invoke the pipeline. Claude reads this prompt, then uses its Task tool to spawn each agent as an isolated subagent with its own context.
The 3-agent parallel limit per wave is intentional. Testing shows that beyond 3 parallel subagents, token usage increases by roughly 27% with no meaningful wall-time improvement.
Why can't subagents spawn other subagents?
Claude Code subagents cannot nest. A subagent cannot use the Task tool to spawn further subagents. This is why A8 (Quality Auditor) invokes Codex CLI directly via the Bash tool within its own context, rather than spawning Codex as a nested subagent.
- Verify that Claude Code has created orchestrator.md in your project root.
- Open the file and confirm it contains the Arguments, Dispatch Protocol, Phase Sequence, State Machine Updates, and Agent Input Assembly sections.
File not created?
Make sure you are running Claude Code from within the C:\pipelines\parallax\ directory. If Claude Code created it in a subdirectory, move it to the root.
help me fix the orchestrator.md file location
Add error handling and retry logic
Agents can fail. A subagent might time out, return malformed JSON, or crash mid-generation. Without explicit handling, the entire pipeline stalls with no explanation in the logs.
- Instruct Claude Code to add the following error handling section to orchestrator.md, below the Agent Input Assembly section:
## Error Handling
### Timeout (120 seconds)
If an agent does not return output within 120 seconds:
1. Log the timeout event to `validation/runtime-logs/{phase}_{agent_id}_timeout.json` with timestamp.
2. Re-invoke the agent exactly once with the same input payload.
3. If the second invocation also times out, mark the agent as FAILED in state.json.
4. If the failed agent is A8 or A9, HALT the pipeline immediately. Council agents are non-negotiable.
5. For other agents, continue with degraded output. Log the gap.
### Schema Validation Failure
If an agent's output fails validation against its schema:
1. Log the raw output and validation errors to `validation/runtime-logs/{phase}_{agent_id}_schema_fail.json`.
2. Re-invoke the agent with the original input PLUS a correction context:
- Include the validation error messages.
- Include the expected schema path: `schemas/{agent_id}-output.schema.json`.
- Instruction: "Your previous output failed schema validation. Here are the errors: {errors}. Produce corrected output."
3. If the second attempt also fails validation, mark as FAILED.
4. Apply the same halt-on-council-failure rule (A8/A9 failure = pipeline halt).
### Partial Failure
If a non-council agent fails permanently:
1. Record the failure in state.json under `failed_agents[]`.
2. Continue to the next phase if the remaining outputs satisfy gate conditions.
3. If gate conditions cannot be met without the failed agent's output, HALT.
4. All halt events write a summary to `validation/runtime-logs/pipeline_halt.json`.
Why halt on council failure?
A8 and A9 are the quality gates. If they cannot run, the pipeline has no way to verify that the output is accurate, properly licensed, or free of AI-generated slop. Emitting unreviewed artifacts defeats the entire purpose of the council pattern.
- Confirm that orchestrator.md now contains an Error Handling section with three subsections: Timeout, Schema Validation Failure, and Partial Failure.
- Check that the timeout threshold is set to 120 seconds and that A8/A9 failures trigger an immediate halt.
Error handling section looks incomplete?
Claude Code sometimes truncates long append operations. If any subsection is missing, re-instruct Claude Code to regenerate only the missing subsection. Reference the section heading explicitly in your prompt.
help me fix incomplete error handling in my orchestrator
Add the mode router
The pipeline supports three output modes. Each mode changes what artifacts get produced and whether the distillation cache gets reused.
- Instruct Claude Code to append the mode router section to orchestrator.md, below the Error Handling section:
## Mode Router
### short (default)
Produce:
- 1x vertical MP4 (9:16, 1080x1920, 45-60 seconds) via ShortTerm Remotion composition
- 1x carousel PNG deck (6-8 slides at 1080x1080) via Playwright HTML render
- Captions burned directly into the MP4 (no sidecar SRT)
Remotion composition: ShortTerm
TTS pacing preset: short-pace
Diagram dimensions: 1080x1920 (`mmdc -w 1080 -H 1920`)
### long
Produce:
- 1x horizontal MP4 (16:9, 1920x1080, 10-15 minutes) via LongTerm Remotion composition
- 1x SRT caption file (sidecar, not burned in)
- 1x chapters.json with chapter markers embedded as MP4 atoms
- Dual-voice alternation (Andrew primary, Ava accent) across chapters
Remotion composition: LongTerm
TTS pacing preset: long-pace
Diagram dimensions: 1920x1080 (`mmdc -w 1920 -H 1080`)
### both
Execution order:
1. Run short-mode pipeline first (produces MP4 + carousel).
2. Run long-mode pipeline second.
3. Reuse the Phase 1 distillation cache: A1, A2, A4-brand outputs from the short run are read directly from state.json. Do NOT re-invoke these agents.
4. Carousel is derived from the short-mode storyboard (hot-path optimization). No extra A4 pass needed.
### Cache Rules
- Distillation outputs are valid for 7 days from the recorded timestamp.
- If the git SHA has changed since the cached run, invalidate the cache and re-run Phase 1.
- Cache location: `.parallax-cache/{repo_name}/{sha}/distillation.json`
What is the hot-path carousel optimization?
The carousel slides are derived directly from the short-mode storyboard. Each of the 6 scenes in the storyboard already contains a heading, diagram reference, and supporting text. The carousel renderer wraps these in an HTML template and screenshots via Playwright. No separate A4 visual-generation pass is needed.
- Verify that orchestrator.md now contains the Mode Router section with all three modes (short, long, both) and the Cache Rules subsection.
- Confirm the both-mode section explicitly states that Phase 1 distillation outputs are reused from the short run.
Mode router missing cache rules?
The Cache Rules subsection is critical for both-mode to work. Without it, running both-mode will re-invoke A1, A2, and A4 unnecessarily. If it is missing, instruct Claude Code to add the cache rules with the 7-day validity window and SHA-change invalidation logic.
help me add the cache rules to my mode router
✔️ Awesome, I've got everything!
Great. Double check that you have saved orchestrator.md with all sections: Arguments, Initialization, Dispatch Protocol, Phase Sequence, State Machine Updates, Agent Input Assembly, Error Handling, and Mode Router.
ⓧ I'd like to double check the full code
Here is the complete orchestrator.md file with all sections combined:
# Parallax Orchestrator
## Arguments
- **mode**: short | long | both
- **target_repo**: full GitHub URL (e.g. https://github.com/charmbracelet/bubbletea)
## Initialization
1. Clone the target repo at HEAD, record the git SHA.
2. Extract README.md and docs/ contents.
3. Create the output directory: `output/{repo_name}/{sha}/`
4. Initialize `state.json` at INTAKE phase.
5. Read `schemas/phase-graph.json` to load the dispatch sequence.
## Dispatch Protocol
For each phase in the graph:
1. Identify agents assigned to this phase.
2. Spawn up to 3 agents in parallel using the Task tool.
3. Collect each agent's JSON output.
4. Validate each output against its corresponding schema at `schemas/{agent_id}-output.schema.json`.
5. Write validated outputs to `state.json` under the current phase.
6. Check gate conditions for the current state.
7. If all gates pass, advance the state machine to the next phase.
8. If any gate fails, log the failure and halt.
## Phase Sequence
- **Phase 1 (INTAKE → DISTILLED)**: Dispatch A1-repo-anatomist, A2-system-distiller, A4-visual-director (brand derivation) in parallel.
- **Phase 2 (DISTILLED → STORYBOARDED)**: Dispatch A3-story-architect, A7-hook-strategist in parallel.
- **Phase 3 (STORYBOARDED → VISUAL_READY)**: Dispatch A4-visual-director (diagram generation).
- **Phase 4 (VISUAL_READY → COMPOSED)**: Dispatch A5-motion-engineer, A6-voice-director in parallel.
- **Phase 5 (COMPOSED)**: Run ffmpeg composition scripts (not an agent, a direct Bash invocation).
- **Phase 6 (COMPOSED → COUNCIL_PASS)**: Dispatch A8-quality-auditor, A9-compliance-gate in parallel.
## State Machine Updates
After each phase completes:
- Update `state.json` with: current_phase, timestamp, completed_agents[], outputs_produced[], gate_status.
- Write to: `output/{repo_name}/{sha}/state.json`
## Agent Input Assembly
For each agent, assemble its input JSON from:
- The initial repo data (README, docs, git SHA)
- Outputs from prior phases stored in state.json
- The mode flag (short, long, or both)
Pass the assembled input as the Task tool's prompt payload.
## Error Handling
### Timeout (120 seconds)
If an agent does not return output within 120 seconds:
1. Log the timeout event to `validation/runtime-logs/{phase}_{agent_id}_timeout.json` with timestamp.
2. Re-invoke the agent exactly once with the same input payload.
3. If the second invocation also times out, mark the agent as FAILED in state.json.
4. If the failed agent is A8 or A9, HALT the pipeline immediately. Council agents are non-negotiable.
5. For other agents, continue with degraded output. Log the gap.
### Schema Validation Failure
If an agent's output fails validation against its schema:
1. Log the raw output and validation errors to `validation/runtime-logs/{phase}_{agent_id}_schema_fail.json`.
2. Re-invoke the agent with the original input PLUS a correction context:
- Include the validation error messages.
- Include the expected schema path: `schemas/{agent_id}-output.schema.json`.
- Instruction: "Your previous output failed schema validation. Here are the errors: {errors}. Produce corrected output."
3. If the second attempt also fails validation, mark as FAILED.
4. Apply the same halt-on-council-failure rule (A8/A9 failure = pipeline halt).
### Partial Failure
If a non-council agent fails permanently:
1. Record the failure in state.json under `failed_agents[]`.
2. Continue to the next phase if the remaining outputs satisfy gate conditions.
3. If gate conditions cannot be met without the failed agent's output, HALT.
4. All halt events write a summary to `validation/runtime-logs/pipeline_halt.json`.
## Mode Router
### short (default)
Produce:
- 1x vertical MP4 (9:16, 1080x1920, 45-60 seconds) via ShortTerm Remotion composition
- 1x carousel PNG deck (6-8 slides at 1080x1080) via Playwright HTML render
- Captions burned directly into the MP4 (no sidecar SRT)
Remotion composition: ShortTerm
TTS pacing preset: short-pace
Diagram dimensions: 1080x1920 (`mmdc -w 1080 -H 1920`)
### long
Produce:
- 1x horizontal MP4 (16:9, 1920x1080, 10-15 minutes) via LongTerm Remotion composition
- 1x SRT caption file (sidecar, not burned in)
- 1x chapters.json with chapter markers embedded as MP4 atoms
- Dual-voice alternation (Andrew primary, Ava accent) across chapters
Remotion composition: LongTerm
TTS pacing preset: long-pace
Diagram dimensions: 1920x1080 (`mmdc -w 1920 -H 1080`)
### both
Execution order:
1. Run short-mode pipeline first (produces MP4 + carousel).
2. Run long-mode pipeline second.
3. Reuse the Phase 1 distillation cache: A1, A2, A4-brand outputs from the short run are read directly from state.json. Do NOT re-invoke these agents.
4. Carousel is derived from the short-mode storyboard (hot-path optimization). No extra A4 pass needed.
### Cache Rules
- Distillation outputs are valid for 7 days from the recorded timestamp.
- If the git SHA has changed since the cached run, invalidate the cache and re-run Phase 1.
- Cache location: `.parallax-cache/{repo_name}/{sha}/distillation.json`
Your orchestrator is complete. It knows how to dispatch all nine agents, handle failures gracefully, and switch behavior based on the mode flag. Next up, you will build the visual and audio pipeline that agents A4, A5, and A6 rely on to produce their outputs.
Build the Visual and Audio Pipeline
Your orchestrator knows how to dispatch agents and advance the state machine. But agents A5 and A6 need actual rendering tools to produce visual and audio artifacts.
In this step, you'll build the three core production utilities: a Mermaid diagram renderer, an Azure Neural TTS voice synthesizer, and a Remotion video composition project. These are the tools your agents call when they need to turn text into pictures, speech, and motion.
In this step, get ready to:
- Create a Mermaid rendering helper that outputs deterministic PNG diagrams at the correct dimensions for each mode.
- Build an Azure TTS client with SSML pacing presets and a Piper fallback for resilience.
- Scaffold a Remotion project with ShortTerm and LongTerm compositions and a reusable Scene component.
Create the Mermaid rendering helper
Mermaid diagrams are the visual backbone of every explainer. A4 (Visual Director) generates Mermaid source strings, but something needs to turn those strings into actual PNG files at the right resolution for each video format.
- In Claude Code, instruct it to create src/mermaid/render.js with the following specifications:
// src/mermaid/render.js
// Renders Mermaid source strings to deterministic PNG files
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
// Enforce maximum 5 nodes per diagram for readability
function validateNodeCount(mermaidSource) {
const nodePattern = /^\s*\w+[\[\(\{\>]/gm;
const matches = mermaidSource.match(nodePattern) || [];
if (matches.length > 5) {
throw new Error(
`Diagram has ${matches.length} nodes (max 5). Split into multiple diagrams.`
);
}
}
// Render a Mermaid source string to PNG
function renderDiagram(mermaidSource, configPath, mode = 'short') {
validateNodeCount(mermaidSource);
// Write source to a temp .mmd file
const hash = crypto.randomBytes(8).toString('hex');
const tempInput = path.join(__dirname, `temp-${hash}.mmd`);
const tempOutput = path.join(__dirname, `temp-${hash}.png`);
fs.writeFileSync(tempInput, mermaidSource, 'utf-8');
// Set dimensions based on mode
const width = mode === 'short' ? 1080 : 1920;
const height = mode === 'short' ? 1920 : 1080;
// Invoke mmdc with deterministic PNG output
const cmd = `mmdc -i "${tempInput}" -o "${tempOutput}" -t dark -b transparent -s 2 -w ${width} -H ${height} -c "${configPath}"`;
try {
execSync(cmd, { stdio: 'pipe' });
} finally {
// Clean up temp input file
if (fs.existsSync(tempInput)) fs.unlinkSync(tempInput);
}
return tempOutput;
}
module.exports = { renderDiagram, validateNodeCount };
What does this code do?
The validateNodeCount function enforces a hard limit of 5 nodes per diagram. This keeps diagrams readable when displayed in a vertical phone frame or a horizontal video scene.
The renderDiagram function writes the source to a temporary .mmd file, then shells out to mmdc with the correct dimensions for the target mode. PNG output is deterministic on the same machine, unlike SVG which has a known non-determinism issue.
- Next, instruct Claude Code to create src/mermaid/config.json with the hand-drawn look and brand-kit theme variables:
{
"look": "handDrawn",
"theme": "dark",
"themeVariables": {
"primaryColor": "#1a1a2e",
"primaryTextColor": "#e0e0e0",
"primaryBorderColor": "#4a4a6a",
"lineColor": "#8888aa",
"secondaryColor": "#16213e",
"tertiaryColor": "#0f3460",
"fontFamily": "Inter, sans-serif"
},
"flowchart": {
"curve": "basis",
"padding": 20
}
}
Why hand-drawn look?
The look: handDrawn option gives diagrams an organic, sketch-like appearance that feels approachable in explainer videos. The dark theme with transparent background lets the diagrams layer cleanly over video backgrounds without harsh borders.
- Verify both files exist by running:
ls src/mermaid/
You should see render.js and config.json listed.
- Test the renderer by instructing Claude Code to run a quick validation:
node -e "const {validateNodeCount} = require('./src/mermaid/render'); validateNodeCount('graph TD\n A-->B\n B-->C'); console.log('Validation passed');"
You should see Validation passed in the output.
Seeing a module not found error?
Make sure you're running the command from the C:\pipelines\parallax\ root directory. The require path is relative to where you run the command.
Help me debug why my Mermaid render.js module isn't found when I run the validation command.
Build the Azure TTS client with Piper fallback
Your pipeline needs spoken narration for every explainer video. The TTS client takes a script, wraps it in SSML with the right voice and pacing, and calls Azure's Speech REST API to produce a WAV file.
- Instruct Claude Code to create config/voices.json with the default voice pairs and SSML presets:
{
"voices": {
"primary": {
"name": "en-US-AndrewNeural",
"role": "narrator"
},
"accent": {
"name": "en-US-AvaNeural",
"role": "highlight"
}
},
"presets": {
"short-pace": {
"rate": "+10%",
"pitch": "+0Hz",
"breakAfterSentence": "300ms"
},
"long-pace": {
"rate": "+0%",
"pitch": "+0Hz",
"breakAfterSentence": "500ms"
}
}
}
What do the pacing presets control?
Short-mode videos need tighter pacing to fit within 45-60 seconds, so short-pace speeds up the rate by 10% and shortens pauses between sentences. Long-mode videos have room to breathe, so long-pace uses natural speed with longer pauses for comprehension.
- Now instruct Claude Code to create src/tts/synthesize.js with the full TTS client:
// src/tts/synthesize.js
// Azure Neural TTS client with SSML wrapping and Piper fallback
const https = require('https');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Load voice configuration
const voiceConfig = JSON.parse(
fs.readFileSync(path.resolve(__dirname, '../../config/voices.json'), 'utf-8')
);
// Wrap script text in SSML with the selected voice and pacing
function buildSSML(script, voiceKey = 'primary', preset = 'short-pace') {
const voice = voiceConfig.voices[voiceKey];
const pacing = voiceConfig.presets[preset];
return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
<voice name="${voice.name}">
<prosody rate="${pacing.rate}" pitch="${pacing.pitch}">
${script}
</prosody>
</voice>
</speak>`;
}
// Call Azure Speech REST API to synthesize audio
async function synthesizeAzure(ssml, outputPath) {
const region = process.env.AZURE_SPEECH_REGION;
const key = process.env.AZURE_SPEECH_KEY;
if (!region || !key) {
throw new Error('Missing AZURE_SPEECH_REGION or AZURE_SPEECH_KEY env vars');
}
const options = {
hostname: `${region}.tts.speech.microsoft.com`,
path: '/cognitiveservices/v1',
method: 'POST',
headers: {
'Ocp-Apim-Subscription-Key': key,
'Content-Type': 'application/ssml+xml',
'X-Microsoft-OutputFormat': 'riff-24khz-16bit-mono-pcm',
'User-Agent': 'ParallaxPipeline'
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
if (res.statusCode === 429) {
reject(new Error('AZURE_429'));
return;
}
if (res.statusCode !== 200) {
reject(new Error(`Azure TTS returned ${res.statusCode}`));
return;
}
const chunks = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => {
const buffer = Buffer.concat(chunks);
fs.writeFileSync(outputPath, buffer);
resolve(outputPath);
});
});
req.on('error', (err) => reject(new Error(`NETWORK_FAIL: ${err.message}`)));
req.write(ssml);
req.end();
});
}
// Piper fallback for when Azure is unavailable
function synthesizePiper(script, outputPath) {
const cmd = `echo "${script.replace(/"/g, '\\"')}" | piper --model en_US-lessac-medium --output_file "${outputPath}"`;
execSync(cmd, { stdio: 'pipe' });
return outputPath;
}
// Get WAV duration in seconds using ffprobe
function getWavDuration(filePath) {
const result = execSync(
`ffprobe -v error -show_entries format=duration -of csv=p=0 "${filePath}"`,
{ encoding: 'utf-8' }
);
return parseFloat(result.trim());
}
// Main synthesis function with fallback logic
async function synthesize(script, preset = 'short-pace', voiceKey = 'primary') {
const timestamp = Date.now();
const outputPath = path.resolve(
__dirname, '../../output', `tts-${voiceKey}-${timestamp}.wav`
);
const ssml = buildSSML(script, voiceKey, preset);
try {
await synthesizeAzure(ssml, outputPath);
} catch (err) {
// Fall back to Piper on 429 or network failure
if (err.message === 'AZURE_429' || err.message.startsWith('NETWORK_FAIL')) {
console.warn(`Azure unavailable (${err.message}), falling back to Piper`);
synthesizePiper(script, outputPath);
} else {
throw err;
}
}
const duration = getWavDuration(outputPath);
return { filePath: outputPath, duration };
}
module.exports = { synthesize, buildSSML };
What does this code do?
The buildSSML function wraps raw script text in SSML tags with the chosen voice (Andrew or Ava) and pacing preset. Azure bills per character, but the <speak> and <voice> tags are excluded from billing.
The synthesize function tries Azure first. If Azure returns a 429 (rate limit) or the network fails, it automatically falls back to Piper, a local TTS engine. This ensures the pipeline never stalls on a single API failure.
How much does Azure TTS cost?
Azure Neural TTS has an always-free tier of 0.5 million characters per month. A 60-second script is roughly 2,000-3,000 billable characters including SSML markup. That gives you approximately 160-250 free pipeline runs per month.
- Verify both files exist by running:
ls src/tts/ && ls config/voices.json
You should see synthesize.js in the src/tts/ directory and the voices.json config file confirmed.
- Test that the SSML builder works by running:
node -e "const {buildSSML} = require('./src/tts/synthesize'); console.log(buildSSML('Hello world', 'primary', 'short-pace'));"
You should see a complete SSML document with en-US-AndrewNeural as the voice name and +10% as the rate.
Getting an empty or malformed SSML output?
Check that config/voices.json is valid JSON. A missing comma or bracket will cause the JSON.parse call to fail silently or throw.
Help me debug why my TTS synthesize.js is producing malformed SSML when I test the buildSSML function.
Scaffold the Remotion compositions
Remotion lets you compose video frames programmatically using React components. Your pipeline needs two composition formats: a vertical 9:16 for short-mode and a horizontal 16:9 for long-mode.
- Instruct Claude Code to scaffold the Remotion project inside src/remotion/ by running:
cd src/remotion && npx create-video@latest .
What does create-video do?
This scaffolds a Remotion project with the Hello World template. You'll replace the default composition with your custom ShortTerm and LongTerm compositions next. Remotion v4 bundles its own ffmpeg for rendering, but your pipeline still needs the system ffmpeg for the mux and ducking steps that happen outside Remotion.
- Now instruct Claude Code to replace the default composition with the two pipeline compositions. The root file should register both:
// src/remotion/src/Root.tsx
// Registers both pipeline compositions with Remotion
import { Composition } from 'remotion';
import { ShortTermVideo } from './compositions/ShortTerm';
import { LongTermVideo } from './compositions/LongTerm';
export const RemotionRoot = () => {
return (
<>
{/* Vertical format for short-mode explainers (9:16) */}
<Composition
id="ShortTerm"
component={ShortTermVideo}
durationInFrames={1800}
fps={30}
width={1080}
height={1920}
defaultProps={{
scenes: [],
audioUrl: '',
diagramUrls: [],
brandKit: {}
}}
/>
{/* Horizontal format for long-mode explainers (16:9) */}
<Composition
id="LongTerm"
component={LongTermVideo}
durationInFrames={18000}
fps={30}
width={1920}
height={1080}
defaultProps={{
scenes: [],
audioUrl: '',
diagramUrls: [],
brandKit: {}
}}
/>
</>
);
};
What does this code do?
Each Composition registers a named video format with Remotion. The ShortTerm composition is 1080x1920 at 30fps (60 seconds = 1800 frames). The LongTerm composition is 1920x1080 at 30fps (10 minutes = 18000 frames).
Both accept the same props shape: an array of scenes, the audio URL, diagram URLs, and a brand kit object. The orchestrator passes these props at render time based on what agents A3 through A6 produced.
- Finally, instruct Claude Code to create the reusable Scene component at src/remotion/components/Scene.tsx:
// src/remotion/components/Scene.tsx
// Renders a single scene: Mermaid diagram with focal highlight and text overlay
import { AbsoluteFill, Img, useCurrentFrame, interpolate, spring, useVideoConfig } from 'remotion';
interface SceneProps {
diagramUrl: string;
text: string;
highlightRegion?: { x: number; y: number; width: number; height: number };
transitionDuration?: number;
}
export const Scene: React.FC<SceneProps> = ({
diagramUrl,
text,
highlightRegion,
transitionDuration = 15
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Fade-in transition for the scene
const opacity = interpolate(frame, [0, transitionDuration], [0, 1], {
extrapolateRight: 'clamp'
});
// Spring animation for focal highlight
const highlightScale = spring({
frame: frame - transitionDuration,
fps,
config: { damping: 12, stiffness: 100 }
});
// Text slide-up animation
const textY = interpolate(
frame,
[transitionDuration, transitionDuration + 20],
[40, 0],
{ extrapolateRight: 'clamp' }
);
return (
<AbsoluteFill style={{ opacity }}>
{/* Mermaid diagram PNG */}
<Img
src={diagramUrl}
style={{
width: '90%',
height: 'auto',
position: 'absolute',
top: '15%',
left: '5%'
}}
/>
{/* Focal highlight overlay */}
{highlightRegion && (
<div
style={{
position: 'absolute',
left: `${highlightRegion.x}%`,
top: `${highlightRegion.y}%`,
width: `${highlightRegion.width}%`,
height: `${highlightRegion.height}%`,
border: '3px solid #4a9eff',
borderRadius: 8,
transform: `scale(${highlightScale})`,
boxShadow: '0 0 20px rgba(74, 158, 255, 0.4)'
}}
/>
)}
{/* On-screen text overlay */}
<div
style={{
position: 'absolute',
bottom: '10%',
left: '5%',
right: '5%',
transform: `translateY(${textY}px)`,
color: '#ffffff',
fontSize: 36,
fontFamily: 'Inter, sans-serif',
textShadow: '0 2px 8px rgba(0,0,0,0.8)'
}}
>
{text}
</div>
</AbsoluteFill>
);
};
What does the Scene component do?
Each scene renders one Mermaid diagram PNG with three animation layers. The diagram fades in over the transition duration. A spring-animated highlight box draws attention to the focal region (the node being discussed). The text slides up from below with a slight delay so the diagram lands first.
A5 (Motion Engineer) generates the specific props for each scene based on the storyboard from A3. This component just knows how to render whatever it receives.
- Verify the Remotion project is valid by running:
cd src/remotion && npx remotion studio src/index.ts
Remotion Studio should open in your browser. You should see both ShortTerm and LongTerm listed as available compositions in the left sidebar.
Remotion Studio not loading?
Make sure you're running the command from inside the src/remotion/ directory where package.json lives. If you see a TypeScript error, confirm that the compositions/ShortTerm.tsx and compositions/LongTerm.tsx files exist and export the expected components.
Help me troubleshoot why Remotion Studio won't start for my Parallax pipeline.
✔️ Awesome, I've got everything!
Great. Double check you've saved all files and that Remotion Studio confirms both compositions.
ⓧ I'd like to double check the full code
Here are all the files created in this step. Compare yours against these:
// src/mermaid/render.js
// Renders Mermaid source strings to deterministic PNG files
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
// Enforce maximum 5 nodes per diagram for readability
function validateNodeCount(mermaidSource) {
const nodePattern = /^\s*\w+[\[\(\{\>]/gm;
const matches = mermaidSource.match(nodePattern) || [];
if (matches.length > 5) {
throw new Error(
`Diagram has ${matches.length} nodes (max 5). Split into multiple diagrams.`
);
}
}
// Render a Mermaid source string to PNG
function renderDiagram(mermaidSource, configPath, mode = 'short') {
validateNodeCount(mermaidSource);
// Write source to a temp .mmd file
const hash = crypto.randomBytes(8).toString('hex');
const tempInput = path.join(__dirname, `temp-${hash}.mmd`);
const tempOutput = path.join(__dirname, `temp-${hash}.png`);
fs.writeFileSync(tempInput, mermaidSource, 'utf-8');
// Set dimensions based on mode
const width = mode === 'short' ? 1080 : 1920;
const height = mode === 'short' ? 1920 : 1080;
// Invoke mmdc with deterministic PNG output
const cmd = `mmdc -i "${tempInput}" -o "${tempOutput}" -t dark -b transparent -s 2 -w ${width} -H ${height} -c "${configPath}"`;
try {
execSync(cmd, { stdio: 'pipe' });
} finally {
// Clean up temp input file
if (fs.existsSync(tempInput)) fs.unlinkSync(tempInput);
}
return tempOutput;
}
module.exports = { renderDiagram, validateNodeCount };
{
"look": "handDrawn",
"theme": "dark",
"themeVariables": {
"primaryColor": "#1a1a2e",
"primaryTextColor": "#e0e0e0",
"primaryBorderColor": "#4a4a6a",
"lineColor": "#8888aa",
"secondaryColor": "#16213e",
"tertiaryColor": "#0f3460",
"fontFamily": "Inter, sans-serif"
},
"flowchart": {
"curve": "basis",
"padding": 20
}
}
{
"voices": {
"primary": {
"name": "en-US-AndrewNeural",
"role": "narrator"
},
"accent": {
"name": "en-US-AvaNeural",
"role": "highlight"
}
},
"presets": {
"short-pace": {
"rate": "+10%",
"pitch": "+0Hz",
"breakAfterSentence": "300ms"
},
"long-pace": {
"rate": "+0%",
"pitch": "+0Hz",
"breakAfterSentence": "500ms"
}
}
}
// src/tts/synthesize.js
// Azure Neural TTS client with SSML wrapping and Piper fallback
const https = require('https');
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// Load voice configuration
const voiceConfig = JSON.parse(
fs.readFileSync(path.resolve(__dirname, '../../config/voices.json'), 'utf-8')
);
// Wrap script text in SSML with the selected voice and pacing
function buildSSML(script, voiceKey = 'primary', preset = 'short-pace') {
const voice = voiceConfig.voices[voiceKey];
const pacing = voiceConfig.presets[preset];
return `<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
<voice name="${voice.name}">
<prosody rate="${pacing.rate}" pitch="${pacing.pitch}">
${script}
</prosody>
</voice>
</speak>`;
}
// Call Azure Speech REST API to synthesize audio
async function synthesizeAzure(ssml, outputPath) {
const region = process.env.AZURE_SPEECH_REGION;
const key = process.env.AZURE_SPEECH_KEY;
if (!region || !key) {
throw new Error('Missing AZURE_SPEECH_REGION or AZURE_SPEECH_KEY env vars');
}
const options = {
hostname: `${region}.tts.speech.microsoft.com`,
path: '/cognitiveservices/v1',
method: 'POST',
headers: {
'Ocp-Apim-Subscription-Key': key,
'Content-Type': 'application/ssml+xml',
'X-Microsoft-OutputFormat': 'riff-24khz-16bit-mono-pcm',
'User-Agent': 'ParallaxPipeline'
}
};
return new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
if (res.statusCode === 429) {
reject(new Error('AZURE_429'));
return;
}
if (res.statusCode !== 200) {
reject(new Error(`Azure TTS returned ${res.statusCode}`));
return;
}
const chunks = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => {
const buffer = Buffer.concat(chunks);
fs.writeFileSync(outputPath, buffer);
resolve(outputPath);
});
});
req.on('error', (err) => reject(new Error(`NETWORK_FAIL: ${err.message}`)));
req.write(ssml);
req.end();
});
}
// Piper fallback for when Azure is unavailable
function synthesizePiper(script, outputPath) {
const cmd = `echo "${script.replace(/"/g, '\\"')}" | piper --model en_US-lessac-medium --output_file "${outputPath}"`;
execSync(cmd, { stdio: 'pipe' });
return outputPath;
}
// Get WAV duration in seconds using ffprobe
function getWavDuration(filePath) {
const result = execSync(
`ffprobe -v error -show_entries format=duration -of csv=p=0 "${filePath}"`,
{ encoding: 'utf-8' }
);
return parseFloat(result.trim());
}
// Main synthesis function with fallback logic
async function synthesize(script, preset = 'short-pace', voiceKey = 'primary') {
const timestamp = Date.now();
const outputPath = path.resolve(
__dirname, '../../output', `tts-${voiceKey}-${timestamp}.wav`
);
const ssml = buildSSML(script, voiceKey, preset);
try {
await synthesizeAzure(ssml, outputPath);
} catch (err) {
// Fall back to Piper on 429 or network failure
if (err.message === 'AZURE_429' || err.message.startsWith('NETWORK_FAIL')) {
console.warn(`Azure unavailable (${err.message}), falling back to Piper`);
synthesizePiper(script, outputPath);
} else {
throw err;
}
}
const duration = getWavDuration(outputPath);
return { filePath: outputPath, duration };
}
module.exports = { synthesize, buildSSML };
// src/remotion/src/Root.tsx
// Registers both pipeline compositions with Remotion
import { Composition } from 'remotion';
import { ShortTermVideo } from './compositions/ShortTerm';
import { LongTermVideo } from './compositions/LongTerm';
export const RemotionRoot = () => {
return (
<>
{/* Vertical format for short-mode explainers (9:16) */}
<Composition
id="ShortTerm"
component={ShortTermVideo}
durationInFrames={1800}
fps={30}
width={1080}
height={1920}
defaultProps={{
scenes: [],
audioUrl: '',
diagramUrls: [],
brandKit: {}
}}
/>
{/* Horizontal format for long-mode explainers (16:9) */}
<Composition
id="LongTerm"
component={LongTermVideo}
durationInFrames={18000}
fps={30}
width={1920}
height={1080}
defaultProps={{
scenes: [],
audioUrl: '',
diagramUrls: [],
brandKit: {}
}}
/>
</>
);
};
// src/remotion/components/Scene.tsx
// Renders a single scene: Mermaid diagram with focal highlight and text overlay
import { AbsoluteFill, Img, useCurrentFrame, interpolate, spring, useVideoConfig } from 'remotion';
interface SceneProps {
diagramUrl: string;
text: string;
highlightRegion?: { x: number; y: number; width: number; height: number };
transitionDuration?: number;
}
export const Scene: React.FC<SceneProps> = ({
diagramUrl,
text,
highlightRegion,
transitionDuration = 15
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Fade-in transition for the scene
const opacity = interpolate(frame, [0, transitionDuration], [0, 1], {
extrapolateRight: 'clamp'
});
// Spring animation for focal highlight
const highlightScale = spring({
frame: frame - transitionDuration,
fps,
config: { damping: 12, stiffness: 100 }
});
// Text slide-up animation
const textY = interpolate(
frame,
[transitionDuration, transitionDuration + 20],
[40, 0],
{ extrapolateRight: 'clamp' }
);
return (
<AbsoluteFill style={{ opacity }}>
{/* Mermaid diagram PNG */}
<Img
src={diagramUrl}
style={{
width: '90%',
height: 'auto',
position: 'absolute',
top: '15%',
left: '5%'
}}
/>
{/* Focal highlight overlay */}
{highlightRegion && (
<div
style={{
position: 'absolute',
left: `${highlightRegion.x}%`,
top: `${highlightRegion.y}%`,
width: `${highlightRegion.width}%`,
height: `${highlightRegion.height}%`,
border: '3px solid #4a9eff',
borderRadius: 8,
transform: `scale(${highlightScale})`,
boxShadow: '0 0 20px rgba(74, 158, 255, 0.4)'
}}
/>
)}
{/* On-screen text overlay */}
<div
style={{
position: 'absolute',
bottom: '10%',
left: '5%',
right: '5%',
transform: `translateY(${textY}px)`,
color: '#ffffff',
fontSize: 36,
fontFamily: 'Inter, sans-serif',
textShadow: '0 2px 8px rgba(0,0,0,0.8)'
}}
>
{text}
</div>
</AbsoluteFill>
);
};
Your visual and audio pipeline is built. The Mermaid renderer turns diagram source into deterministic PNGs. The TTS client turns scripts into narrated WAV files with automatic fallback. And Remotion is ready to compose animated video frames from all of it. Next up, you'll build the carousel generator and the ffmpeg composition scripts that stitch everything into final deliverables.
Build the Carousel and Composition Pipeline
Your visual and audio engines are ready. The Mermaid renderer can produce diagrams, the Remotion compositions can animate scenes, and the Azure TTS client can synthesize voice.
Now you need the delivery layer. This step builds three things: an HTML carousel renderer that produces shareable slide decks, ffmpeg composition scripts that mux everything into final video, and a bootstrap script that prepares fresh machines for pipeline execution.
In this step, get ready to:
- Create HTML carousel templates and a Playwright-based renderer.
- Build ffmpeg mux scripts for short-mode and long-mode video composition.
- Author a PowerShell bootstrap script that validates tools, downloads assets, and locks versions.
Create the carousel HTML template and render script
The carousel is the pipeline's social-ready output. Each storyboard scene from A3 becomes a standalone 1080x1080 slide image, ideal for LinkedIn and Instagram carousels.
- Instruct Claude Code to create the carousel slide template by giving it this prompt:
Create src/carousel/templates/slide.html - a single-slide HTML template at exactly 1080x1080 pixels.
The template must render these elements:
- Slide number (top-left corner, muted styling)
- Heading (prominent, centered at top)
- Mermaid diagram PNG (centered, max 700px wide, preserving aspect ratio)
- Supporting text (below diagram, max 2 lines, readable font size)
- Brand footer (bottom bar with project name and slide count indicator)
Use CSS variables for all colors and fonts so the brand kit can override them.
Use {{placeholder}} syntax for dynamic values: {{slideNumber}}, {{heading}},
{{diagramPath}}, {{supportingText}}, {{brandName}}, {{slideCount}}.
Keep the HTML self-contained with inline styles - no external dependencies.
Why 1080x1080?
LinkedIn and Instagram carousels both accept square images at 1080x1080 pixels. This single dimension works across both platforms without cropping or resizing.
- Verify that Claude Code created src/carousel/templates/slide.html with all six placeholder variables and a self-contained layout.
- Now instruct Claude Code to create the Playwright render script:
Create src/carousel/render.js - a Node.js script that uses Playwright to render
the carousel slides as PNG images.
Requirements:
- Import chromium from 'playwright'
- Accept two arguments: a storyboard JSON (array of 6-8 scene objects with
heading, diagramPath, supportingText) and an output directory path
- Launch a headless Chromium browser
- Set viewport to exactly 1080x1080 pixels using page.setViewportSize()
- For each scene in the storyboard array:
- Read slide.html template from src/carousel/templates/
- Replace {{placeholder}} values with scene data
- Set page content using page.setContent()
- Screenshot to output/{repo}/{sha}/carousel/01.png through 08.png
(zero-padded numbering)
- Close browser when done
- Export the render function for use by the orchestrator
- Add inline comments explaining each logical section
What does this script do?
The render script is the hot-path optimization mentioned in the architecture. Instead of making A4 (Visual Director) do a separate pass for carousel images, the script directly converts storyboard scenes into slides.
Playwright launches a headless browser, sets the viewport to 1080x1080, injects the HTML template with scene data, and captures a screenshot. This runs locally with no network calls.
- Confirm both files exist by running:
ls src\carousel\templates\slide.html, src\carousel\render.js
- You should see both files listed in the output.
Files missing?
If either file is missing, check that Claude Code created them in the correct subdirectory. The template goes in src/carousel/templates/ and the render script goes in src/carousel/. Re-run the prompt and specify the full path explicitly.
Help me troubleshoot my carousel template and render script creation in Claude Code.
Build the ffmpeg composition scripts
The ffmpeg scripts are the final assembly step in the pipeline. They take all the generated assets (Remotion video, TTS audio, music bed, brand sting) and mux them into polished output files.
- Instruct Claude Code to create the short-mode mux script:
Create src/ffmpeg/mux-short.js - a Node.js wrapper around ffmpeg CLI commands
for short-mode video composition.
Inputs (passed as a config object):
- remotionOutput: path to the Remotion-rendered 9:16 video (no audio)
- ttsAudio: path to the TTS WAV file
- musicBed: path to a background music track from assets/music-bed/
- brandSting: path to the 3-5 second brand sting WAV
- srtFile: path to the SRT caption file
- outputPath: where to write the final MP4
Behavior:
1. Mix audio with ducking: use ffmpeg sidechaincompress filter to lower
music bed volume when TTS audio is active
(threshold=0.02, ratio=8, attack=200ms, release=800ms)
2. Prepend brand sting audio before the ducked mix
3. Burn open captions from the SRT file using the subtitles filter
(force_style with white text, semi-transparent background box)
4. Mux the Remotion video with the mixed audio track
5. Output codec: libx264, audio: aac, pixel format: yuv420p
6. Final container: MP4 at 9:16 (1080x1920)
Use child_process.execSync to run ffmpeg commands.
Add inline comments explaining each ffmpeg filter chain.
Export the mux function for orchestrator use.
What is audio ducking?
Audio ducking automatically lowers the background music volume whenever the voiceover is speaking. The sidechaincompress filter in ffmpeg detects when the TTS audio signal exceeds the threshold, then compresses (quiets) the music bed in response.
- Now instruct Claude Code to create the long-mode mux script:
Create src/ffmpeg/mux-long.js - a Node.js wrapper around ffmpeg CLI commands
for long-mode video composition.
Same inputs as mux-short.js, plus:
- chapters: array of {title, startTimeMs, endTimeMs} objects
- Optional shortsExtracts: array of {startTimeMs, endTimeMs} for vertical cutdowns
Differences from mux-short:
1. Output is 16:9 (1920x1080) instead of 9:16
2. Do NOT burn captions - generate SRT sidecar file instead at outputPath.replace('.mp4', '.srt')
3. Embed chapter markers as MP4 atoms using ffmpeg metadata:
- Write a temporary FFMETADATA1 file with [CHAPTER] sections
- Use -i metadata.txt -map_metadata 1 to embed chapters
4. After main render, optionally extract vertical Shorts cutdowns:
- For each shorts extract, crop 1080x1920 from center of the 1920x1080 frame
- Save to output directory with -short-01.mp4, -short-02.mp4 naming
5. Write chapters.json to the output directory
Use child_process.execSync for ffmpeg commands.
Add inline comments explaining the chapter embedding process.
Export the mux function for orchestrator use.
Why two separate scripts?
Short-mode and long-mode have fundamentally different output requirements. Short-mode burns captions directly into the video (social platforms auto-play muted). Long-mode keeps captions as a sidecar SRT file so YouTube can render them natively with user controls.
Long-mode also embeds chapter markers as MP4 atoms. These let YouTube automatically generate chapter navigation in the player timeline.
- Verify both ffmpeg scripts exist by running:
ls src\ffmpeg\mux-short.js, src\ffmpeg\mux-long.js
- You should see both mux-short.js and mux-long.js listed.
- Open mux-short.js and confirm it contains a sidechaincompress filter reference and a subtitles filter for burn-in captions.
Script missing the ducking filter?
If Claude Code used a simpler volume reduction instead of sidechaincompress, ask it to refactor. A static volume drop sounds unnatural. The sidechain approach responds dynamically to the voiceover signal.
Help me fix my ffmpeg mux script to use proper sidechain compression for audio ducking.
Author the bootstrap automation script
The bootstrap script is what makes the pipeline reproducible on any machine. It validates that every tool is the correct version, downloads shared assets, and locks the entire environment into a tools.lock file.
- Instruct Claude Code to create the bootstrap PowerShell script:
Create bin/bootstrap.ps1 - a PowerShell script that prepares a fresh machine
for pipeline execution.
The script must perform these tasks in order:
1. VALIDATE TOOL VERSIONS
- Check Node.js version (require v18.19+ or v20+)
- Check npm version (require v10+)
- Check git version
- Check ffmpeg version and PATH availability
- Check mmdc version (require @mermaid-js/mermaid-cli v11.14.0)
- Check Puppeteer is installed as peer dependency
- Check Playwright browsers are installed
- If any tool is missing or wrong version, print clear error and exit
2. DOWNLOAD PIXABAY MUSIC TRACKS
- Download 5 royalty-free ambient/electronic tracks from Pixabay Music API
- Save to assets/music-bed/ as track-01.mp3 through track-05.mp3
- If Pixabay API is unreachable, print warning and continue with empty dir
3. GENERATE BRAND STING
- Attempt to generate a 3-5 second audio sting using MusicGen (Meta AudioCraft)
- If MusicGen is not installed or takes longer than 60 seconds,
fall back to copying a silence placeholder WAV
- Save to assets/music-bed/brand-sting.wav
4. CREATE DEFAULT BRAND KIT
- Create assets/brand-kit-defaults/ directory
- Write defaults.json with neutral color palette:
primary=#1a1a2e, secondary=#16213e, accent=#0f3460, text=#e0e0e0
- Include default font stack: Inter, system-ui, sans-serif
5. WRITE TOOLS.LOCK
- Record every tool name and exact version to tools.lock at project root
- Format: one tool per line as "toolname=version"
- Include timestamp of when bootstrap was run
Add comments explaining each section.
Use Write-Host for progress messages with color (Green for success, Yellow for warnings, Red for errors).
What does tools.lock provide?
The tools.lock file captures the exact version of every tool at the time the pipeline ran successfully. If a future run produces different output, you can compare lock files to find which tool version changed. This satisfies the reproducibility requirement: same inputs plus same tool versions equals same output.
Why the MusicGen fallback?
MusicGen (Meta AudioCraft) generates short audio stings from text prompts. On CPU-only Windows machines it can be very slow. The 60-second timeout ensures the bootstrap never hangs. The silence placeholder lets the pipeline continue without a brand sting.
- Confirm the bootstrap script was created by running:
ls bin\bootstrap.ps1
- You should see bootstrap.ps1 listed in the output.
- Run the bootstrap script to validate your current environment:
powershell -ExecutionPolicy Bypass -File bin\bootstrap.ps1
- Watch the output for green success messages on each tool check.
- Confirm that a tools.lock file was written at the project root by running:
cat tools.lock
- You should see a list of tool names and version numbers, one per line, with a timestamp at the bottom.
Bootstrap script fails on Pixabay download?
The Pixabay API requires a free API key. If the script errors on the download step, check that your Pixabay API key is set in your environment variables. Alternatively, the script should print a warning and continue. If it exits instead, ask Claude Code to refactor the download section to use a try/catch with a graceful fallback.
Help me fix my bootstrap script's Pixabay download section.
✔️ Awesome, I've got everything!
Double check you have saved all files. Your delivery layer is complete.
ⓧ I'd like to double check the full file list
Run this command to confirm all six new files from this step exist:
ls src\carousel\templates\slide.html, src\carousel\render.js, src\ffmpeg\mux-short.js, src\ffmpeg\mux-long.js, bin\bootstrap.ps1, tools.lock
All six files should appear in the listing. If any are missing, re-run the relevant Claude Code prompt from the substep where that file was created.
Your pipeline now has every component it needs: agent definitions, orchestrator, visual rendering, audio synthesis, carousel generation, video composition, and a reproducible bootstrap. Next up, you will run the entire pipeline end-to-end against a real GitHub repo and watch all nine agents execute in sequence.
Run the Pipeline End-to-End
You have a complete pipeline architecture sitting on disk. Nine agent definitions, typed contracts, visual renderers, audio synthesizers, and composition scripts are all wired together through your orchestrator.
Now it is time to prove it works. In this step, you will run the full pipeline against a real open-source repo and watch all six phases execute in sequence. By the end, you will have a council-approved 45-60 second vertical video and a carousel deck produced entirely by your nine-agent foundry.
In this step, get ready to:
- Invoke the orchestrator against charmbracelet/bubbletea in short-mode and verify the distillation phase completes.
- Monitor the creative, visual, and composition phases as agents dispatch in parallel waves.
- Validate the council review gate and confirm all final output artifacts exist.
Invoke the orchestrator in short-mode
The orchestrator reads your schemas/phase-graph.json to determine which agents run in which order. Phase 1 dispatches three agents in parallel (A1, A2, and A4) using the Task tool. This is the maximum parallel wave size proven to balance wall-clock time and accuracy.
- Open Claude Code in your terminal.
- Make sure your working directory is C:\pipelines\parallax\ by running:
cd C:\pipelines\parallax
- Instruct Claude Code to run the orchestrator in short-mode against the target repo by entering this prompt:
Run the orchestrator defined in orchestrator.md against the repo https://github.com/charmbracelet/bubbletea in short mode. Follow the phase-graph.json dispatch sequence exactly. Write state.json after each phase transition. Log wall-clock times to validation/runtime-logs/.
What happens when you hit Enter?
Claude Code reads orchestrator.md and begins the INTAKE phase. It clones the bubbletea repo at the latest commit SHA, extracts the README and docs, then spawns Phase 1 agents A1, A2, and A4 in parallel using the Task tool.
Each agent runs in its own isolated context with restricted tools. A1 (Repo Anatomist) identifies load-bearing files and core nodes. A2 (System Distiller) extracts the system boundary and entry points. A4 (Visual Director) derives brand signals or falls back to assets/brand-kit-defaults/.
- Wait for Phase 1 to complete. Claude Code will report when all three agents return their JSON outputs.
- Verify the state machine advanced by checking state.json. Run this in a separate terminal:
# Find the output directory (SHA will vary per run)
$repoDir = Get-ChildItem -Path "output\bubbletea" -Directory | Select-Object -First 1
Get-Content "$($repoDir.FullName)\state.json" | ConvertFrom-Json | Select-Object current_phase, completed_agents
You should see current_phase showing DISTILLED and completed_agents listing A1, A2, and A4.
What should the distillation output contain?
For bubbletea, you should see 3-5 core nodes identified (the Program interface, Model, Update, View, and the tea package itself). The load-bearing files list should include files like tea.go, model.go, and the main examples directory.
Since bubbletea has a recognizable brand (Charm CLI toolkit), A4 should extract brand signals. If it cannot find enough signals, it applies the default kit from assets/brand-kit-defaults/.
State.json still showing INTAKE?
If the state has not advanced, check Claude Code's output for schema validation errors. An agent may have returned JSON that does not match its output contract in schemas/. The orchestrator's retry logic should re-invoke the failing agent once with correction context. If the retry also fails, check validation/runtime-logs/ for the specific error.
help me debug why my orchestrator is stuck at INTAKE phase
Watch the creative and visual phases
With distillation complete, the pipeline now enters the creative phases. These phases build on each other. A3 and A7 use the distillation output to write the script and hook. Then A4 renders diagrams from the storyboard. Finally A5 and A6 produce the video frames and voiceover audio.
- Watch Claude Code's output as it dispatches Phase 2. A3 (Story Architect) and A7 (Hook Strategist) run in parallel.
- When Phase 2 completes, verify the state has advanced to STORYBOARDED by re-running the state check:
Get-Content "$($repoDir.FullName)\state.json" | ConvertFrom-Json | Select-Object current_phase, completed_agents
You should see STORYBOARDED with A1, A2, A3, A4, and A7 in the completed list.
- Phase 3 renders Mermaid diagrams from the storyboard scenes. Watch for Claude Code invoking src/mermaid/render.js with the 5-node-cap diagrams. The state should advance to VISUAL_READY.
- Phase 4 dispatches A5 (Motion Engineer) and A6 (Voice Director) in parallel. A5 triggers the Remotion render for the ShortTerm composition. A6 calls Azure Neural TTS to synthesize the voiceover.
- Phase 5 runs the ffmpeg composition via src/ffmpeg/mux-short.js. This mixes the Remotion output with TTS audio, music bed, and brand sting, then burns in open captions. The state should advance to COMPOSED.
- Monitor the per-phase wall-clock times in validation/runtime-logs/ by running:
# List runtime log files generated so far
Get-ChildItem -Path "validation\runtime-logs" -Recurse | Format-Table Name, LastWriteTime, Length
You should see log files for each completed phase with timestamps and duration records.
Why does Phase 4 take the longest?
The Remotion render in Phase 4 composites every frame of the 45-60 second video at 30fps. That is 1,350 to 1,800 frames. Even with Remotion's optimized rendering pipeline, this is the most compute-intensive phase. The TTS synthesis runs in parallel so it does not add to the total wait time.
Seeing a Remotion render failure?
If Remotion fails to render, check that your Node.js version is v18.19 or higher. Remotion also requires sufficient memory for frame composition. Close other heavy applications and retry. The orchestrator should automatically retry once after 120 seconds.
If TTS synthesis fails, the orchestrator falls back to Piper (offline TTS). Check validation/runtime-logs/ for a line mentioning Azure 429 or network timeout.
help me debug my Remotion or TTS failure during Phase 4
Validate the council review and final outputs
The council gate is your pipeline's quality checkpoint. Two agents independently verify the final artifacts before the pipeline declares success. A8 runs an adversarial fact-check through Codex CLI. A9 runs Whisper against the TTS audio to verify caption accuracy.
- Watch Claude Code dispatch Phase 6. A8 (Quality Auditor) and A9 (Compliance Gate) run in parallel.
- A8 invokes Codex CLI via its Bash tool to cross-check the technical claims in the script against the actual repo source code. This is the adversarial check that catches hallucinated explanations.
- A9 runs Whisper speech-to-text on the final TTS audio and diffs it against the caption SRT. It also verifies licensing, brand-kit correctness, and rights confirmation.
- Once both agents return, verify the council receipt by running:
# Find and display the council receipt
$receipt = Get-ChildItem -Path "validation\council-receipts" -Filter "bubbletea-*-short.json" | Select-Object -First 1
Get-Content $receipt.FullName | ConvertFrom-Json | Format-List
What should the council receipt contain?
The receipt at validation/council-receipts/bubbletea-{sha}-short.json records every agent that participated in the run. Each entry includes the agent prompt file hash (SHA-256), model used, timestamp, and pass/veto status with reasoning.
All nine agents should show pass status. If either A8 or A9 vetoes, the orchestrator halts and logs the veto reason. This is by design. The pipeline refuses to emit artifacts that fail quality gates.
- Confirm the final video output exists by running:
# Verify the short-mode MP4 and carousel PNGs exist
$sha = $repoDir.Name
Write-Host "Checking output/bubbletea/$sha/"
Get-ChildItem -Path "output\bubbletea\$sha" -Recurse | Format-Table Name, Length, LastWriteTime
You should see short-term.mp4 (your 45-60 second vertical video) and a carousel/ folder containing 01.png through 08.png.
- Play the video to confirm it renders correctly. Open it from File Explorer or run:
# Open the video in your default media player
Start-Process "output\bubbletea\$sha\short-term.mp4"
The video should be 9:16 vertical format (1080x1920), 45-60 seconds long, with burned-in captions, voiceover narration, and background music. The Mermaid diagrams should animate in with transitions matching the script beats.
- Verify the final state.json shows COUNCIL_PASS as the current phase:
# Final state check - should show COUNCIL_PASS with all 9 agents
Get-Content "output\bubbletea\$sha\state.json" | ConvertFrom-Json | Select-Object current_phase, completed_agents, timestamps
You should see all nine agents (A1 through A9) in the completed list and timestamps for each phase transition.
Council veto from A8 or A9?
If A8 vetoes, it found a factual claim in the script that Codex CLI could not verify against the source repo. Check the veto reasoning in the council receipt. You may need to re-run with a correction prompt asking the orchestrator to regenerate the script section A8 flagged.
If A9 vetoes, the Whisper transcription of the TTS audio diverged too far from the captions. This usually means a pronunciation issue caused garbled words. Check which segment failed and verify the SSML pronunciation lexicon in config/voices.json covers the problematic terms.
help me resolve a council veto in my Parallax pipeline
Your nine-agent content foundry just produced its first reviewed output. A 45-60 second explainer video and 6-8 slide carousel deck, generated from a single GitHub URL, validated by an adversarial council gate. Next up, the Secret Mission challenges you to run the full long-mode pipeline and produce a chaptered YouTube-ready video.
Secret mission
Run Long Mode and Produce a Chaptered YouTube Video
Your short-mode pipeline is proven. Now push Parallax to its full potential by producing a 10-15 minute chaptered YouTube explainer from the same repo. Long-mode renders 27,000+ frames, alternates dual voices across 8-12 chapters, and embeds chapter markers directly into the MP4 atoms.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs primarily on local tools with Azure Neural TTS on the free tier (0.5 million characters/month), so there are no ongoing costs unless you exceed that threshold.
Resources you used:
- Project directory at C:\pipelines\parallax\ (all agent definitions, schemas, source code, and output artifacts).
- Azure Speech Services resource (used for Neural TTS voice synthesis).
- Global npm packages: @mermaid-js/mermaid-cli and puppeteer.
- Remotion project scaffolded at src/remotion/.
- Warm-start cache at .parallax-cache/ (Puppeteer browser binary, music tracks, previous distillation outputs).
- Output artifacts at output/ (rendered videos, carousel PNGs, council receipts).
- Downloaded Pixabay music tracks and brand sting audio in assets/.
- OpenAI Codex CLI (pre-existing, used for adversarial fact-checking).
Keep everything running
No action needed. Choose this if you're still actively building or plan to run the pipeline against more repos soon.
- Your Azure Speech Services resource stays on the free tier and costs nothing while idle.
- The .parallax-cache/ directory enables warm-start runs that complete in 70% of first-run wall-clock time.
- Global npm packages remain available for other projects that use Mermaid or Puppeteer.
Pause - I'll come back to this later
Free up disk space while keeping your pipeline definition intact for future runs.
- Delete the output/ directory to reclaim space from rendered videos and carousel PNGs.
- Delete the .parallax-cache/ directory to free the Puppeteer browser binary and cached music tracks.
- Keep the C:\pipelines\parallax\ project directory with all agent definitions, schemas, and source code.
- Keep your Azure Speech Services resource. It costs nothing while idle on the free tier.
- Keep global npm packages installed. They take minimal disk space.
Delete - I don't want to use this again
Remove all project resources and start fresh. This is irreversible for local files.
- Delete the entire C:\pipelines\parallax\ directory including all agent definitions, schemas, source code, and outputs.
- Uninstall global npm packages by running npm uninstall -g @mermaid-js/mermaid-cli puppeteer.
- Delete your Azure Speech Services resource from the Azure Portal if you no longer need it for other projects.
- Remove the .parallax-cache/ directory if it exists outside the project folder.
- Optionally uninstall ffmpeg if you installed it specifically for this project and have no other use for it.
That's a Wrap!
That's a Wrap!
You built Parallax. A nine-agent content foundry that converts any public GitHub repo into reviewed explainer artifacts in a single command.
You've learned how to:
- Architect a multi-agent orchestrator that dispatches nine specialist agents in parallel waves using typed JSON contracts, a six-phase state machine, and explicit gate conditions.
- Build a complete programmatic video pipeline combining Mermaid diagram rendering, Remotion animation compositions, Azure Neural TTS voiceover, and ffmpeg audio ducking into polished explainer content.
- Implement a council review gate with adversarial fact-checking via Codex CLI, Whisper caption verification, and deterministic receipt logging for full pipeline traceability.
- Run a long-mode render producing a chaptered YouTube-ready MP4 with embedded chapter markers, SRT captions, and dual-voice narration.
Ready to quiz yourself?