diff --git a/docs/architecture/architecture.md b/docs/architecture/architecture.md
new file mode 100644
index 0000000..022f64e
--- /dev/null
+++ b/docs/architecture/architecture.md
@@ -0,0 +1,196 @@
+# Tapir — Architecture (C4 + sequences)
+
+Diagrams describe **current assumptions**, not a frozen design. They are diffable Mermaid
+so they live in version control and render in Gitea. When a decision here changes, record it
+in `DECISIONS.md` and update the relevant diagram in the same commit.
+
+Altitude: C4 Level 1 (Context) and Level 2 (Container), plus sequence diagrams for the two
+behaviours that carry the most design risk. Clean Architecture layering is described after
+the diagrams.
+
+---
+
+## C4 L1 — System Context
+
+Who and what Tapir talks to. (Brain and external AI are dashed — both optional.)
+
+```mermaid
+graph TB
+ user["User
(maintainer; later, trusted users)"]
+ tapir["Tapir
watches subscriptions,
summarizes new videos"]
+ yt["YouTube
(Data API + captions)"]
+ vimeo["Vimeo
(API + text tracks)"]
+ local["Local AI stack
(LiteLLM / piguard alias)"]
+ byo["User's BYO AI
(Claude / OpenAI / Gemini)"]
+ brain["brain
(brain-mcp)"]
+
+ user -->|connects accounts,
reads summaries| tapir
+ tapir -->|subscriptions,
new-video events,
captions| yt
+ tapir -->|subscriptions,
text tracks| vimeo
+ tapir -->|summarize
PRIMARY| local
+ tapir -.->|summarize
FALLBACK, opt-in| byo
+ tapir -.->|optional sink:
brain_ingest| brain
+
+ classDef opt stroke-dasharray: 5 5;
+ class byo,brain opt;
+```
+
+---
+
+## C4 L2 — Containers
+
+Inside Tapir. The **engine** is provider- and sink-agnostic; everything external is an
+adapter behind an interface (Clean Architecture ports & adapters).
+
+```mermaid
+graph TB
+ subgraph tapir["Tapir (Go)"]
+ http["HTTP server
OAuth callbacks +
user-facing API"]
+ watcher["Watcher
detects new videos
(WebSub + poll)"]
+ engine["Summarization engine
(use-case core)"]
+ resolver["Transcript resolver
(captions-first)"]
+ router["AI router
(llm.Router:
Primary -> Fallback)"]
+ store[("User store
(Postgres,
per-user isolated)")]
+
+ subgraph sinks["Sink adapters (Sink interface)"]
+ sink_store["Store sink
(primary)"]
+ sink_brain["Brain sink
(HTTP brain-mcp)"]
+ end
+
+ subgraph providers["Provider adapters (VideoSource interface)"]
+ p_yt["YouTube adapter"]
+ p_vimeo["Vimeo adapter"]
+ end
+ end
+
+ http --> store
+ watcher --> p_yt
+ watcher --> p_vimeo
+ watcher -->|NewVideo event| engine
+ engine --> resolver
+ resolver --> p_yt
+ resolver --> p_vimeo
+ engine --> router
+ engine --> sink_store
+ engine -.-> sink_brain
+ sink_store --> store
+
+ classDef opt stroke-dasharray: 5 5;
+ class sink_brain opt;
+```
+
+**Interfaces that keep the engine pure (the ports):**
+
+- `VideoSource` — list subscriptions, detect new videos, fetch transcript. Implemented by
+ YouTube and Vimeo adapters.
+- `Summarizer` — turn a transcript + context into highlights/takeaways. Backed by the
+ `llm.Router` (Primary local, Fallback BYO).
+- `Sink` — deliver a summary. Implemented by the store sink (primary) and brain sink
+ (optional). New sinks add an implementation, nothing else.
+- `SecretStore` — fetch/store per-user OAuth tokens and BYO keys. Backed by ESO/1Password.
+
+The engine depends only on these interfaces, never on YouTube, brain, or a concrete model.
+This is what makes "standalone vs homelab" a configuration of which adapters are wired, not
+two codebases (ADR-003).
+
+---
+
+## Sequence — core use case: new video summarized
+
+```mermaid
+sequenceDiagram
+ participant Src as VideoSource
(YouTube/Vimeo)
+ participant W as Watcher
+ participant E as Engine
+ participant R as Transcript resolver
+ participant AI as AI router
+ participant S as Sink(s)
+
+ Src->>W: new upload (WebSub push / poll)
+ W->>E: NewVideo{user, channel, videoID}
+ E->>R: resolve transcript(videoID)
+ R->>Src: fetch captions
+ alt captions available
+ Src-->>R: captions text
+ R-->>E: Transcript{source: captions}
+ E->>AI: summarize(transcript, userContext)
+ AI-->>E: Summary{highlights, takeaways}
+ E->>S: deliver(summary)
+ S-->>E: ok
+ else no transcript
+ Src-->>R: none
+ R-->>E: NoTranscript
+ E->>S: deliver(skipped: no transcript)
+ end
+```
+
+---
+
+## Sequence — AI routing (local-first, BYO fallback)
+
+```mermaid
+sequenceDiagram
+ participant E as Engine
+ participant R as llm.Router
+ participant P as Primary
(local: LiteLLM/piguard)
+ participant F as Fallback
(user BYO key)
+
+ E->>R: summarize(transcript)
+ R->>P: complete(prompt)
+ alt local succeeds
+ P-->>R: summary
+ R-->>E: summary (fallback_used = false)
+ else local fails (error/timeout/unavailable)
+ P-->>R: error
+ alt user has BYO configured
+ R->>F: complete(prompt)
+ F-->>R: summary
+ R-->>E: summary (fallback_used = true)
+ else no BYO
+ R-->>E: error (queued for retry)
+ end
+ end
+```
+
+> "Reliably" (VISION / the BYO trigger) is operationalized as: **Primary returned without
+> error within timeout.** Richer quality scoring can layer on later without changing the
+> interface. `fallback_used` is recorded per summary so "is the local stack good enough?"
+> becomes a query, not a guess.
+
+---
+
+## Clean Architecture layering
+
+```mermaid
+graph LR
+ subgraph domain["Domain (entities)"]
+ d["User, Subscription, Video,
Transcript, Summary"]
+ end
+ subgraph usecase["Use cases (engine)"]
+ u["SummarizeNewVideo,
ConnectAccount,
RouteAI"]
+ end
+ subgraph ports["Ports (interfaces)"]
+ po["VideoSource, Summarizer,
Sink, SecretStore"]
+ end
+ subgraph adapters["Adapters (infra)"]
+ a["YouTube, Vimeo, llm.Router,
Store sink, Brain sink,
ESO secret store, HTTP, Postgres"]
+ end
+
+ a --> po
+ po --> u
+ u --> d
+```
+
+Dependencies point inward only. Domain knows nothing of YouTube, brain, Postgres, or any
+model. Adapters are swappable; tests target the use-case core through fake adapters (see the
+Gherkin features in `docs/use-cases/`).
+
+---
+
+## Out of scope in these diagrams (deferred per ADRs)
+
+- Audio-download + speech-to-text resolver (ADR-007) — would be an additional `VideoSource`
+ fallback path, drawn when built.
+- Multi-tenant isolation primitives (per-tenant Postgres role, NetworkPolicy, tenant label)
+ — activate at Stage 1 (ADR-002); single-user Stage 0 doesn't exercise them.
+- Public SaaS surface (sign-up, billing) — Future C, not built (ADR-008).