The template's runtime architecture is intentionally small — one backend service, one frontend app, one tracing collector. The point of the scaffold is to exercise the harness, not to ship features.
┌────────────────────┐
│ Browser │
│ (React 19.2) │
└─────────┬──────────┘
│
│ http://localhost:5173
│ (Vite dev server, HMR)
▼
┌────────────────────┐
│ frontend/ │
│ Vite + React +TS │
│ • App.tsx │
│ • lib/api/ │
│ client.ts (SSE)│
└─────────┬──────────┘
│
│ /api/v1/* (proxied)
▼
┌──────────────────────────────────────────────────────────┐
│ src/api/ │
│ ┌──────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ main.py │ ─►│ routes.py │ │ sessions.py │ │
│ │ FastAPI │ │ /v1/health │ │ in-memory store │ │
│ │ lifespan │ │ /v1/echo │ │ │ │
│ └────┬─────┘ └─────────────┘ └──────────────────┘ │
│ │ │
│ │ setup_tracing → setup_logging → instrument_* │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ src/observability/ │ │
│ │ tracing.py · logging.py · spans.py │ │
│ └─────────────────────┬───────────────────────────────┘ │
└────────────────────────┼──────────────────────────────────┘
│ OTLP gRPC :4317
▼
┌─────────────────────┐
│ Jaeger all-in-one │ :16686 (UI)
│ │ :4317 (OTLP gRPC)
│ │ :4318 (OTLP HTTP)
└─────────────────────┘
| Slot | Today | Eventual |
|---|---|---|
src/agent/ |
Empty package | LLM tool-calling loop |
src/tools/ |
registry.py + echo_tool |
Real domain tools |
src/data/ |
Empty package | DB / file / API clients |
src/api/sessions.py |
In-memory dict | Redis or DB-backed store |
eval/golden_qa.json |
One echo case | 15-50 cases by category |
The layered import-linter contract lets each slot grow without coordination — adding src/data/db_client.py doesn't trigger any change in src/models/ or src/api/.
- Browser hits
GET /api/v1/health(or/echo?msg=...). - FastAPI routes to
src/api/routes.py:health/routes.py:echo. - The handler builds a typed response (
HealthResponse/EchoResponse— bothStrictModel). - OpenTelemetry's FastAPI instrumentation produces a span; OTLP exporter ships it to Jaeger.
- The structured logger writes one JSON record per request, correlated by
trace_id/span_id. - Response returns to the browser as JSON.
- Vite dev server serves
index.html→src/main.tsx→App.tsx. App.tsxrunsuseEffectonce, fetching/api/v1/healthvia the browser'sfetch(proxied through Vite to:8000in dev, served same-origin in a real deployment).- The component renders one of three states (
loading | ok | error) using semantic ARIA roles +data-testidhooks for the Vitest suite. - CSS variables in
src/styles/palette.cssdrive the visual;[data-theme='dark']flips palette tokens.
The typed SSE client at src/lib/api/client.ts is unused by the scaffold's hello page but ships ready: any backend that returns text/event-stream from a POST endpoint can be consumed via sendMessage<TEvent>(...).
- Persistence (real DB / queue / cache).
- Auth (OIDC, sessions, API keys).
- Rate limiting.
- Real production observability backend (Jaeger is the local dev choice; production typically uses a managed OTLP collector / vendor).
Each of these earns a section in this document when the project decides on its concrete shape.