Architecture
tapes is built around four components:
tapes serve derive-worker /metrics All servers share the same PostgreSQL database (with pg_duckdb + pgvector) when run together via tapes serve.
Data Flow
┌─────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ │ │ │ │ │
│ Claude Code│────────▶│ Proxy :8080 │────────▶│ Upstream LLM │
│ Ollama CLI │◀────────│ (or Ingest │◀────────│ (Anthropic, │
│ AI SDK │ │ sidecar) │ │ Ollama, etc.) │
│ curl ... │ │ intercept + │ │ │
│ │ │ forward │ │ │
└─────────────┘ └────────┬─────────┘ └──────────────────┘
│
│ append raw turn
▼
┌──────────────────┐
│ PostgreSQL │
│ ┌────────────┐ │
│ │ raw_turns │ │ ← append-only capture log
│ └─────┬──────┘ │
│ │ derive │
│ ▼ │
│ ┌────────────┐ │
│ │ sessions / │ │ ← derived model
│ │ traces / │ │ (pg_duckdb)
│ │ spans │ │
│ ├────────────┤ │
│ │ pgvector │ │ ← span embeddings
│ └────────────┘ │
└────────┬─────────┘
│
│ query
▼
┌─────────────┐ ┌──────────────────┐
│ │ │ │
│ Deck TUI │◀───────▶│ API :8081 │
│ tapes CLI │ │ │
│ MCP clients│ │ /v1/sessions │
│ Prometheus │ │ /v1/traces │
│ │ │ /v1/stats │
│ │ │ /v1/search/... │
│ │ │ /v1/mcp │
│ │ │ /metrics │
└─────────────┘ │ /swagger │
│ │
└──────────────────┘
Requests flow through the proxy (or arrive via the ingest sidecar), which forwards to your upstream LLM and appends each completed turn to the raw_turns log. The deriver projects those raw turns into sessions, traces, and spans. The API server reads the derived model to serve session, search, and MCP queries — and self-documents at /swagger via Scalar.
Data Model
tapes separates capture from interpretation. The proxy and ingest sidecar only append; everything you query is derived:
raw_turns deriver sessions / traces / spans
(append-only) (pure, idempotent) (what you query)
┌──────────┐ ┌──────────────────────┐
│ raw turn │──┐ │ session │
├──────────┤ │ │ ├─ trace (turn 1) │
│ raw turn │ ├──────────▶ derive ──────────▶ │ │ ├─ span (llm) │
├──────────┤ │ │ │ ├─ span (tool) │
│ raw turn │──┘ │ │ └─ span (sub) │
└──────────┘ │ └─ trace (turn 2) │
└──────────────────────┘
- raw_turns — the durable, append-only capture log. Every request/response is recorded exactly as it happened. Nothing is interpreted here.
- deriver — a pure, idempotent projection. Given the same raw turns it always produces the same derived model, so it can be re-run safely (for example to backfill after a schema change).
- sessions / traces / spans — the model you actually browse. A session is one agent run; a trace is one user-visible turn; spans are the nested operations within a turn (llm calls, tools, subagents, injected context). Span links capture dataflow between spans, including cross-turn compaction seams.
Internally the deriver content-addresses raw turns with a merkle structure to deduplicate re-sent history and preserve provenance. That layer is an implementation detail of the deriver — it is not a browsable surface, and the API exposes only sessions, traces, and spans.