- git rev-list --count origin/main in the ai-interviewer repo
- packages/*/package.json on origin/main
- packages/ai/src/prompts/*.v1.ts on origin/main
- scripts/check-prompt-evals.ts, run by pnpm verify in .github/workflows/ci.yml
The problem
Interview practice is usually a friend who is too kind or a coach who costs too much. A job seeker needs honest, specific feedback tied to the role they are actually applying for, and a place to practise saying the answers out loud.
Kaivi is a product for that gap, aimed at job seekers in the Philippines. I am building it alone, so every decision below had to be cheap to run and cheap to change.
What I built
A mobile-first web app with a separate voice worker. You upload a resume and a job description. Kaivi plans the interview from both, runs it turn by turn by voice or text, and finishes with a report on what to fix.
- Brief: reads the resume and job description and plans the interview.
- Drill coach: asks, follows up and adapts to the level you picked.
- Evidence: every point in the report cites what the candidate actually said.
The monorepo has the Next 16 web app and a Hono voice worker, plus packages for the AI layer, core logic, the Drizzle database layer, the role library, shared UI and the evals.
Architecture
AI design
- Models by role: the report, the live interviewer and the cheap utility calls use different Claude models, chosen by cost per interview and recorded in ADRs 0011 and 0019.
- Prompt caching: stable prefix first, volatile content last, so a long interview does not pay for its prefix again on every turn.
- Untrusted input: resumes and answers go into delimited blocks, with any tag that could close or fake a block broken first. They never enter the system prompt.
- Usage and pricing: token use is metered per call, priced from a table in
packages/ai/src/pricing.ts, and unit tested. - Evals: a dedicated evals package holds a golden set of briefs and a judge. A repo check fails when a prompt file changes without a recorded eval outcome.
- Fakes for everything: every model call has a deterministic fake, so the unit suite never touches the network.
How it was built
With a Claude Code workflow I designed. A /feature command chains brainstorming, a written plan, parallel implementers on separate files, a fresh-context reviewer and a verify step. Hooks block destructive commands and run the verify script when a session stops.
The decisions live in 22 numbered ADRs, each with the evidence it was based on, so the reason for a choice outlasts my memory of it.
Tradeoffs
- Turn-based voice, not live conversation. ADR 0010 dropped a streaming voice cascade after the cost model showed it missing the per-interview cap. You tap to answer, audio uploads in chunks, and the next line is generated while you wait. A hands-free conversation mode is deferred. Speech to text, the live model and text to speech sit behind interfaces, so adding it later is a transport swap.
- One eval is still a template. The Sonnet versus Opus comparison for the report (
docs/evals/2026-11-report-sonnet-vs-opus.md) recordsOutcome: pending. The gate exists and blocks merges, but that specific number is not in yet. - Auth hardening is deferred. Google sign-in, MFA for admin and account deletion are listed in ADR 0004 as a later spec.