Architecture overview
Claimpilot Chat is one Cloudflare Worker, one React application and one shared contract. The Worker serves the built front end, the JSON and SSE API, and the voice WebSocket relay. The browser renders the claim screens and the chat. A shared package defines the document and the protocol both sides speak.
Principles
The claim is the unit of everything. The assistant answers about one claim: the one on screen. The browser sends that document with every turn, so the assistant cannot see any other. Every capability, tool and prompt is written against the Claim contract.
Read through tools, not through the prompt. The system prompt carries a compact brief. Everything specific (wording, dates, amounts, who said what) comes through tool calls, which keeps the context small and every answer traceable. The UI shows which lookups produced each answer.
Do arithmetic in code. Where a judgement depends on counting, dates or money (was the policy in force, which payments are duplicates, how many days since first contact), a deterministic tool computes it and the model reasons over the result.
One seam per moving part. Each concern has one home and one interface.
| Concern | Its one home | The seam |
|---|---|---|
| The claim's shape | packages/shared/src/claim.ts | the Claim zod schema |
| What the chat sends and receives | packages/shared/src/chat.ts | ChatRequest, ChatEvent |
| Prompt text | api/src/prompts/catalogue.ts | resolvePrompt(id, overrides) |
| A capability | one object in api/src/capabilities/<group>.ts | Capability |
| The model | api/src/ai/ | AiProvider |
| The claims store | api/src/claims/ | ClaimRepository |
| The feedback store | api/src/feedback/ | FeedbackRepository |
| What differs between UK and US | frontend/src/region/regions.ts | useRegion() |
| Every number on a claim screen | frontend/src/claimpilot/claimView.ts | deriveClaimView(claim) |
Stateless where it can be. The Worker keeps no transcript and no session. Prompt Lab edits live in the browser and travel with each request. A turn id is minted and forgotten. The only things the Worker writes are claims (by the CLI) and feedback votes.
The request pipeline
Every /api/* request passes the same middleware in api/src/index.ts, in this order:
cors(access.ts). Same-origin requests pass. A cross-origin request from an origin not inALLOWED_ORIGINSgets a403before any work is done. Preflights from allowed origins get a204with the CORS headers.- Services. The claims repository, AI provider and feedback repository are built from
envfor this request and set onc.var. A misconfigured store throws here, whichonErrorturns into a500naming the problem. requireEmbedKeyon/api/chat,/api/voiceand/api/feedbackonly. Unset key means open.- The route.
onError. AZodErrorbecomes a400; anything else a500, with the message.notFound. An unknown/api/*path is a JSON404. Anything else goes to the static assets, which fall back toindex.htmlfor client-side routes.
assertCatalogueMatches() runs when the module loads: the Worker refuses to start if the server's capability registry and the shared catalogue disagree.
Where to go next
| To understand | Read |
|---|---|
| The document everything is built on | The Claim contract |
| What happens in one chat turn | The chat pipeline |
| How capabilities and tools are put together | Capabilities and tools |
| The model port, Gemini, the gateway, the test model | AI providers |
| D1, Neon and "no store at all" | Data stores |
| The Live audio relay | Voice |
| The React side | Front end |
| Safety, access and data protection | Security |
The stack
| Layer | Choice |
|---|---|
| Front end | Vite 8, React 19, TypeScript, Tailwind v4, react-router, react-markdown, lucide icons |
| Back end | Cloudflare Worker running Hono; Workers Static Assets for the SPA |
| Data | One JSON document per claim; Cloudflare D1 (SQLite) by default, Neon Postgres as an alternative |
| Models | Gemini Flash Lite for chat and generation, Gemini Live for voice, through @google/genai, proxied by Cloudflare AI Gateway |
| Contract | zod 4 schemas in packages/shared |
| Tests | Vitest 5 (Node and jsdom), Testing Library, Playwright |