📄 content-pipeline-v2-final.md 14,597 bytes Apr 22, 2026 📋 Raw

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

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)

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:

{
  "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

@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)

{
  "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