Architecture¶
The pipeline is a set of independent agents that never talk to each other directly. Every read and every write goes through one FastAPI service — the hub — over plain HTTP. This page covers the component map, the 7-stage data flow, the on-disk storage layout, the memory model, and the platform-wide concerns (logging, evals, config, secrets) that every agent implements the same way.
The one integration principle
The hub (ReelScraper, served at http://127.0.0.1:8787) is the single integration point.
Agents read and write only via /api/* and never touch another agent's files or folders.
Decoupling comes from the HTTP boundary, not from folder layout. See the full contract in
API Reference.
Component map¶
graph LR
subgraph Sources["Sources"]
IG[Instagram]
X[X / Twitter]
YT[YouTube]
end
subgraph Hub["ReelScraper — the hub :8787"]
API["FastAPI /api/*"]
Store[("content.json / analysis/ / media/ /\nstudio/ / discovery/ / logs/ / evals/ / config/")]
SSE["/api/events (SSE)"]
Media["/media static mount"]
Static["/ static mount (frontend/dist)"]
end
Scrapers["Scraper subagents\n(ig-analyst, x-analyst, yt-analyst)\n+ core/virality.py scoring"]
AutoSearch["AutoSearch\n(kind: discovery)"]
AnalysisEngine["AnalysisEngine\n(blueprints, Gemini)"]
SimilarContent["SimilarContent\n(kind: clone, producer)"]
FutureProducers["future producers:\nproposal-content, creative-idea,\ntemplate-content"]
Dashboard["Dashboard\n(The Cutting Room, React/TS)"]
IG --> Scrapers
X --> Scrapers
YT --> Scrapers
Scrapers -->|writes content.json, media| Store
AutoSearch -- "POST /api/discovery/{p}" --> API
AnalysisEngine -- "GET/POST /api/analysis, /api/corpus, /api/insights" --> API
SimilarContent -- "GET /api/corpus, /api/analysis, /api/audio\nPOST /api/studio" --> API
FutureProducers -.->|registers via producer SPI| API
Dashboard -- "GET/POST /api/* over HTTP only" --> API
Dashboard -.->|same-origin in prod| Static
Dashboard -.->|inline clip playback| Media
API --> Store
API --> SSE
SSE -.->|jobs + log frames| Dashboard
API -- "shells out (uv run cli.py)" --> AnalysisEngine
API -- "shells out (uv run cli.py)" --> AutoSearch
Roster¶
| Component | Role | Talks to hub via |
|---|---|---|
| ReelScraper | The hub itself. Scrapes handpicked creator pages, scores virality (4-signal engine), and serves the entire /api/* contract, the built Dashboard, and local /media files. |
— (is the hub) |
| AnalysisEngine | Watches top clips frame-by-frame with Gemini and writes generation-ready blueprints (schema_version 2). The shared substrate every producer reads. |
/api/analysis, /api/corpus, /api/insights, /api/reference |
| SimilarContent | A producer (kind: clone) that recreates a winning clip 1:1 from its blueprint's shots[] and regeneration_guide. |
/api/corpus, /api/analysis, /api/audio, /api/studio |
| AutoSearch | The "front door" (kind: discovery). Searches Instagram for new creators, scores niche-fit, and posts candidates for human review. |
/api/discovery, /api/config, /api/corpus/{p}/factors, /api/insights |
| Dashboard | "The Cutting Room" — React 18 + TS + Vite + Tailwind v4 control board. Renders producer lanes, the human gate, sounds, blueprints, activity, and discovery. | all read routes, plus the human-gate POST routes |
_producer-template/ |
Scaffold for spinning up new producers (cp -r _producer-template <Dir>). |
registers via the same producer SPI |
Settled decision, not a TODO
The hub staying inside ReelScraper/ — rather than moving to the repo root — is a deliberate
architectural call: it's deeply coupled to the scraper's core/, its per-platform data, and its
subprocess pipeline control. Moving it would buy no additional decoupling, since HTTP already
provides that boundary.
The 7-stage data flow¶
Discovery was prepended to an originally 6-node board (Sources → Scrape → Analyze → Media → Blueprint → Studio) once AutoSearch was added, making it 7 stages end to end.
flowchart LR
D["1. Discover\n(AutoSearch)"] -->|human approves candidate| S["2. Sources\n(pages.txt)"]
S --> SC["3. Scrape\n(platforms/<p>/scrape.py)"]
SC --> AN["4. Analyze / score\n(run.py analyze, virality.py)"]
AN --> M["5. Media\n(download_media.py)"]
M --> BP["6. Blueprint\n(AnalysisEngine, Gemini)"]
BP --> ST["7. Studio\n(producers)"]
ST --> G{Human gate}
G -->|approved| Post[Post]
G -->|rejected| Drop[Dropped]
| Stage | Owner | Produces |
|---|---|---|
| 1. Discover | AutoSearch | candidate records pending human review (POST /api/discovery/{p}) |
| 2. Sources | pages.txt (human-curated or auto-search-approved) |
the handle/link list a scrape run consumes |
| 3. Scrape | platforms/<p>/scrape.py |
raw per-post JSON → content.json after normalize (metrics, captions, media URLs — CDN links expire in hours) |
| 4. Analyze / score | run.py analyze + core/virality.py |
the 4-signal engine (engagement_rate, reach_multiplier, outlier_score, velocity), percentile-normalized and blended into a virality_score (0-100) + tier; xlsx/CSV reports; indexed memory |
| 5. Media | download_media.py |
top-viral clips persisted to media/<p>/<content_id>.mp4 (+ .jpg) for inline board playback and for AnalysisEngine to watch |
| 6. Blueprint | AnalysisEngine | schema_version-2 blueprint via POST /api/analysis/{p}: video_metadata, global_style, audio, audio_strategy, characters_and_subjects[], text_overlays[], shots[] (each with generation_prompt/negative_prompt), regeneration_guide, virality_formula, evaluation |
| 7. Studio | producers (SimilarContent + future proposal/idea/template agents) | markdown proposals via POST /api/studio/{p} (mandatory ## Audio block), gated by human approve/reject before posting |
Naming: Blueprint, not Analyze
Stage 6 is labeled Blueprint on the pipeline board, not "Analyze," specifically to avoid confusion with stage 4's virality scoring — the two are unrelated uses of "analysis" in this system.
See Pipeline Stages for the per-stage CLI commands and job lifecycle, and Agents for each agent's full call sequence against the hub.
On-disk storage layout¶
Everything lives inside ReelScraper/ (ROOT below is that repo root). Sibling repos —
AnalysisEngine/, SimilarContent/, Dashboard/, AutoSearch/, and _producer-template/ — sit
alongside it under the parent directory and never read these paths directly; they only call
BACKEND_API.
core/ shared, platform-agnostic engine
schema.py the normalized Content record every platform emits
virality.py 4-signal scoring engine + Excel/CSV reports
memory.py ContentMemory (per-platform) + SharedInsights (cross-agent)
runner.py shared run.py CLI (analyze|search|insight|insights)
corpus.py the read adapter behind /api/corpus/*
audio.py sound_trend_score + bucket computation
logsetup.py shared production logging convention
platforms/<p>/ per-platform adapter (instagram|x|youtube)
scrape.py, normalize.py, run.py, niche_config.json, pages.txt
content.json normalized scraped rows — has content_id per row
memory/<p>/ per-platform memory: MEMORY.md, patterns.md, persona.md, decisions.jsonl, content.db (SQLite FTS5)
memory/shared/ ONLY cross-agent channel: METHOD.md, INSIGHTS.md, insights.jsonl
logs/ per-invocation logs (git-ignored); agents.jsonl = central curated LIFECYCLE log
api/app.py the hub itself (REST + SSE + media + serves frontend build)
cli.py single entry point: start | scrape | analyze | media
download_media.py persists top videos to media/ for inline play
media/<p>/<content_id>.mp4|.jpg downloaded top-viral clip media; ref_*.mp4 for references
renders/<p>/<render_id>/ producer-GENERATED reels (reel.mp4 + poster.jpg + render.json); render_id derived server-side from the studio filename. STRICTLY separate from media/ (scraped corpus); mounted at "/renders"
studio/<p>/ generated proposals (markdown) + meta.json sidecar + gate.jsonl
analysis/<p>/ blueprints keyed <content_id>.json; ref_<hash>.json for references
producers/registry.json self-registered producer manifests
references/<p>/registry.json ad-hoc reference/template videos (ref_<hash>) metadata
discovery/<p>/candidates.json creator candidates from auto-search
discovery/<p>/gate.jsonl candidate approve/reject decision log
evals/<agent>/<id>.json + evals.jsonl self-eval/judge results
config/agents/<agent>.json per-agent config (Dashboard-editable)
.claude/agents/ ig-analyst + x-analyst + yt-analyst — the hub's own scraper subagents
frontend/dist/ the built Dashboard (mounted at "/" by the hub)
Join keys¶
| Key | Ties together |
|---|---|
content_id |
platforms/<p>/content.json rows, media/<p>/<content_id>.{mp4,jpg}, analysis/<p>/<content_id>.json blueprints. Also used as an alias of candidate_id in AutoSearch's board gate-join. |
audio_id |
content.json rows' audio_* fields, GET /api/audio/{p}/sound/{audio_id}, and the trending-sound table. |
candidate_id |
Stable cand_<sha1(platform:handle)[:10]> (or agent-supplied); identifies one creator candidate across discovery/{p}/candidates.json and discovery/{p}/gate.jsonl. Doubles as content_id in the agent board's gate-join for discovery-kind producers (no data.file, unlike studio's file-keyed join). |
run_id |
Links a central logs/agents.jsonl lifecycle event back to an agent's own full-fidelity local per-run JSONL log; groups a run's items in GET /api/agents/{name}/board. |
Memory model¶
Memory splits cleanly along the same integration boundary as everything else: what belongs to one agent's own operation stays local; what's worth transferring across agents goes through the hub.
- Per-agent memory — each platform adapter keeps
memory/<p>/MEMORY.md,patterns.md,persona.md,decisions.jsonl, and a SQLite FTS5 index (content.db) that backsGET /api/corpus/{p}/search. Each sibling agent (AnalysisEngine, SimilarContent, AutoSearch) also keeps its own local memory files it reasons over per run — these are never read by any other agent directly. - Shared insights — the only cross-agent memory channel is
memory/shared/(METHOD.md,INSIGHTS.md,insights.jsonl), exposed over HTTP asGET/POST /api/insights. Each producer run posts at most one transferable learning (kind: finding, negative, method, or idea) and reads prior insights before acting — for example, AutoSearch reads the priortrending-termsinsight before expanding search terms.
Note
No agent ever reads another agent's per-agent memory files. The shared-insights endpoint is the only sanctioned crossover point, which keeps the same HTTP-only integration discipline intact even for "soft" knowledge transfer.
Platform-wide concerns¶
Every agent — analyzer or producer — implements four cross-cutting conventions identically. The hub is the shared implementation for all of them.
Unified logging¶
Each agent keeps its own full-fidelity local per-run log (pretty + JSONL, via a shared
logsetup convention) and additionally posts curated lifecycle events to the hub:
POST /api/logs {agent, level, event, msg?, run_id?, platform?, content_id?, ts?, data?}
The hub appends these to logs/agents.jsonl, buffers them for the SSE log channel on
/api/events, and GET /api/agents/{name}/board reduces the same stream per agent into
runs → items → current stage. The event vocabulary is shared across every agent:
run.start → item.start(data.stage) → item.stage(mid-item transition) → item.done(terminal stage, +data.score/+data.file for producers) → run.end
with item.error mapping to an implicit Failed lane on failure. Posting is best-effort and
swallowed on failure, so a hub logging outage never aborts an agent's run.
Warning
Approved and Rejected are never emitted by an agent itself — they are always derived by the
hub's board reducer left-joining a human's gate decision (studio/{p}/gate.jsonl by filename
for producers, discovery/{p}/gate.jsonl by content_id == candidate_id for discovery).
Eval pipeline¶
Producers and the analyzer self-score their own output and post the result:
POST /api/evals {agent, target_type, target_id, scores?, verdict?, judge?, notes?, platform?, ts?}
stored at evals/<agent>/<id>.json and appended to evals.jsonl; GET /api/evals feeds the
Dashboard's score-trend charts. target_type covers blueprint, clone, proposal, idea,
audio, and similar generated-artifact kinds.
Hub-stored config¶
Per-agent configuration lives on the hub, not in each agent's own repo:
GET /api/config/agent/{agent}— read config, with defaults populated from the agent's registered manifestconfig_schema.PUT /api/config/agent/{agent}— write config; the Dashboard renders a schema-driven form from the sameconfig_schema.
Every agent fetches its config at the start of a run and merges it over local defaults, so an operator can retune an agent (thresholds, cadence, feature flags) from the Dashboard without touching that agent's code.
Secrets by name¶
The hub never stores a secret value. Each producer manifest declares secrets by env-var name only; the hub can report presence but never contents:
GET /api/config/agent/{agent}/secrets/status → [{name, env_var, present, required}]
Actual credentials (Gemini keys, Anthropic keys, Instagram session cookies) stay local to the agent process that needs them, referenced everywhere else purely by the env-var name.
Deployment note¶
The pipeline is designed to run local-first: uv run cli.py start inside ReelScraper/ boots
the hub and opens the web board at http://127.0.0.1:8787 — hub and Dashboard together are "the
product." In production the Dashboard is served same-origin with the hub: api/app.py mounts
the Dashboard's built Vite output (frontend/dist) at / via FastAPI StaticFiles, so there is no
separate frontend server and no cross-origin requests to reason about. Further StaticFiles mounts
serve /media (the scraped corpus) and /renders (producer-generated reels) — both range-request
capable for inline playback and kept structurally separate — plus /documentation (this MkDocs
site) when it has been built. In development the Dashboard dev server proxies /api, /media and
/renders to the same hub instead. Sibling
agent repos are independent uv-managed projects that integrate purely by calling BACKEND_API
(default http://127.0.0.1:8787) — never a hardcoded filesystem path — which keeps the whole system
portable to a different host or port with a single env-var change.
Continue to Pipeline Stages for stage-by-stage CLI and job-lifecycle detail, or
API Reference for the full /api/* route contract.