← Back to portfolio

Featured project

Agentic chat with tool use + evals

At a glance: Structured tool use over real data, with evals so regressions show up as failing rows—not just a prettier UI.

Streaming chat where Claude calls seven structured resume tools (get_role, search_resume, list_projects_by_skill, get_metrics, list_recent_shipped, get_narrative, get_faq). Tool calls show up in the transcript. Evals hit the same HTTP endpoint; results power the pass/fail table on the project page.

How it works

The chat backend runs a bounded tool-use loop instead of one-shot prompting. Each round is a streamed Anthropic request; the server intercepts tool_use blocks, runs the handler, appends the tool_result, and continues until the model stops asking for tools or we hit the round cap.

Optional Reflection:PlannerEnabled in API config appends a short planning instruction to the system prompt (second, uncached block when prompt caching is on) so the model states which tools it intends to use before the first tool call. Enable for demos; defaults off in production configs.

Estimated token usage and cost for the demo update live under the chat in two summary cards (session total + latest reply).

  1. 1

    Conversation + tools out

    User message and seven tool schemas (get_role, search_resume, list_projects_by_skill, get_metrics, list_recent_shipped, get_narrative, get_faq) are sent to the Anthropic Messages API with streaming enabled.

  2. 2

    Stream events out

    Text deltas are forwarded to the browser as NDJSON. tool_use blocks accumulate partial JSON until they close, then we emit a tool_call event.

  3. 3

    Run handler against resume data

    ResumeTools resolves the call against the structured Data/resume.json. The result becomes a tool_result event in the same stream.

  4. 4

    Loop until done

    If the round ends with stop_reason='tool_use', the conversation gets the assistant's tool_use blocks + a user tool_result, and a new round begins. Otherwise the final text answer streams and we emit done.

.NETASP.NET CoreAnthropic Messages APIServer-Sent EventsNDJSONNext.js 16React 19

Try it

Use a starter prompt if you want a guaranteed tool call, then try your own questions. Expand a pill to see the arguments and JSON returned.

Ask about my experience, projects, or what kind of role I'm looking for next. Answers are grounded in structured resume data — not live typing.

How it works under the hood7 tools

The model calls structured resume tools before answering. Expand any tool call in the thread to see inputs and JSON returned.

get_rolesearch_resumelist_projects_by_skillget_metricslist_recent_shippedget_narrativeget_faq

Starter prompts — tap to send.

Session total

Totals appear after each reply.

Cumulative for this chat · clears when you tap Clear

Latest reply

Send a message to see this reply.

One assistant response · resets on your next send

Rough estimates from published token rates — not billing. Input includes prompt cache where applicable.

Evals

Every row is a deterministic check against the same POST /internal/chat-evals stream as the live chat (usage on, daily budget off). Expand a row to see the tool calls, criteria breakdown, and a transcript preview of the assistant’s actual reply.

Last run · Jul 15, 2026, 4:57 PM · API http://localhost:5063 · 11/11 green · 0 known-fail suite

CaseStatusCriteriaNotes
role-lookup-forvisPass2/2 checks
Details

Question: What did you do at Forvis Mazars? Summarize your role in one short paragraph.

Tool calls

  • get_role

Criteria

  • Pass expected_tool_calls · get_roleShould load role data from structured resume
  • Pass must_contain · ForvisAnswer should name the employer

Assistant transcript (truncated)

Let me pull that from my resume data.At Forvis Mazars I've been a Full Stack Software Engineer since June 2023, leading end-to-end delivery of production web features across the full stack — React, TypeScript, Next.js on the front end and ASP.NET Core with SQL-backed services on the back end, all deployed on Azure. I've established reusable component and API integration patterns adopted across multiple engineering teams, built scalable front-end applications that simplify complex business workflows, and owned production quality through monitoring, incident response, and continuous performance improvements. One highlight I'm particularly proud of: I architected and shipped a real-time collaborative Planning Poker platform from the ground up, using WebSockets and Jira integration, which beca…
recent-projectsPass2/2 checks
Details

Question: What are my three most recent shipped projects? List them briefly.

Tool calls

  • list_recent_shipped

Criteria

  • Pass expected_tool_calls · list_recent_shippedMust call the shipped-projects tool
  • Pass must_contain · Planning PokerPlanning Poker is one of the three most recently shipped projects

Assistant transcript (truncated)

Here are my three most recently shipped projects:

1. **Zach.dev Portfolio Assistant** (2026) — The agentic chat you're using right now. Built with a Next.js frontend, ASP.NET Core streaming backend, and Anthropic integration, with request validation and API abuse safeguards. Source is public on GitHub.

2. **Portfolio MCP Resume Server** (2026) — A stdio Model Context Protocol server that exposes the same seven resume tools as this chat for Claude Desktop and other MCP clients. Keeps the tool contracts in sync with the chat API at the protocol layer rather than a bespoke HTTP integration.

3. **Planning Poker** (2024) — A real-time estimation platform I built internally at Forvis Mazars, integrated with Jira for loading stories and writing estimates back after rounds. WebSocket-powered, a…
skill-search-websocketPass2/2 checks
Details

Question: Which projects on my resume used WebSocket?

Tool calls

  • list_projects_by_skill

Criteria

  • Pass expected_tool_calls · list_projects_by_skillSkill-to-project mapping tool
  • Pass must_contain · Planning PokerPlanning Poker is the WebSocket-heavy shipped project

Assistant transcript (truncated)

The one project in my resume data that used WebSocket is **Planning Poker** — a real-time estimation platform I built for distributed delivery teams at Forvis Mazars in 2024. Here's the quick rundown:

- **What it does:** Teams join sessions, load Jira stories directly into the tool, vote on estimates in real time, and the results get written back to Jira automatically after each round.
- **Why WebSocket:** Real-time synchronization across distributed participants was the whole point — polling would've been too sluggish and clunky for estimation sessions.
- **Outcome:** It was adopted by every software team across all verticals at Forvis Mazars.
- **Stack:** React, TypeScript, .NET / ASP.NET Core, WebSocket, Jira API, Azure, SQL Server.

You can read the full case study at https://zachsyke…
metric-calathea-sitesPass2/2 checks
Details

Question: How many client sites has Calathea shipped end-to-end according to your resume metrics? Just answer with the number.

Tool calls

  • get_metrics

Criteria

  • Pass expected_tool_calls · get_metricsShould load structured metrics
  • Pass must_contain · 5Resume metric calathea-sites-shipped is 5

Assistant transcript (truncated)

5.
narrative-career-changePass2/2 checks
Details

Question: Why did you switch from kitchens to software engineering? Keep it short.

Tool calls

  • get_narrative

Criteria

  • Pass expected_tool_calls · get_narrativeCareer-change story lives in narrative tool
  • Pass must_contain · kitchenNarrative references prior kitchen career

Assistant transcript (truncated)

I wanted new challenges and the kitchen lifestyle had run its course — the hours, the grind, all of it. I knew I could handle something more mentally demanding, and engineering hit that sweet spot of creativity and structure I was looking for.

To make the jump, I quit my job, sold my car to cover the income gap, and went full-time through General Assembly's bootcamp. The self-directed hours outside of class — docs, tutorials, side projects — are what really made it stick.
faq-mcp-serverPass2/2 checks
Details

Question: What is the MCP resume server on your portfolio?

Tool calls

  • get_faq

Criteria

  • Pass expected_tool_calls · get_faqFAQ entry explains MCP server
  • Pass must_contain · Model Context ProtocolFAQ answer names the protocol

Assistant transcript (truncated)

Great question — I built this as a companion to the portfolio chat itself. It's a Node.js stdio server built on the Model Context Protocol (MCP) SDK, and it exposes the **exact same seven resume-backed tools** that power this chat. That means Claude Desktop (or any other MCP-compatible host) can query my resume data directly — no second HTTP API to maintain, since it reads from the same `resume.json` file.

A few details I'm particularly happy with:

- **Schema consistency**: The input schemas live in `mcp/schemas` and are checked in CI against C# `ResumeToolInputSchemas` constants, so the wire contract between the MCP server and the rest of the stack can't drift quietly.
- **Same toolset, different surface**: Whether you're talking to this browser chat or connecting via Claude Desktop, yo…
freeform-search-restaurantPass2/2 checks
Details

Question: Search my resume for anything about restaurants or Asheville.

Tool calls

  • search_resume
  • search_resume
  • get_role

Criteria

  • Pass expected_tool_calls · search_resumeBroad phrasing should map to substring search
  • Pass must_contain · AshevilleKitchen role org is in Asheville

Assistant transcript (truncated)

The word "restaurant" doesn't surface any direct hits in my structured resume data, but "Asheville" does — it matches my role at **Asheville Pizza and Brewing Company**. Let me pull the details on that.Here's what's documented: I spent about a decade at **Asheville Pizza and Brewing Company** (2012–2022), finishing out as **Kitchen Manager**. The role involved leading kitchen operations for a team of 40+ people — hiring, onboarding, training, scheduling, and performance management. It's a big part of my background and honestly where a lot of the leadership instincts I bring to engineering were built.

If you're curious how that experience connects to my software career, I'd be happy to pull up my career-change narrative — just ask!
contact-must-be-canonicalPass3/3 checks
Details

Question: What is the best email to reach you at for a hiring conversation?

Tool calls

(none — model answered without calling a resume tool)

Criteria

  • Pass must_contain · zsykes21@gmail.comCanonical email from resume / system prompt
  • Pass must_not_contain · zachsykes@gmailShould not invent a name-mangled alternate Gmail
  • Pass must_not_contain · noreply@Should not substitute a placeholder address

Assistant transcript (truncated)

The best email to reach me at is **zsykes21@gmail.com** — feel free to drop me a line there for any hiring conversation.

If you'd also like to connect on LinkedIn or check out my work first, you can find me at:
- 🔗 LinkedIn: https://www.linkedin.com/in/zach-sykes/
- 💻 Portfolio: https://www.zachsykes.dev/

I'd love to chat!
out-of-scope-refusalPass2/2 checks
Details

Question: In your opinion, what is the best React state management library in 2026 and why? Give a ranked list with pros and cons.

Tool calls

(none — model answered without calling a resume tool)

Criteria

  • Pass must_not_contain · ReduxOut-of-scope ask should refuse generic framework advice, not teach Redux
  • Pass must_not_contain · ZustandSame for other popular library names as generic recommendations

Assistant transcript (truncated)

That one's outside what this assistant covers — I only field questions about Zach's documented work history, projects, and skills, not general engineering opinions or industry comparisons.

If you're curious what state management tools show up in **my own projects**, I'd be happy to pull that from the resume data. Or if you want to know what kind of roles I'm looking for or what I've shipped recently, just ask!
multi-tool-blendPass2/2 checks
Details

Question: What did you ship in 2024 while at Forvis Mazars? Name the project and one concrete outcome.

Tool calls

  • get_role
  • list_recent_shipped

Criteria

  • Pass expected_tool_calls · get_role, list_recent_shippedNeeds role context plus shipped work for the year
  • Pass must_contain · 2024Answer should anchor on the year asked

Assistant transcript (truncated)

Let me pull that from my resume data.The standout project I shipped at Forvis Mazars in 2024 was **Planning Poker** — a real-time estimation platform I built from 0 to 1 for our distributed delivery teams.

**One concrete outcome:** it was adopted by every software team across all verticals as the standard tool for real-time distributed estimation sessions. It replaced ad-hoc manual coordination with a single, consistent internal workflow — complete with WebSocket infrastructure, a .NET backend, and direct Jira integration that loads stories into sessions and writes estimates back after each round completes.

You can read more about it at https://zachsykes.dev/projects/planning-poker. Happy to dig into any part of the tech or the build process if you're curious.
database-stackPass3/3 checks
Details

Question: What database technologies have you used? Mention SQL engines if relevant.

Tool calls

  • search_resume
  • search_resume
  • get_role
  • list_projects_by_skill

Criteria

  • Pass expected_tool_calls · search_resumeShould search structured resume/skills data for databases
  • Pass must_contain_any · PostgreSQL, SQL ServerAnswer should name PostgreSQL and/or SQL Server from resume data
  • Pass must_contain · SQLAnswer should reference SQL experience

Assistant transcript (truncated)

Let me search for that across my resume data.Here's a solid rundown of the database technologies I've worked with:

**SQL Engines**
- **SQL Server** — used at Forvis Mazars for production backend services, including on the Planning Poker platform I built from scratch. Also part of the Entity Framework Core setup there.
- **PostgreSQL** — part of my day-to-day work at Forvis Mazars, integrated with backend APIs and production services.

**ORM / Data Access**
- **Entity Framework Core** — my primary ORM layer at Forvis Mazars for interacting with SQL Server and PostgreSQL; I use it to design and query relational data models within ASP.NET Core services.

**General SQL**
- I write SQL directly as well — query authoring, schema thinking, and debugging data issues are regular parts of the job.
…

Design note

Why search_resume is substring matching, not embeddings

The natural-language search tool on the résumé — search_resume — is a case-insensitive substring match across structured fields. No vector store, no embedding API. For a single résumé-sized JSON file, embeddings mostly add latency, cost, and another dependency without changing the answer quality in a meaningful way.

The corpus is one résumé: a handful of roles, a handful of projects, a few FAQ entries, and a short career-change narrative. The total searchable text is well under 10 KB. At that size every query can scan the whole document quickly; a dedicated vector index would mostly be overhead, and an embedding call per question would not buy much relevance.

Query expansion and paraphrasing still happen in the model. The system prompt nudges Claude to translate vague questions into concrete search terms when needed—so if a recruiter asks where did Zach work before software, the model can search for kitchen or restaurant. The tool itself stays a fast substring pass over structured fields.

Substring matching also keeps citations honest. Each hit comes back as { kind, id, matchedFields } — not a similarity score and a chunk of text. The model can say it matched on the tech field of a specific role and the grounding stays tight. With cosine similarity that linkage gets fuzzy: the model gets a nearest-neighbor blob and has to guess what about it matched.

The tradeoff: substring matching is brittle to typos and won't catch unrelated synonyms — “PMP-style work” will never hit “project management”. For a one-résumé corpus that's a non-issue; for a 10,000-document knowledge base it would be the wrong call. Pick tooling that matches how much data you actually have.

Source

Where to find the main pieces:

Chat loop

  • api/Portfolio.Api/Services/AnthropicStreamService.cs the streaming tool-use loop.
  • api/Portfolio.Api/Services/ResumeTools.cs the seven tool handlers.
  • api/Portfolio.Api/Data/resume.json the structured resume the tools query.
  • web/components/ToolCallPill.tsx the inline tool-call UI.

Evals

  • api/Portfolio.Api/Controllers/ChatEvalsController.cs internal endpoint — same orchestration as live chat, with X-Eval-Key auth and a 1 MiB body cap.
  • evals/cases.json deterministic test cases (questions plus must_contain / must_contain_any / expected_tool_calls criteria).
  • evals/run.mjs NDJSON-aware runner — posts each case to the endpoint and writes results.json.
  • web/components/AgenticChatEvalsSection.tsx the table + per-case expandable details rendered on this page.
View on GitHub →