Skip to main content

Put a CopilotKit chat in your Next.js app where each user talks to a coding agent running in their own Upstash Box.

Upstash Box combines the agent and its sandbox: the agent runs inside the box, next to the shell and files it works on. CopilotKit is the UI. A small adapter in your app connects the two.

Each user gets one box and one continuing conversation with the agent. The box keeps the agent’s files and memory between visits.


1. Set up#

.env.local

In a Next.js app:

Note

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

Open http://localhost:3000 and try:

  1. Create squares.py that prints the first 10 squares, then run it.
  2. Reload the page, then: Change it to print cubes instead and run it again.

The chat is empty after the reload, but the agent finds the script and edits it.


Customize#

Change the harness and model in getBox; see Agent for the options.

To cap a run or give the agent standing instructions, pass options with the prompt:

lib/box-agent.ts

The system prompt is set when the conversation starts and stays for the rest of it.


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. InMemoryAgentRunner records no owner for a thread, so /api/copilotkit/threads lists 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#

  • BoxAgent is not assignable to AbstractAgent: your @ag-ui/client version differs from the one @copilotkit/runtime uses.
  • 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.