1. Set up
- A Box API key from the Upstash Console.
- An Anthropic API key from API keys.
.env.local
These versions were tested together. If you upgrade CopilotKit, match
@ag-ui/client to the version it depends on: npm ls @ag-ui/client.2. Add the agent
CopilotKit talks to agents over AG-UI. This class sends the user’s message to the agent in the box and turns the box’s stream into AG-UI events.lib/box-agent.ts
3. Add the runtime route
Each user gets one box, found by name or created on the first message.app/api/copilotkit/[[...slug]]/route.ts
4. Add the chat
useDefaultRenderTool shows each tool call the agent makes, such as Bash and Write, as a card in the chat.
app/page.tsx
http://localhost:3000 and try:
Create squares.py that prints the first 10 squares, then run it.- Reload the page, then:
Change it to print cubes instead and run it again.
Customize
Change the harness and model ingetBox; see Agent for the options.
To cap a run or give the agent standing instructions, pass options with the prompt:
lib/box-agent.ts
Before production
- Identify users from your auth session. Replace
getUserId, and never read the user from the request body: the box name decides whose files the agent can reach. - Protect the thread routes.
InMemoryAgentRunnerrecords no owner for a thread, so/api/copilotkit/threadslists every user’s threads. Require authentication, scope thread listings to the signed-in user, and verify that a requested thread belongs to them. - Persist chat history. Store messages outside the server’s memory and restore the returning user’s thread ID. Realtime history and durable LLM streams show how to keep messages in Redis Streams and replay them after a reload.
- One conversation per user. The thread ID is not sent to the box, so every chat from a user continues the same agent session.
Troubleshooting
BoxAgentis not assignable toAbstractAgent: your@ag-ui/clientversion differs from the one@copilotkit/runtimeuses.- Permission denied writing files: the agent can only write under
/workspace/home. - The chat is empty after a reload: this example keeps visible messages in memory. The agent in the box still has the earlier conversation and files.
Box cannot start a run in its current state: a box runs one prompt at a time. The user sent a message, for example from a second tab, while the agent was still working.- No tool cards with OpenCode: the OpenCode harness streams only text, so the chat shows the reply without the tool calls.
- Box limit reached: the error comes from
Box.create. Delete unused boxes in the console or upgrade your plan.