Creative Ad Agent - System Architecture
Version: 7.1 | Updated: January 2026 | Status: Production
AI-powered ad generator using hook-first methodology : analyzes brand websites, extracts data, creates 6 diverse ad concepts with AI images.
┌─────────────────────────────────────────────────────────────────────────┐
│ ORCHESTRATOR (Main Agent) │
│ Coordinates: 1 Agent + 2 Skills + 1 MCP Tool │
│ │
│ ┌──────────┐ ┌─────────────────┐ ┌────────────┐ │
│ │ research │ -> │ hook-methodology│ -> │ art-style │ │
│ │ (Agent) │ │ (Skill) │ │ (Skill) │ │
│ └────┬─────┘ └───────┬─────────┘ └─────┬──────┘ │
│ ↓ ↓ ↓ │
│ research/ hook-bank/ creatives/ │
│ {brand}.md {brand}-{date}.md {brand}.json │
│ ↓ │
│ ┌──────────────────┐ │
│ │ nano-banana │ │
│ │ (MCP Tool) │ │
│ │ fal.ai API │ │
│ └────────┬─────────┘ │
│ ↓ │
│ generated-images/ │
└─────────────────────────────────────────────────────────────────────────┘
Hook-first ad generation with 6 diverse emotional triggers
Real-time WebSocket streaming with cancel/pause/resume
Session recovery after disconnect (40-min buffer)
MCP image generation via fal.ai Nano Banana Pro
Dual deployment: Local (Express) + Production (Cloudflare Workers)
┌─────────────────────────────────────────────────────────────────────────┐
│ CLIENT REQUEST │
│ WebSocket /ws → { type: "generate", prompt } │
└───────────────────────────────────┬─────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────────────┐
│ EXPRESS SERVER (sdk-server.ts) HTTP: 3001 | WebSocket: /ws │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ WebSocket: generate, cancel, pause, resume, ping, subscribe │ │
│ │ REST: POST /generate, GET /sessions, GET /images │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────┬─────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────────────────┐
│ AI CLIENT (ai-client.ts) → CLAUDE SDK ORCHESTRATION │
│ ┌───────────────┐ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ SessionManager│ │ SDKInstrumentor │ │ Claude SDK query() │ │
│ │ (persistence) │ │ (cost tracking) │ │ model: opus-4-5 │ │
│ └───────────────┘ └─────────────────┘ └──────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ ORCHESTRATOR → Task(research) → Skill(hooks) → Skill(art) → MCP │ │
│ └─────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
Aspect
Local (server/)
Cloudflare (creative-agent-cf/)
Runtime
Node.js + Express
Workers + Sandbox containers
Streaming
WebSocket /ws
SSE with trace events
Storage
Filesystem JSON
D1 (metadata) + R2 (files)
Bidirectional
Yes (cancel/pause/resume)
No (SSE unidirectional)
Extract URL, brand name, style preference from prompt.
Task(subagent_type: "research")
├── WebFetch homepage
├── Extract: Offer, Value Props, Proof Points, Brand Colors
├── Analyze: Target Audience / ICP
└── Write: files/research/{brand}_research.md
3. Hook-Methodology Skill
Skill("hook-methodology")
├── Read research file
├── Build 10+ hooks using formulas
├── Select 6 diverse hooks (diversity matrix below)
├── Quality check each hook
└── Write: hook-bank/{brand}-{date}.md
Diversity Matrix:
Concept
Hook Type
Emotional Trigger
1
Stat/Data
Social Proof
2
Story/Result
Empathy + Relief
3
FOMO/Urgency
Loss Aversion
4
Curiosity
Intrigue
5
Call-out
Recognition
6
Contrast/Enemy
Differentiation
Skill("art-style")
├── Read hook-bank file
├── Route to style workflow (default: soft-brutalism-clay)
└── Write: files/creatives/{brand}_prompts.json
mcp__nano-banana__generate_ad_images({
prompts: [prompt1, prompt2, prompt3], // Batch 1
aspectRatio: "1:1", imageSize: "2K"
})
→ Repeat for prompts 4-6 (Batch 2)
→ Output: generated-images/{sessionId}/*.png
Return summary with image URLs and instrumentation data.
┌─────────────────┐
│ RESEARCH AGENT │ WebFetch → Extract → Analyze ICP
└────────┬────────┘
│ writes
↓
┌─────────────────────────────────────────┐
│ files/research/{brand}_research.md │
│ # Brand - Research Brief │
│ ## The Offer, Value Props, Proof Points│
│ ## Brand Colors (hex), Target ICP │
└────────┬────────────────────────────────┘
│ reads
↓
┌─────────────────────┐
│ HOOK-METHODOLOGY │ Build 10+ hooks → Select 6 diverse → Quality check
└────────┬────────────┘
│ writes
↓
┌─────────────────────────────────────────┐
│ hook-bank/{brand}-{date}.md │
│ # Brand - Hook Bank │
│ ## Concept 1-6 (Hook + Body + CTA) │
└────────┬────────────────────────────────┘
│ reads
↓
┌─────────────────────┐
│ ART-STYLE SKILL │ Route to style → Create visual prompts
└────────┬────────────┘
│ writes
↓
┌─────────────────────────────────────────┐
│ files/creatives/{brand}_prompts.json │
│ { "brand": "...", "prompts": [...] } │
└────────┬────────────────────────────────┘
│ reads
↓
┌─────────────────────┐
│ ORCHESTRATOR + MCP │ Batch 1 (3 images) → Batch 2 (3 images)
└────────┬────────────┘
│ generates
↓
┌─────────────────────────────────────────┐
│ generated-images/{sessionId}/ │
│ ├── {timestamp}_1_{prompt}.png │
│ └── ... (6 total) │
└─────────────────────────────────────────┘
File
Lines
Purpose
sdk-server.ts
~920
Express server, WebSocket init, REST endpoints
lib/websocket-handler.ts
~480
WebSocket server, resilience layer
lib/event-buffer.ts
~110
Event buffering for session recovery
lib/ai-client.ts
~490
Claude SDK wrapper, session-aware queries
lib/session-manager.ts
~340
Session lifecycle, persistence, forking
lib/nano-banana-mcp.ts
~300
MCP server for fal.ai image generation
lib/orchestrator-prompt.ts
~72
System prompt defining workflow
lib/instrumentor.ts
~150
Cost/token tracking
Type
Payload
Description
generate
{ prompt, sessionId? }
Start generation
cancel
{}
Abort current generation
pause
{}
Pause streaming (buffer messages)
resume
{}
Resume streaming (flush buffer)
ping
{}
Keep-alive heartbeat
subscribe
{ sessionId, lastEventId }
Reconnect to existing session
All messages include id (number) for event tracking.
Type
Fields
Description
ack
message
Connection/action acknowledgment
phase
phase, label
Workflow phase change
tool_start
tool, toolId, input
Tool invocation started
tool_end
toolId, success
Tool completed
message
text
Assistant text output
image
urlPath, prompt, filename
Generated image ready
complete
sessionId, duration, imageCount
Generation finished
error
error
Error occurred
subscribed
sessionId, message
Recovery confirmation
pong
-
Heartbeat response
Generation continues on server when client disconnects. Events are buffered for replay on reconnect.
┌─────────────────────────────────────────────────────────────────────────┐
│ WEBSOCKET RESILIENCE ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ CLIENT SERVER │
│ ┌──────────────────┐ ┌─────────────────────────────┐ │
│ │ localStorage │ │ event-buffer.ts │ │
│ │ ┌────────────┐ │ │ ┌───────────────────────┐ │ │
│ │ │activeSession│ │ │ │ sessionEventBuffers │ │ │
│ │ │{ sessionId,│ │ │ │ Map<sessionId, │ │ │
│ │ │ prompt } │ │ │ │ EventBuffer> │ │ │
│ │ ├────────────┤ │ │ │ │ │ │
│ │ │lastEventId │ │ │ │ { events: [...], │ │ │
│ │ │ 42 │ │ │ │ nextId: 43 } │ │ │
│ │ └────────────┘ │ │ └───────────────────────┘ │ │
│ └────────┬─────────┘ └──────────────┬──────────────┘ │
│ │ │ │
│ ┌────────▼─────────┐ │ │
│ │ useWebSocket.ts │◄══════ WebSocket ══════════►│ │
│ │ - Track eventId │ │ │
│ │ - Persist state │ ┌──────────────▼──────────────┐ │
│ │ - Auto-subscribe │ │ Generation Runner │ │
│ └──────────────────┘ │ (continues on disconnect) │ │
│ └─────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ RECOVERY FLOW: │
│ 1. Client disconnects (refresh/tab switch/network) │
│ └── Server: Generation continues, events buffered │
│ 2. Client reconnects, reads sessionId + lastEventId from localStorage │
│ 3. Client sends: { type: "subscribe", sessionId, lastEventId: 42 } │
│ 4. Server replays events 43, 44, 45... → Client UI catches up │
│ 5. Server sends: { type: "subscribed" } → Normal streaming resumes │
└─────────────────────────────────────────────────────────────────────────┘
Configuration:
MAX_EVENTS_PER_SESSION: 1000 events
MAX_BUFFER_AGE_MS: 40 minutes
Cleanup interval: 5 minutes
Scenario
Behavior
Page refresh
Auto-recovers from localStorage
Mobile tab switch
Reconnects when tab active
Network blip
Replays missed events
Close & reopen
Recovers if within 40-min window
User cancels
Works even after reconnect
Buffer expired
Graceful error: "Session not found"
Endpoint
Method
Description
/generate
POST
Campaign generation (non-streaming)
/health
GET
Health check
/sessions
GET
List all sessions
/sessions/:id
GET
Get session stats
/sessions/:id/continue
POST
Resume session
/sessions/:id/fork
POST
Create A/B variant
/images
GET
List all images by session
/images/:sessionId/:filename
GET
Serve image
┌─────────────────────────────────────────────────────────────────────────┐
│ AGENT & SKILLS HIERARCHY │
├─────────────────────────────────────────────────────────────────────────┤
│ ┌─────────────────────────┐ │
│ │ ORCHESTRATOR │ │
│ │ (Main Agent) │ │
│ │ Tools: Task, Skill, │ │
│ │ TodoWrite, MCP, Read │ │
│ └───────────┬─────────────┘ │
│ ┌────────────────────┼────────────────────┐ │
│ ↓ ↓ ↓ │
│ ┌─────────────┐ ┌───────────────┐ ┌─────────────┐ │
│ │ RESEARCH │ │ HOOK- │ │ ART-STYLE │ │
│ │ (Agent) │ │ METHODOLOGY │ │ (Skill) │ │
│ │ │ │ (Skill) │ │ │ │
│ │ Tools: │ │ Guidance: │ │ Guidance: │ │
│ │ - WebFetch │ │ - Formulas │ │ - Style │ │
│ │ - Read │ │ - Diversity │ │ routing │ │
│ │ - Write │ │ - Quality │ │ - Prompts │ │
│ └──────┬──────┘ └───────┬───────┘ └──────┬──────┘ │
│ ↓ ↓ ↓ │
│ research/{brand}.md hook-bank/{date}.md prompts.json │
└─────────────────────────────────────────────────────────────────────────┘
Component
Task
Skill
TodoWrite
WebFetch
Read
Write
MCP
Orchestrator
✓
✓
✓
-
✓
-
✓
Research Agent
-
-
-
✓
✓
✓
-
Skills provide guidance/context, not tool access.
Research Agent (agent/.claude/agents/research.md)
Purpose: Extract factual data from homepages + analyze ICP
Tools: WebFetch, Read, Write
Output: files/research/{brand}_research.md
Rules: Be specific (numbers, quotes, hex codes), no recommendations
Hook-Methodology Skill (agent/.claude/skills/hook-methodology/SKILL.md)
Purpose: Generate conversion-focused ad copy
Core principle: Hook = 80% of ad performance
Output: hook-bank/{brand}-{date}.md
Quality checks: Specific? Emotional? 3-second clarity? Competitor-proof?
Anti-patterns: "Your trusted partner", round numbers, "Learn more" CTA
Art-Style Skill (agent/.claude/skills/art-style/SKILL.md)
Purpose: Create visual prompts for image generation
Input: hook-bank file
Output: files/creatives/{brand}_prompts.json
Style routing: clay/brutalist → soft-brutalism-clay (default), surreal → surrealist-scale, minimal → minimal-photography
creative_agent/
├── agent/ # Agent ecosystem (SOURCE OF TRUTH)
│ ├── .claude/
│ │ ├── agents/research.md # Data extraction agent
│ │ └── skills/
│ │ ├── hook-methodology/ # Hook generation skill
│ │ │ ├── SKILL.md
│ │ │ ├── formulas.md
│ │ │ └── hook-bank/ # Generated hooks
│ │ └── art-style/ # Visual prompt skill
│ │ ├── SKILL.md
│ │ └── workflows/
│ └── files/ # Working directory
│ ├── research/ # Research output
│ └── creatives/ # Visual prompts
├── server/ # Local dev server
│ ├── sdk-server.ts
│ ├── lib/
│ │ ├── websocket-handler.ts
│ │ ├── event-buffer.ts
│ │ ├── ai-client.ts
│ │ ├── session-manager.ts
│ │ ├── nano-banana-mcp.ts
│ │ ├── orchestrator-prompt.ts
│ │ └── instrumentor.ts
│ └── sessions/ # Session JSON files
├── creative-agent-cf/ # Cloudflare production
│ ├── src/ # Worker code
│ ├── sandbox/ # Container code
│ └── agent/ # Copy of agent/
├── client/ # React frontend
│ ├── src/
│ │ ├── components/ # UI components
│ │ ├── hooks/useWebSocket.ts # WebSocket hook
│ │ ├── store/ # Zustand state
│ │ └── types/ # TypeScript types
│ └── vite.config.ts
├── generated-images/ # Image output (git-ignored)
└── docs/ # Documentation
1. CREATE → sessionManager.getOrCreateSession() → campaign-{timestamp}
2. SDK INIT → SDK returns session_id → sessionManager.updateSdkSessionId()
3. MESSAGES → Each SDK message → sessionManager.addMessage() → auto-save every 10
4. RESUME → POST /sessions/:id/continue → SDK continues with context
5. FORK → POST /sessions/:id/fork → Creates A/B variant
6. COMPLETE → sessionManager.completeSession() → status: 'completed'
7. CLEANUP → Every 1 hour: delete sessions > 24 hours old
prompt, status (idle/generating/complete/error)
sessionId, phase, terminalLines[], images[]
Component
Purpose
PromptInput
Input form, cancel button, recovery banner
ProgressDots
Phase indicators (parse → research → hooks → art → images → complete)
Terminal
Log output with syntax highlighting
ImageGrid
Image gallery with lightbox
Auto-connect on mount with 500ms delay
Auto-reconnect (max 5 attempts, exponential backoff)
25-second ping interval
Session recovery from localStorage on reconnect
Category
Technology
AI
Claude SDK 0.1.54, claude-opus-4-5-20251101
Image Gen
fal.ai Nano Banana Pro via MCP
Server
Express 4.x, ws 8.x
Client
React 19, Vite 7, Zustand, TailwindCSS
Production
Cloudflare Workers, D1, R2
Decision
Rationale
1 Agent + 2 Skills
Agents have tools, Skills provide guidance
Hook-first methodology
Hooks = 80% of ad performance
File-based communication
Simple, debuggable, no shared state
WebSocket over SSE
Bidirectional control (cancel/pause/resume)
Event buffering
Recovery without generation restart
Session forking
A/B testing capability
MCP for images
Proper tool interface, SDK integration
In-memory buffer
Fast, sufficient for generation lifetime