# Content Pipeline v2 — Final Implementation Document **Unified Brief: Socrates 🧠 + Daedalus 🎨 Responses Assimilated** **Date:** 2026-04-22 **Status:** Clarifications received, ready for implementation **Owner:** Wadsworth 📋 (coordination), Socrates (backend), Daedalus (frontend) **Approver:** Matt (Director) — 5 decisions required --- ## Executive Summary Content Pipeline v2 transforms blog generation from "topic → generic output" to "struggle → authentic narrative." Both agents have provided implementation feedback. This document resolves open questions and specifies final architecture. **Key change:** Human-in-the-loop approval gate before GPU spend. --- ## Architecture Decisions (Resolved) ### 1. Approval Surface — TELEGRAM DM **Decision:** Async notification pattern - Brief created → stored with `status=pending` - Telegram DM to Matt: "New brief: [title] — Approve? [Yes] [No] [Edit]" - On approval: trigger generation, notify user - On rejection: return to brief author with feedback **Why:** Matt's workflow is Telegram-centric; no new UI surface needed ### 2. State Persistence — SQLITE TABLE **Decision:** SQLite table `content_briefs_v2` ```sql CREATE TABLE content_briefs_v2 ( id TEXT PRIMARY KEY, title TEXT NOT NULL, struggle_angle TEXT NOT NULL, origin_story TEXT NOT NULL, attempts JSON NOT NULL, -- array of {attempt, why_failed} the_moment TEXT NOT NULL, the_fix TEXT NOT NULL, reflection TEXT NOT NULL, voice_checklist JSON NOT NULL, -- boolean map status TEXT CHECK(status IN ('draft', 'pending', 'approved', 'rejected', 'generating', 'completed')), style_reference TEXT, target_length INTEGER, created_by TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, approved_at TIMESTAMP, approved_by TEXT, generation_job_id TEXT, struggle_score REAL, content_output TEXT ); ``` **Migration:** Net new table, no v1 migration needed ### 3. Struggle Score — HEURISTIC V1 **Decision:** Weighted heuristic formula (not LLM judge) ```python def calculate_struggle_score(content: str) -> float: checks = { "i_statement_ratio": len(re.findall(r"^I\s", paragraphs)) / len(paragraphs), "temporal_markers": len(re.findall(r"\d{1,2}:\d{2}|yesterday|last night", content)), "struggle_patterns": len(re.findall(r"I thought.*but|I tried.*failed|didn't work", content)), "cost_mentions": len(re.findall(r"hour|minute|sleep|wife|kid|Aundrea|frustrated", content)), "admission_count": len(re.findall(r"I was wrong|I didn't expect|should have", content)) } weights = { "i_statement_ratio": 0.30, "temporal_markers": 0.15, "struggle_patterns": 0.25, "cost_mentions": 0.20, "admission_count": 0.10 } score = sum(checks[k] * weights[k] for k in weights) return min(score * 100, 100) # Cap at 100 ``` **Why:** Fast, local, deterministic; v2 may add LLM judge overlay ### 4. Style Examples — MANUAL V1 **Decision:** Phase 1 = manual JSON; Phase 2 = automated extraction **Required from Matt:** Provide `shared/project-docs/blog/style-examples/dns-night.json` Structure: ```json { "slug": "dns-night", "title": "The Night I Broke DNS and My Wife Couldn't Reach Facebook", "excerpt": "2 AM. Aundrea's phone wasn't loading Instagram...", "structure": { "hook": { "type": "timestamp_location_crisis", "content": "..." }, "struggle": { "attempts": 2, "escalation": "..." }, "moment": { "realization": "..." }, "fix": { "solution": "...", "caveats": "..." }, "reflection": { "lesson": "..." } }, "voice_markers": { "i_statements": 12, "quotes": ["Aundrea said...", "I thought..."], "cost_mentions": ["2 AM", "wife acceptance factor"] } } ``` **Socrates task:** Create template; Matt fills with "DNS Night" article ### 5. API Versioning — EXTEND CURRENT ENDPOINT **Decision:** Option B — backward-compatible extension ```python @router.post("/admin/content/generate") async def generate_content( request: GenerateRequest, brief_id: Optional[str] = None, # NEW: v2 uses brief_id version: str = Query("v1", description="API version"), ): if request.content_type == "struggle_first" or version == "v2": return await generate_v2(request, brief_id) return await generate_v1(request) ``` **Why:** Daedalus keeps existing integration; v2 is opt-in --- ## Component Specifications (Final) ### Backend (Socrates) | Module | File | Purpose | |--------|------|---------| | Brief model | `blog/models/brief.py` | SQLAlchemy model for `content_briefs_v2` | | Brief validation | `blog/validation/brief.py` | Real-time field validation | | Telegram notifier | `blog/notifications/telegram.py` | Send approval DMs to Matt | | Brief approval | `blog/router/brief.py` | Endpoints for create/validate/approve | | Generation v2 | `blog/generation/v2.py` | Prompt + example injection | | Struggle score | `blog/metrics/struggle.py` | Heuristic calculation | | Style loader | `blog/style/loader.py` | Load JSON examples | ### Frontend (Daedalus) | Component | File | Purpose | |-----------|------|---------| | Design tokens | `shared/design-tokens/blog-pipeline/content-v2.json` | Colors, spacing, animations | | `StruggleBriefForm` | `/admin/components/StruggleBriefForm.jsx` | Multi-section incident capture | | `VoiceChecklist` | `/admin/components/VoiceChecklist.jsx` | Pre-generation validation | | `ProgressBar` | `/admin/components/ProgressBar.jsx` | Sticky completion indicator | | `BriefReview` | `/admin/screens/BriefReview.jsx` | Preview before approval | | `ContentPreview` | `/admin/screens/ContentPreview.jsx` | Side-by-side brief/output | | `StruggleScore` | `/admin/components/StruggleScore.jsx` | Bar chart visualization | | `StyleSelector` | `/admin/components/StyleSelector.jsx` | Published post reference | --- ## User Flow (Complete) ``` USER (Daedalus/Matt) BACKEND (Socrates) │ │ ├─ Opens Magic Wand ──────────────────────►│ │ (new "Struggle-First" tab) │ │ │ ├─ Fills StruggleBriefForm ───────────────►│ │ ├─ What broke? │ │ ├─ Who was affected? │ │ ├─ Attempt 1 + why failed │ │ ├─ Attempt 2 + why failed │ │ ├─ The moment (realization) │ │ ├─ The fix + caveats │ │ ├─ What you'd do differently │ │ └─ Style reference │ │ │ ├─ Completes VoiceChecklist ──────────────►│ │ (all 6 items checked) │ │ │ ├─ Clicks "Submit for Review" ────────────►├─ POST /api/v1/content/briefs │ │ ├─ Validate fields │ │ ├─ Save to SQLite │ │ └─ status = "pending" │ │ │◄─ Shows "Brief submitted" confirmation ──┤ │ │ │ ├─ Telegram DM to Matt │ │ "New brief: [title] │ │ Approve? [Yes] [No] [Edit]" │ │ MATT (in Telegram DM) │ │ │ ├─ Clicks [Yes] ─────────────────────────►├─ POST /api/v1/content/briefs/{id}/approve │ │ ├─ status = "approved" │ │ ├─ Trigger generation │ │ └─ WebSocket/SSE: "generating" │ │ │◄─ "Generation started" notification ─────┤ │ │ │ ├─ Load style example │ ├─ Build v2 prompt │ ├─ Call phi4:14b (local) │ ├─ Calculate struggle_score │ ├─ Save output │ └─ status = "completed" │ │ │◄─ Notification: "Content ready" ─────────┤ │ │ ├─ Opens ContentPreview ──────────────────►├─ GET /api/v1/content/briefs/{id}/output │ ├─ Left: Brief (highlighted sections) │ │ ├─ Right: Generated content │ │ ├─ Struggle score bar chart │ │ └─ [Edit Brief] [Accept] [Regenerate] │ │ │ ├─ Clicks [Accept] ───────────────────────►├─ Create post from output │ │ ├─ Save to database │ │ └─ Trigger static build │◄─ "Published to notes.hoffdesk.com" ───┤ ``` --- ## Design Tokens v2 (Final) ```json { "colors": { "struggle": { "high": "#22c55e", "medium": "#eab308", "low": "#ef4444" }, "validation": { "pass": "#22c55e", "fail": "#ef4444", "pending": "#6b7280", "complete": "#3b82f6" }, "section": { "active": "#f8fafc", "inactive": "#ffffff", "border": "#e2e8f0" } }, "spacing": { "brief-section-gap": "1.5rem", "progress-sticky-top": "1rem", "preview-pane-gap": "1rem" }, "preview": { "pane-width": "50%", "min-width": "320px", "max-content-width": "720px" }, "animation": { "check-pop": "200ms cubic-bezier(0.34, 1.56, 0.64, 1)", "section-collapse": "300ms ease-out", "button-pulse": "1.5s ease-in-out infinite" } } ``` --- ## API Contract (Final) ### Endpoints | Method | Path | Auth | Description | |--------|------|------|-------------| | POST | `/api/v1/content/briefs` | Bearer | Create new brief | | GET | `/api/v1/content/briefs/{id}` | Bearer | Get brief by ID | | POST | `/api/v1/content/briefs/{id}/validate` | Bearer | Validate fields | | POST | `/api/v1/content/briefs/{id}/submit` | Bearer | Submit for approval | | POST | `/api/v1/content/briefs/{id}/approve` | Bearer (Matt only) | Approve and trigger | | POST | `/api/v1/content/briefs/{id}/reject` | Bearer (Matt only) | Reject with feedback | | GET | `/api/v1/content/briefs/{id}/output` | Bearer | Get generated content | | GET | `/api/v1/content/style-references` | Bearer | List published posts | ### WebSocket Events (Optional Phase 2) | Event | Direction | Payload | |-------|-----------|---------| | `brief.status_change` | Server → Client | `{brief_id, old_status, new_status}` | | `generation.progress` | Server → Client | `{brief_id, progress: 0.0-1.0}` | | `generation.complete` | Server → Client | `{brief_id, content_preview}` | --- ## Implementation Phases ### Phase 0: Foundation (Both can start now) - [ ] Socrates: SQLite schema, model code - [ ] Daedalus: Design tokens, responsive grid, wireframes - [ ] Matt: Provide `dns-night.json` style example ### Phase 1: Backend Core (Socrates) - [ ] Brief CRUD endpoints - [ ] Telegram notification integration - [ ] Approval workflow - [ ] Prompt template v2 with manual style injection ### Phase 2: Frontend Core (Daedalus) - [ ] `StruggleBriefForm` with progress bar - [ ] `VoiceChecklist` with validation - [ ] `BriefReview` screen - [ ] API integration ### Phase 3: Integration (Both) - [ ] End-to-end testing - [ ] Struggle score verification - [ ] Mobile responsive pass ### Phase 4: Polish (Both) - [ ] Side-by-side `ContentPreview` - [ ] `StruggleScore` visualization - [ ] `StyleSelector` with auto-extraction (Phase 2) --- ## Success Metrics | Metric | Baseline | Target | Measurement | |--------|----------|--------|-------------| | Brief completion rate | N/A | >80% | Starts vs completions | | Approval-to-publish | 3 days (manual) | <24h | Time from approve to publish | | Struggle score | N/A | >75 avg | Post-generation metric | | "Sounds like me" test | N/A | 80%+ | Matt qualitative rating | --- ## Open Questions (For Matt — 5 Decisions) **✅ RESOLVED 2026-04-22** | Question | Decision | |----------|----------| | **Timeline** | MVP state achieved (flexible, no hard deadline) | | **Priority** | **P1** — quality improvement (not blocking) | | **v1 Retention** | **Yes** — 30-day overlap, v1 works but not well | | **Approval delegation** | **Household-level** — Aundrea can approve | | **Cloud fallback** | **Yes, opt-in** — local default, cloud with explicit toggle | --- ## File Locations | Document | Path | |----------|------| | This final brief | `shared/project-docs/blog/content-pipeline-v2-final.md` | | Backend briefing | `shared/project-docs/blog/content-pipeline-v2-backend-briefing.md` | | Backend response | `shared/project-docs/blog/content-pipeline-v2-socrates-response.md` | | Frontend briefing | `shared/project-docs/blog/content-pipeline-v2-frontend-briefing.md` | | Frontend response | (in Daedalus workspace) | | Editorial policy | `shared/project-docs/blog/content-ideas-v2.md` | | Style example template | `shared/project-docs/blog/style-examples/dns-night.json` (pending) | --- ## Next Steps 1. **✅ Matt:** Decisions provided — implementation approved 2. **Socrates:** Begin Phase 0 (schema) + Phase 1 (endpoints) 3. **Daedalus:** Begin Phase 0 (tokens) + Phase 2 (components) 4. **Wadsworth:** Coordinate daily standups, track blockers --- **Status:** ✅ **APPROVED** — Implementation begins **Timeline:** MVP when ready (no hard deadline) **Priority:** P1 (quality improvement) **Risk:** Low (v1 remains, backward compatible) *Document assimilated by: Wadsworth 📋* *Sources: Socrates response, Daedalus response, Wadsworth synthesis*