agentic-ai intent-routing catalog-in-context rag chat-navigation

"Agentic AI" usually means a language model that acts: it picks tools, calls APIs, loops until a goal is met, and sometimes plans before it executes. ReAct-style agents, function calling, planner–executor graphs — that family of designs.

This site's chat feels agentic to visitors. It can open the contact form, jump to /projects?q=c%23, rank skills, answer "which company did you stay at longest?", and land you on a project detail page with sources. Under the hood, though, the public chat path has no LLM tool registry and no multi-step agent loop.

That was intentional. This article is about what "agentic" means here, why I rejected a full agent for recruiter chat, and what I shipped instead.

What people mean by agentic

On a spectrum:

Style Who decides the next step? Typical risk
Pure RAG Always retrieve → generate Wrong tool for aggregations
Deterministic routing + RAG Rules / classifiers before the LLM Rule treadmill if overused
Catalog-in-context + RAG LLM reasons over a full fact table you provided Token cost (fine at CV scale)
Tool-calling agent LLM chooses tools and arguments in a loop Latency, hallucinated args, hard tests

I wanted the UX of the right-hand side (the assistant does the useful thing) with the control of the left-hand side (predictable, grounded, testable).

Why I did not ship a tool-calling agent

The motivating failure was simple:

"Which company have you worked for the longest?" → "I don't have information about that."

The data existed. Hybrid RAG still failed, for two structural reasons:

  1. Early embedding text omitted date ranges, so even perfect retrieval lacked the dates.
  2. Superlatives need all items in a category and a comparison — top-k semantic search returns "similar documents," not "the longest tenure."

The obvious "agentic" fix is tools: list experiences, rank by duration, count projects by technology. I considered it and rejected it for this product:

  • Latency — each tool round trip is another model call before the visitor sees an answer.
  • Tool-arg hallucination — agents invent filter values; recruiters notice wrong employers faster than wrong prose.
  • Testability — non-deterministic tool traces fight the grounding contract I already enforce on RAG.
  • Scale — the entire structured CV is a few kilobytes. The classic agent justification ("corpus too big for context") does not apply.

So the decision is blunt: prefer structured-data-in-context over a full agent for recruiter chat.

flowchart TB
  subgraph agentLoop [Tool-calling agent]
    Q1[User question] --> Plan[LLM plans]
    Plan --> Tool[Call tool]
    Tool --> Observe[Observe result]
    Observe --> Plan
    Plan --> Ans1[Answer]
  end

  subgraph catalogPath [This site]
    Q2[User question] --> Route[Deterministic pre-RAG route]
    Route -->|browse or command| Nav[Navigate or action]
    Route -->|analytical or descriptive| Bundle[Catalog plus hybrid hits]
    Bundle --> OneShot[Single grounded LLM call]
    OneShot --> Ans2[Answer plus sources]
  end

What "agentic" means on this site

Three cooperating layers. Only the last one talks to the chat LLM.

1. Deterministic intent routing (before RAG)

The chat service does not start with an embedding call. It runs a short decision tree:

if message matches an app command
  → navigate or open a UI action (login, contact, listings, filtered browse)
else if message asks for skills ranking
  → answer from published skill metadata (no LLM)
else
  → fall through to RAG (hybrid retrieve + catalog + Gemini)

App-command matching maps phrases to concrete outcomes:

  • Open login / contact / listings
  • Filtered browse (/projects?q=…)
  • Content-type narrowing hints for RAG
  • Post-answer navigation to a detail route when the message and sources clearly name one item

Skills ranking stays as a deterministic path for "strongest skills"-style questions: sort published skills by proficiency and years, no LLM required.

This is agentic in the UX sense — the assistant does something — but the decision is code, not a tool call the model invented.

flowchart TD
  Msg[User message] --> Trim[Trim and load server session history]
  Trim --> AppCmd{App command?}
  AppCmd -->|yes| Action[Navigate or UI action]
  AppCmd -->|no| Skills{Skills ranking phrase?}
  Skills -->|yes| Rank[Deterministic skills answer]
  Skills -->|no| RAG[Hybrid retrieve]
  RAG --> Catalog[Attach full published catalog]
  Catalog --> LLM[Gemini grounded completion]
  LLM --> Guard[Grounding validation and sources]
  Guard --> Out[Answer plus optional navigate]
  Action --> Out
  Rank --> Out

2. Catalog-in-context (instead of per-question tools)

Every RAG call loads the complete published catalog as compact fact lines — titles, roles, employers, technologies, dates, precomputed approximate durations, proficiency, years — and prepends it to the system message alongside the usual retrieved prose.

There is no descriptive/analytical classifier. The model always receives both; it uses what is relevant. Classification would have been another treadmill (keyword rules) or another round trip (LLM router). At this corpus size, tokens are cheap and correctness is expensive.

Source chips still work: retrieval hits plus catalog items whose title or employer is named in the answer (capped). Grounding still forbids inventing facts — it does not forbid counting or ranking the facts you were given. The system prompt says that explicitly.

One operational detail that mattered: the first catalog load included embedding vectors on every item (~395 KB of mostly dead weight). Projecting embeddings out of the catalog query dropped that to ~23 KB. Same idea, usable latency.

3. Navigation-as-answer (SSE side effects)

Sometimes the right answer is a page, not a paragraph. The chat API can emit:

  • navigate — client routes to a listing or detail URL
  • action — open a UI surface (for example contact)

Those events are produced by the routing layer (or post-RAG content navigation), not by the model emitting a made-up link. The prompt even tells the model not to invent portfolio URLs; the client linkifies known entity names from the catalog.

That split keeps SEO pages authoritative and chat complementary: when navigation fires, the chat can collapse and the public page carries the story.

Where a little agentic work does live

I still use LLMs with structured outputs in admin and PDF paths — extraction/synthesis while authoring, summarization of long project narratives for the portfolio PDF. Those are privileged, offline-ish workflows. They are not the public recruiter chat loop, and they do not get to invent CV facts on the live site.

Public chat stays boring on purpose: route → retrieve → reason once → gate → stream.

Tradeoffs I accepted

Benefit Cost
Deterministic browse/login/contact Growing phrase rules for edge cases
Analytical answers without tools Catalog tokens on every RAG call
Testable grounding More post-processing than a raw LLM demo
Fast single-shot answers No open-ended multi-tool workflows

The longest ongoing maintenance cost has been routing conflicts: analytical questions that look like browse intents ("show me recent projects" vs "what is your most recent project?"). That fight is product work, not missing agent machinery — and it is the subject of the lessons-learned article next.

Takeaway

Agentic UX does not require an agent runtime. For a bounded, trusted corpus, deterministic routing + full catalog in context + grounded single-shot generation beats a tool loop on latency, honesty, and testability.

If the corpus ever outgrew the context window, or chat needed side-effecting tools (email send, calendar book), I would revisit a real agent. Until then, the "agent" is the architecture — not the model calling itself.

Updated Aug 12, 2026