docs/architecture-decisions.md
Rama main · réplica de lectura del repositorio original.
# Architecture decisions
Small, permanent records of decisions that are easy to re-litigate by accident.
Each entry says what was decided and which deployment constraint forced it.
## Why no WebSockets
**Status:** decided — 4.0, for the Render free web service.
**Decision.** Archiver will not open WebSocket connections between the browser
and the app server. If streaming is ever needed, the answer is Server-Sent
Events over the existing HTTP handler: one-directional, closed on completion.
**Reasoning.** The deployment target decides this, not taste:
1. **There is nothing to push.** Inference runs entirely client-side
(`web-llm` / `wllama`, vendored). Tokens are generated in the visitor's own
browser, so there is no server-side token stream a socket could carry. The
server's jobs are serving static bytes and performing request/response web
fetches for search and retrieval — both naturally HTTP.
2. **A socket holds the instance awake.** The free tier spins down after ~15
minutes idle; spin-down is the free tier's only cost control. An open
connection counts as activity, so a persistent socket quietly converts the
service from "sleeps when unused" to "always on", and an idle-but-connected
tab is exactly the workload the tier is not priced for.
3. **Every frame is billed in the priciest bandwidth meter.** Render meters
*WebSocket Responses*, *Service-Initiated*, and *Service-Initiated (Private
Link)* against one shared monthly allowance. WebSocket traffic lands in the
most expensive category, and heartbeat frames are pure meter burn — bytes
with no information in them. Exceeding the allowance throttles the service;
there is no overage billing to absorb the mistake.
4. **Reconnect storms on wake.** After a spin-down every parked client
reconnects at once when the instance wakes — a thundering herd against a
shared CPU during the slowest part of the cold start, and each reconnect is
another metered handshake. A request/response client instead simply waits
for the one slow response, which is what the "waking the server" status in
the UI already explains.
**Consequences.** No persistent connection state on the server (nothing to lose
on an ephemeral disk), no keep-alive pinger to keep the instance hot (that
would defeat spin-down *and* burn the same bandwidth meter), and any future
streaming feature must fit inside a bounded HTTP response that closes when it
ends. SSE satisfies that; WebSockets never can.