Overview
This example is a fullstack chat agent that teaches you Trigger.dev by drawing. Instead of returning paragraphs, it composes interactive UI: ask “how does a fan-out with retries work?” and it renders an animated node-graph of the flow; ask it to teach you retries and you get a short explainer, a quiz, and a gotcha callout. Every turn ends with clickable next-step chips, so the learning keeps flowing. The agent writes a sentence or two, then calls arenderVisualization tool with a json-render spec that a Next.js chat UI renders live with React Flow and a kit of shadcn/ui teaching components. The model supplies data, not markup, so every card is a few tokens and always looks right. Every fact it states is grounded on the live docs through a documentation MCP server, so it doesn’t invent API surface.
Tech stack:
- Trigger.dev AI chat for the agent session, turn loop, streaming and resumability
- AI Prompts for a versioned system prompt with dashboard overrides and per-generation LLM observability
- AI SDK with Anthropic Claude for the model and tool calling, and
useChaton the frontend - Model Context Protocol (default: the hosted Context7 docs server) to ground answers on live documentation
- json-render with the
@json-render/shadcncomponent library for generative UI - React Flow with dagre for the signature interactive node-graphs
- Next.js chat app using
useTriggerChatTransport— the browser talks directly to Trigger.dev, no API route to maintain
- Generative UI: a
renderVisualizationtool takes a json-render spec built from a fixed kit of components:FlowGraph,HeroCard,Quiz,Callout,Compare,Steps,Glossary,StatCard,CodeCard,DiagramCard,PromptCard. Specs are validated against the component catalog and errors are returned to the model, so it corrects the spec and retries. - One shared catalog: the same module generates the system-prompt component reference and validates tool calls, so the prompt and the renderer can’t drift apart.
- Docs grounding via MCP: docs-server tools are merged into the agent each turn, so it looks up Trigger.dev APIs instead of answering from memory. Swap
DOCS_MCP_URLto point the demo at any other product’s docs MCP. - Untrusted-input quarantine: retrieved docs are the upstream prompt-injection vector, so each tool’s output is wrapped as data-not-instructions and flagged for injection markers before the model ever sees it.
- Versioned system prompt: defined with
prompts.define(), resolvable per-run and overridable from the dashboard without redeploying. Storing it viachat.prompt.set()wires upexperimental_telemetry, so every model call appears in the run trace with token, cost and latency metrics. - Per-turn model triage: most turns answer on a fast, low-cost model (Claude Haiku); only genuinely hard teaching turns escalate to a stronger model (Claude Sonnet), so the common case stays cheap and quick.
- A learning flywheel: a
suggestNexttool ends every turn with 2–4 next-step chips whose labels are sent verbatim as the next message, so the learner never has to invent the next question.
GitHub repo
View the Ask Trigger chat agent repo
Click here to view the full code for this project in our examples repository on GitHub. You can
fork it and use it as a starting point for your own project.
How it works
The agent
The agent is a singlechat.agent() call. Trigger.dev handles the chat session, turn loop, streaming and resumability. The system prompt is a versioned AI Prompt: the editable teaching guidance lives in the prompt template, while the json-render component reference is generated from the catalog at run time and injected as a template variable, so it always matches the deployed code. Setting the resolved prompt with chat.prompt.set() lets chat.toStreamTextOptions() supply the system text, model, config and telemetry:
src/trigger/trigger-chat-agent.ts
Grounding answers on live docs with MCP
Before stating any fact, the agent looks it up through a documentation MCP server rather than answering from memory. The client and its tool set are created lazily and cached at module scope, so a successful handshake happens once per run process and every later turn reuses the same tools. A failure isn’t cached, so the turn degrades gracefully and a later turn retries. The tools are declared on the agent config, so their calls survive Trigger.dev’s cross-turn history re-conversion:src/trigger/trigger-chat-agent.ts
toModelOutput (not execute) is the layer that decides what text the model sees, and it’s re-applied on cross-turn history re-conversion so the wrapping persists across turns:
src/trigger/trigger-chat-agent.ts
quarantineDocs (in src/lib/quarantine.ts) wraps the content in nonce-fenced “untrusted reference material” markers using the standard spotlighting defense, strips any fence markers the page itself contains, and flags common injection phrases so a review of the run trace surfaces them. Even if an injection slips through, validateSpec is the second net: the only thing the model can render is a spec built from the fixed component catalog, validated server-side before it reaches the client.
Generative UI with one shared catalog
A single module defines which components the model may use: layout and text primitives from@json-render/shadcn, plus a kit of custom teaching components. Every component is data-driven: the model fills fields and the component renders, so there’s no model-authored markup to sanitize. The same catalog produces the system-prompt reference and validates tool calls:
src/lib/catalog.ts
renderVisualization tool accepts a flat json-render spec. Validation failures go back to the model as tool output, and the full spec is read straight off the tool call’s input by the client, so the return value only needs a short ack:
src/trigger/trigger-chat-agent.ts
suggestNext, is the flywheel: called last on every turn with 2–4 chips (deeper, sideways, practice or topic). The client renders each chip as a button, and its label is sent verbatim as the next message when clicked, so the learning keeps flowing without the user having to think up the next question.
The Next.js chat UI
The frontend usesuseChat with useTriggerChatTransport: the browser subscribes to the session’s durable streams directly, authenticated by two small server actions, so there’s no API route to maintain. renderVisualization tool parts in the message stream render through json-render’s <Renderer> with the shadcn component registry:
src/components/chat.tsx
@json-render/shadcn components plus the custom teaching kit (src/lib/registry.tsx). The main visual is FlowGraph (src/components/flow-graph.tsx): a directed node-graph on React Flow and dagre, styled like the Trigger.dev dashboard, with status dots, dashed retry edges, an animated topological reveal, and an optional timed status sequence that plays a run’s state changes live.
Conversations are device-local: each has its own URL (/c/<id>) and its transcript is saved in the browser (IndexedDB), so a refresh restores the thread and the sidebar lists past chats. There’s no server database: Trigger.dev keeps the live conversation with the durable Session, so history never leaves the machine.
The session server actions
The browser talks to Trigger.dev directly; two server actions start the session and mint session-scoped tokens:src/app/actions.ts
Running it
The example needsANTHROPIC_API_KEY (and optionally DOCS_MCP_URL) set in the Trigger.dev dashboard on the Environment Variables page, and TRIGGER_PROJECT_REF plus TRIGGER_SECRET_KEY in the local .env for the Next.js server actions:
.env
Relevant code
- Agent + tools: src/trigger/trigger-chat-agent.ts: the
chat.agent()definition, the versioned prompt, the docs-MCP loading and quarantine wrapping,renderVisualizationandsuggestNext, and the per-turn model triage - Docs quarantine: src/lib/quarantine.ts: spotlighting wrappers, fence-marker stripping, and injection-phrase flagging for untrusted tool output
- Shared catalog: src/lib/catalog.ts: component definitions, prompt-reference generation, and spec validation
- Component registry: src/lib/registry.tsx: maps catalog components to their React implementations
- FlowGraph: src/components/flow-graph.tsx: the React Flow + dagre node-graph with the animated status sequence
- Chat UI: src/components/chat.tsx:
useChat+ the Trigger chat transport, message parts, and visualization rendering - Server actions: src/app/actions.ts: session creation and token minting
Learn more
AI chat overview
How chat agents, sessions, and the turn loop work.
Frontend
The chat transport, session tokens, and reconnection.
AI Prompts
Versioned prompts with dashboard overrides and generation tracking.
Tools
Declaring tools on your agent and how they persist across turns.

