An agentic AI support-triage demo by Vertrez.
It watches an inbox of incoming support tickets, and for each one runs a multi-step pipeline that classifies the message, retrieves relevant help-doc passages, drafts a grounded reply, suggests an assignee team, finds similar tickets in the queue, and flags low-confidence cases for human review. A human always approves, edits, or rejects before anything ships — nothing is auto-sent.
It is built for SaaS support teams who feel the pinch between ticket volume and headcount, and want to see what agentic AI looks like wired into the real flow of work rather than a chat sidebar.
This is a demo / reference implementation, not a production product. It runs locally, with zero infrastructure, on a free LLM tier. It exists to show the architecture and behaviour of a triage agent end-to-end so you can judge the approach before commissioning a production build. See "Production note" below for what changes when this gets wired into your actual stack.
cp .env.example .env.local # then add your free Gemini API key
npm install
npm run seed # embeds the help docs locally (~10s)
npm run dev # http://localhost:3000Get a free Gemini API key at aistudio.google.com. The seed step computes embeddings entirely on-device via @huggingface/transformers — no embedding API is called.
incoming ticket
│
▼
┌──────────────┐
│ 1. Classify │ category · priority · sentiment · reasoning (low-temp JSON)
└──────┬───────┘
│ (if body > 1400 chars, summarise first)
▼
┌──────────────┐
│ 2. Retrieve │ embed ticket → top-K passages from help docs
└──────┬───────┘
│
▼
┌──────────────┐
│ 3. Draft │ reply grounded only in retrieved passages
└──────┬───────┘ (escalates honestly when docs don't cover it)
│
▼
┌──────────────┐
│ 4. Route │ suggest assignee team + tags
└──────┬───────┘
│
▼
┌──────────────┐
│ 5. Flag │ confidence band + SLA + similar tickets in queue
└──────┬───────┘
│
▼
┌────────────────────────┐
│ Human in the loop │ approve · edit · reject
└────────────────────────┘
The pipeline lives in lib/triage.ts and reads as cleanly as the diagram above — every step is one named function, all of them typed.
- Grounded answers only. The draft is generated against the retrieved doc passages. If the top retrieval similarity is below the threshold, the agent does not invent an answer — it writes an honest "we don't have this documented, escalating to a human" message instead.
- Never auto-sends. Every draft is gated behind Approve / Edit / Reject. There is no automatic delivery path.
- Human-in-the-loop by default. Low-confidence triage, missing context, and very low-info messages are explicitly flagged "Needs human review" and surfaced in the inbox.
- Deterministic classification. The classifier runs at low temperature with a strict JSON schema, so the same ticket triages the same way every time.
This is intentionally a scoped demo. The things this codebase does NOT do (and which production needs):
- No real inbox integration. Tickets are seeded from a JSON file. Production wires this to Gmail, Zendesk, Intercom, Front, etc.
- In-memory state only. Tickets, decisions, and document embeddings live in the Node process. Nothing is persisted across restarts of the dev server (the doc embeddings are cached to
data/seed-embeddings.jsonfor speed). - No auth, no multi-tenancy. Single-user, single-process. Production adds auth, RBAC, and workspace scoping.
- No analytics persistence. The summary strip is computed in memory from the current session.
- No automated sending. Approve/Edit/Reject update local state — they do not actually email anything.
- Single LLM call per pipeline step. No retries, no streaming. Adequate for demo; production wraps with retry and observability.
- Deterministic demo data. The 15 seeded tickets are crafted to exercise the different code paths (escalation, frustration, low-info, long thread, etc.).
app/ Next.js 15 app router (UI + API routes)
components/ Inbox, TicketDetail, TriagePanel, Badges, SummaryStrip, TopBar
lib/
config.ts central thresholds (confidence, SLA, retrieval-k, models)
embeddings.ts local on-device embeddings via @huggingface/transformers
chunker.ts paragraph-aware chunking
vectorStore.ts in-memory doc store with cosine search
ticketStore.ts in-memory ticket store
llm.ts provider-agnostic LLM module — see below
triage.ts the pipeline
scripts/seed.ts embeds help docs and writes data/seed-embeddings.json
data/seed-docs/ markdown help docs (Lumora demo)
data/seed-tickets.json the seeded inbox
types/ shared types
lib/llm.ts exposes typed functions (classifyTicket, draftReply, summarizeThread) and is the only place that talks to a generation model. The demo uses Google Gemini's free tier. For production, this file is swapped (or its body replaced) with the Anthropic Claude SDK — every caller stays unchanged because the function signatures are stable.
The production version of this agent connects to your real inbox (Gmail / Zendesk / Intercom / Front), persists tickets and decisions to a real datastore, runs generation on Claude with prompt caching and observability, adds auth, multi-tenancy, audit logging, and your brand voice. Built by Vertrez — vertrez.dev. If you want this wired into your actual support stack, say hi.
MIT.