Table 2, all projects

Priyanshu OS / AI Voice Portfolio

A portfolio you talk to. An AI agent grounded in structured profile data that can answer, navigate and reason about fit.

Case study · Working MVP: deployed on its own domain, and you are reading it

Explore PriyanshuArchitectureLive demo GitHub
The portfolio homepage: a career plotted as Fig. 1 on quadrille paper, with an ask input drawn as an annotation on the plot
Fig. 1 · The career as a plotted function, with the ask input drawn as an annotation on it

The idea

Replace the static resume with an agent that answers questions about Priyanshu's background, navigates this site, surfaces relevant work, and analyses a pasted job description against his actual experience.

Why I wanted to build it

Resumes flatten a non-linear path into bullet points. Recruiters and hiring managers have specific questions, and a static page cannot answer them or admit what it does not know.

Product philosophy

Grounded, not generative
Every answer derives from /data/*.json. The agent is instructed to say when something is not in the data rather than fill the gap.
Gaps are a feature
The relevance mode reports missing experience explicitly. A match list without gaps is not trustworthy.
Actions over prose
The agent can navigate the page and highlight dimensions through tool calls, so an answer is also a demonstration.

Fig. 2Activate, follow, pause, save, leave, come back.

  1. 1
    Land

    Fig. 1 plots the career as a curve. The ask input is drawn on the plot as an annotation, not bolted on as a chat bubble.

  2. 2
    Ask

    The question goes to a system prompt built from the same six JSON files the page itself renders from.

  3. 3
    Answer

    The reply arrives in the margin, in third person, and says plainly when something is not on record.

  4. 4
    Act

    Validated actions move the page: scroll to a section, hatch the relevant tracks, open a case study.

  5. 5
    Check fit

    Paste a job description. Every claimed match must cite a dataset id, and an uncited one is dropped rather than shown.

  6. 6
    Read the gaps

    Missing experience comes back as output, next to the matches, instead of being left out.

The marginalia panel open beside the figure, answering whether he is primarily technical or business
Fig. 3 · The agent answers in the margin, grounded in the same JSON the page renders from
The job-description fit check showing an honest verdict and strong matches, each citing the dataset row it came from
Fig. 4 · A pasted job description, an honest verdict, and matches that cite their source row

Table 1Decisions, and what they cost.

DecisionTradeoff accepted
The data is the source of truth, not the componentsEvery section and every AI answer renders from six JSON files, so the page and the agent can never disagree. A copy fix needs a deploy, which is the price of there being one version of the truth.
Grounded, not generativeThe prompt forbids invention and requires "that is not something I have on record". It refuses questions a looser agent would happily answer, which is the point: a portfolio agent that guesses is worse than no agent.
Gaps are output, not omissionThe fit check returns missing experience alongside the matches, so the site argues against its own candidate in writing. A fit report with no gaps is not trustworthy.
One model provider was a single point of failureOpenRouter withdrew the free model this site ran on and the AI went down until it was noticed. Five free models failed five different ways in one afternoon: withdrawn, rate limited, upstream overloaded, restricted to other clients, and answering 200 then 503 a minute later. The fix was a chain across four providers with a circuit breaker rather than a better single provider. Writing it surfaced a subtler bug: the first version treated any 4xx as fatal, which is wrong, because Google answers 400 to a bad API key and Groq answers 413 to a prompt Cerebras accepts. A sabotage test caught it before it shipped.
The prompt is sized to the providerGroq's free tier allows 8,000 tokens a minute against a full prompt of about 10,000, so it is sent a lean knowledge base measured at 5,993 tokens against its own tokenizer. A fallback answers with less depth on project internals. A thinner answer beats no answer.
The model routes are rate limitedTwo public endpoints on a free-tier key are an open proxy for anyone who finds them, so both are capped per visitor and per day. A genuinely curious visitor can hit a wall, and the figures and notes all still work without the AI.
  1. q1Still open: Rate-limit counters live in the running instance, so the daily cap is a brake rather than a guarantee if the platform runs several. Is a shared store worth the dependency at this size?
  2. q2Still open: There is no conversation memory. Should a visitor be able to pick up where they left off, or is a stateless agent the more honest demonstration?
  3. q3Still open: Voice quality is whatever the browser ships, which varies a lot by platform.
  4. q4Still open: A free provider can withdraw a model at any time. The chain turns that into a slower answer instead of an outage, but it does not remove it.

Fig. 5Technical architecture

Stack
  • Next.js 15 (App Router) and TypeScript, Tailwind v4, self-hosted OFL fonts
  • Four OpenAI-compatible providers tried in order (Cerebras, Gemini, OpenRouter, Groq), with a five-minute circuit breaker and a per-attempt timeout
  • Per-provider prompt sizing: a provider with a smaller token allowance is sent a smaller knowledge base
  • Structured JSON output for UI actions, validated against known section, dimension and project ids before anything runs
  • In-memory rate limiting on both model routes, with the daily provider budget split between them
  • Web Speech API for voice in and out, d3-force for the knowledge graph, Motion for one authored motion grammar

Where AI fits

Central. This is a tool-using agent with grounding, structured outputs and explicit gap reporting. Its limits are also documented: no persistent memory, browser-native voice quality, and dependence on free-tier model rate limits.

Works today

  • ✓Grounded chat with structured UI actions, validated against known ids before the client runs them
  • ✓Job-description fit check with cited matches, transferable experience and explicit gaps
  • ✓Four-provider failover chain with a five-minute circuit breaker and per-attempt timeouts
  • ✓Per-provider knowledge-base tiers, so a small token budget gets a prompt built to fit it
  • ✓An offline provider benchmark that races the real dataset and reports latency, success rate and JSON compliance
  • ✓Rate limiting on both model routes, with the daily budget split between them
  • ✓Two renditions of one world, keyboard access to every figure, and a reduced-motion path

Roadmap (planned, not claimed)

  • ○Hybrid retrieval over the same JSON, so a question retrieves what it needs instead of sending the whole dataset every time
  • ○A labelled question set and a scoring harness, so retrieval quality is measured rather than asserted
  • ○Per-project OG images for the case studies

What it says, without saying it

Grounding and refusal: answers derive from a dataset, and the agent says when the dataset does not cover the question Structured outputs treated as untrusted: every UI action is validated against known ids before it runs, and the job description is data, never instructions Operating on infrastructure you do not control: failover, circuit breaking, prompt budgets per provider, and a benchmark to decide the order Honesty as a product decision: statuses that do not overclaim, and a fit report that reports its own gaps

Related tracks: Builder, Agents, Product · see Fig. 2