Xilin Zhu
ContactAvailable for work
← AI Digital Twin

AI Digital Twin

Architecture

A stateless FastAPI backend and a native Web Component frontend

The backend keeps no sessions — the browser carries recent history. Private source material and deployable material are physically separated. Production boundaries are locked in code, not in configuration.

Call path

Personal site / Web Component
             │ JSON or SSE over HTTPS

FastAPI: validation → persona assembly → chat service → provider adapter
             │                                              │
             │                                              ├── hosted API (current production default)
             │                                              ├── local llama.cpp (development)
             │                                              └── self-hosted model host over HTTPS + API key (ready)
             └── read-only, owner-approved persona files

The provider adapter is the one place in this architecture reserved for changing models later. All three backends sit behind the same interface, so switching never touches the frontend contract.

Deployment topology

The static frontend is served from my own domain; FastAPI runs as a single instance on a free container platform. The image is built from the repository root so the container gets both the backend runtime code and the read-only persona directory that lives above it.

Key architectural decisions

The backend is stateless. Conversation history travels with the browser. The cost is a slightly larger request; the return is a service that can restart, scale, or move at any moment — and no question of whose chat log is sitting on the server.

Private source material is physically separated from the deployable persona. Two directories; only the second enters the image.

Development may start without an API key; the production readiness check may not. Local work should not be blocked by secrets, but production refuses to start if anything is missing.

The frontend uses Shadow DOM. The component can be embedded anywhere without leaking styles into the host page, and the host’s CSS cannot reach into it.

Production CORS is not a configurable list — it is two origins locked in code. One extra, one missing, or a wildcard, and the service refuses to boot.

The deployment blueprint declares the free plan, a single instance, and disabled auto-deploy explicitly, and creates no database or disk. The cost boundary lives in version control rather than in memory.

The container is built from a lockfile, runs as a non-root user, and starts the process with exec so the platform’s SIGTERM actually reaches it instead of being swallowed by a shell.

The self-hosted model host does not run on the free platform. It runs separately, with the model port visible only inside the container network and a reverse proxy exposing nothing over HTTPS but a health check and a Bearer-protected inference endpoint. Until that host is verified, production stays pointed at the hosted API — no opportunity to switch live traffic by accident.