Full-Stack: the agent behind a web UI
The Full-Stack track puts the agent you built in Foundations behind a real web UI. It needs no new agent concepts, only some frontend comfort, so you can take it straight after Foundations, no Patterns required.
Rather than build the app from scratch, you start from a ready, production-shaped template
and build on it: a single Hono server on Node that serves
both the React useChat UI and the AI SDK agent, streaming the Vercel AI data-stream
protocol. Pure TypeScript, one process, deployable like any Node app.
Get the template
Section titled “Get the template”No API key needed: it runs on Ollama (granite4.1:3b) by default. Open http://localhost:5173.
What you start with
Section titled “What you start with”One tool is wired all the way through, end to end: get_current_time. The model has no
clock of its own, so asking “what time is it?” makes it call the tool, and you see the
call render as a tool-call card in the chat. That round-trip (agent → tool → card) is the
thing to understand; everything else is yours to extend.
The agent, with its model, instructions, and tools, lives in src/server/agent.ts; the
chat route is src/server/api.ts. The UI in src/client/ infers its tool-part types
straight from the tool definitions, so there are no duplicate schemas to keep in sync.
The mechanic, on the tool already there
Section titled “The mechanic, on the tool already there”You are not learning React here. You are learning the end-to-end SHAPE of one tool flowing through a web app.
get_current_time is already wired all the way through, and your job is to copy that shape
for get_weather. There are four pieces:
Read the existing path once before you add anything:
src/tools/time-tool.tsThe tool itself:tool({ description, inputSchema, execute }), plus the exported UI types inferred from that tool.src/tools/index.tsThe registry: the tool is added tochatTools, and its UI types are re-exported.src/client/components/chat-tool-parts.tsxThe card: one component that renders the tool part’sstate,input, andoutput.src/client/Chat.tsxThe switch: onecase 'tool-get_current_time':that picks that card when the stream contains that tool part.
That is the whole transfer pattern. get_weather is not a new frontend concept; it is the
same four moves with different fields.
Here is the important shape in miniature:
If you can trace those four pieces for the time tool, you have everything you need for the weather tool. The React side is just rendering typed data the tool already defined.
Build it
Section titled “Build it”-
Run it. Ask “what time is it?” and watch the
get_current_timecard appear, then resolve with the result. Open the trace viewer on :4445 to see the same run as a span tree. -
Give the assistant a voice. Tighten the
instructionsinsrc/server/agent.ts: a house style, a format rule, a persona. Save; Vite hot-reloads. Ask again and watch the tone shift. -
Add your own tool. Copy the
get_current_timepath, piece by piece, forget_weather:- define
getWeatherToolinsrc/tools/weather-tool.tswithtool({ description, inputSchema, execute }), - export its inferred UI types from the same file,
- register it in
src/tools/index.tsby addingget_weathertochatToolsand re-exporting its types, - render its streamed part with a
GetWeatherToolPartinsrc/client/components/chat-tool-parts.tsx, - add
case 'tool-get_weather':insrc/client/Chat.tsxso the chat picks that card.
Then ask “what’s the weather in Lisbon?” and watch your new card render.
- define
The model can’t invent a real weather feed, so, like the clock, it has to call your tool. That is the whole point of the track: the UI only shows what a tool returned.
- Tool not in the registry. If
get_weatherisn’t added tochatToolsinsrc/tools/index.ts, the agent can’t call it and no card ever streams. - A plain-text answer, no card. You wired the tool but not the UI: add the
GetWeatherToolPartcomponent and thecase 'tool-get_weather':inChat.tsx. Miss the case and the part falls through to text. - Type errors on the card. The UI tool types are inferred from the tool definition, so fix the tool’s
inputSchema/return shape, not the card’s types, when they disagree. - The model won’t call it. Like the clock, weather must be something the model can’t already know; a vague tool
descriptionlets it answer from memory instead. The description is the interface (f6).
Open the reference only after you’ve traced the existing time tool end to end once.
Reference solution: the get_weather tool
src/tools/weather-tool.ts:
src/tools/index.ts, add it to the set:
Then add a GetWeatherToolPart in chat-tool-parts.tsx (rendering
invocation.output.location / temperature_c / condition) and wire
case 'tool-get_weather': into the switch in Chat.tsx, the same way
tool-get_current_time is handled.