This project is a complete Next.js chat for one persistent Letta agent. The chat streams Markdown, reasoning, and tool calls. It also restores saved conversations after a reload.
You need Node.js 22.19 or later and a Letta API key.
-
Clone the repository and install the dependencies:
git clone https://github.com/letta-ai/letta-agent-sdk-react-chat.git cd letta-agent-sdk-react-chat npm install -
Create the local environment file:
cp .env.example .env.local
-
Replace
your-key-herein.env.localwith your API key. -
Remove the sample
LETTA_CHAT_AGENT_IDline. -
Create the persistent demo agent:
node --env-file=.env.local scripts/create-agent.mjs
The script adds
LETTA_CHAT_AGENT_IDto.env.local. -
Start the development server:
npm run dev
-
Open http://localhost:3000.
-
Send the following message:
Use web_search to find the Letta documentation homepage and reply with only its title.
The check passes when the page shows web_search before the answer. Reload the
page. The complete transcript must return.
Choose a learning path after the chat works:
- 15 minutes: read the glossary and trace one turn in Learn the application.
- 60 minutes: complete the theme and event-order labs in the same guide.
- Half a day: complete the labs. Then use Customize the chat to add one visible SDK event.
Read the source in this order:
tests/browser-events.test.tsapp/api/chat/route.tslib/letta/sdk-rows.tslib/letta/browser-events.tscomponents/chat/transcript-message.tsx
The test drives one complete turn from SDK messages to React. The next three files show how that turn reaches the browser — the session, the SDK's transcript accumulator, and the wire it produces — and the last file renders it.
| Change | Start here |
|---|---|
| Colors, spacing, width, or radii | styles/tokens.css |
| Layout and responsive behavior | styles/chat.module.css |
| User and assistant message markup | components/chat/transcript-message.tsx |
| Tool call display | components/chat/tool-disclosure.tsx |
| Reasoning display | components/chat/thinking-disclosure.tsx |
| Composer display and loading state | components/chat/chat-composer.tsx |
| Browser conversation state | hooks/use-chat-session.ts |
| Follow-at-bottom behavior | hooks/use-follow-output.ts |
| Wire protocol and live reducer | lib/letta/browser-events.ts |
| SDK messages and restored history | lib/letta/sdk-rows.ts |
| Rows to displayed message parts | lib/letta/transcript.ts |
| Agent SDK session lifecycle | app/api/chat/route.ts |
| Conversation creation and bootstrap | app/api/conversations/route.ts |
Use Learn the application for guided exercises. Use Customize the chat for change recipes. Read Architecture before you change a session or the transcript state.
Run all checks:
npm run verifyThe tests use fixed event data. They do not call Letta Cloud. Test the tool-use message after you change an API route or the session flow.
The chat has no approval interface, so app/api/chat/route.ts answers the
agent's approval requests with an explicit policy: tools listed in
LETTA_AUTO_APPROVE_TOOLS (comma separated, or *) run, and everything else is
refused with a message the transcript shows on the tool card.
Never leave canUseTool unset. Without it the SDK falls back to deny, and the
conversation can park on an approval that nothing answers.
The browser does not receive LETTA_API_KEY. The server runs the SDK's
transcript accumulator and sends the rows it produces through an explicit field
allowlist: the tool name, its parsed arguments, and its status. It does not send
the raw tool output.
This example supports one trusted user. Before deployment, authenticate each request. Add rate limits. Store the conversation IDs that each user can access. Do not show one agent's conversation list to unrelated users.
