Skip to content
Docs

Build Claude Managed Agents with Vercel Services

Deploy Claude Managed Agents with Vercel Services to run a streaming research analyst in a browser chat, using Anthropic's Chat SDK quickstart.

Claude Managed Agents are agents that Anthropic runs for you, with server-side sessions, sandboxed tools, and an event stream your app consumes.

Vercel's Chat SDK gives that agent a chat surface through a single type-safe handler, with adapters for the web, Slack, Teams, Discord, Telegram, and WhatsApp.

The new Anthropic quickstart pairs Claude Managed Agents with Chat SDK into a research analyst in a browser chat. Each conversation maps to a single persistent session; replies stream token by token, and a live feed shows the agent's activity.

Copy link to headingOverview

In this guide, you'll learn how to:

  • Provision a Managed Agents research analyst and its sandbox environment
  • Run the Chat SDK web chat locally and watch the agent research in real time
  • Update the agent's model and system prompt without creating duplicates
  • Deploy the chat to Vercel using Vercel Services

Copy link to headingPrerequisites

Before you begin, make sure you have:

  • Node.js 22.9 or later
  • An Anthropic API key from platform.claude.com, or run ant auth login
  • An Anthropic organization with Managed Agents access

Copy link to headingHow it works

Copy link to headingSessions are the conversation store

The quickstart holds no state of its own. Every conversation in the browser maps to exactly one Managed Agents session, and the sessions API backs each part:

In the browserWhat powers it
Starting a new chatPOST /api/sessions creates a session, and useChat uses the returned session ID as its thread ID
Conversation sidebarsessions.list()
Replaying a transcriptsessions.events.list()
Follow-up questionsThe session holds the research context server-side

Copy link to headingThe web adapter needs no platform registration

The demo uses the Chat SDK's web adapter, so there is no chat platform to register with. You don't need a Slack app, a webhook to verify, or a tunnel. The only credential is your Anthropic auth.

Copy link to headingReplies and activity stream on separate channels

When a message arrives, the adapter holds the HTTP response open while the bridge holds an Anthropic event stream open for the same turn, forwarding each agent.message event to the response. The acknowledgment appears within seconds, and the finished brief arrives with the same response minutes later.

Tool calls, model requests, and thinking indicators stream separately. The page receives them as a live feed over GET /api/activity while the turn runs.

Copy link to headingThe agent runs entirely on Anthropic's side

The tool loop, sandboxed web research, session state, compaction, and prompt caching all occur within the session.

The agent's shell tool is disabled by design. The analyst reads arbitrary web pages, and prompt-injected content combined with an auto-approved shell and open egress would create an exfiltration path. The brief relies on web search, web fetch, and the file tools instead.

Copy link to headingSteps

Copy link to heading1. Clone the quickstart

Clone the repository and move into the project:

Terminal
git clone https://github.com/anthropics/claude-quickstarts.git
cd claude-quickstarts/managed-agents/chat-sdk

Copy link to heading2. Install dependencies

Terminal
npm install

This installs:

  • Chat SDK packages:
    • chat: the core SDK with the type-safe handler and message primitives
    • @chat-adapter/web: the web adapter that serves the browser chat surface
    • @chat-adapter/state-memory: in-memory adapter state
  • Anthropic SDK: @anthropic-ai/sdk 0.109.0 or later
  • Server and UI: Hono and the React chat page's dependencies

Copy link to heading3. Configure authentication

Copy the environment template:

Terminal
cp .env.example .env

Then add Anthropic auth in one of two ways:

  • Uncomment ANTHROPIC_API_KEY in .env and paste a key from platform.claude.com.
  • Run ant auth login once and leave the variable out, since the SDK discovers CLI credentials on its own.

Copy link to heading4. Provision the agent and environment

Terminal
npm run setup

This one-time step creates two persistent resources:

  • A cloud sandbox environment where the agent's tools run
  • The analyst agent, configured with a model (Claude Opus by default, overridable with QUICKSTART_MODEL), a system prompt tuned for chat-length research briefs, and a toolset with bash disabled

The script prints both IDs:

environment: env_...
analyst: agent_... (version 1, claude-opus-4-8)
Add to .env:
CLAUDE_AGENT_ID=agent_...
CLAUDE_ENVIRONMENT_ID=env_...

Paste the two IDs into .env. Don't re-run npm run setup later, because it would create a duplicate agent. To change the agent, use npm run update-agent.

Copy link to heading5. Run the chat locally

Start the development server:

Terminal
npm run dev

Open http://localhost:3000, select New chat, and ask for a brief on any topic. Here's what you should see:

  1. An acknowledgment message within seconds
  2. The activity feed filling with searches for one to three minutes
  3. The brief streaming in token by token
  4. A collapsed tool-call trace and a "Brief ready" card linking to the session's trace in the Anthropic Console

To confirm that sessions hold all the state, restart the development server and reload the page. The sidebar and every transcript come back intact from the sessions API, and follow-up questions in a replayed chat still work because the session keeps the research context server-side.

Copy link to heading6. Customize the agent

The agent's entire identity (name, model, and system prompt) lives in setup/agent-config.ts. After editing it, push the change as a new agent version:

Terminal
npm run update-agent

Running sessions keep their pinned version and new chats use the latest, so an edit never disrupts a conversation in flight.

Copy link to headingDeploying to Vercel

Deploying to Vercel comes down to a Vite build for the page, a Node entry for the API, environment variables, and a longer function duration.

Copy link to heading1. Serve the page from the CDN and the API from a function

The local Node server serves both the chat page and the API. On Vercel, use Vercel Services to deploy them as two services in the same project:

  • A Vite service builds the existing web/ directory and serves the generated frontend assets from Vercel’s CDN.
  • A Node service runs the Hono application and handles the four /api routes through deployedApi().

First, create the API entry point for the Hono server by adding a new server.ts file at the project root:

server.ts
import { createAdaptorServer } from "@hono/node-server";
import { deployedApi } from "./src/app";
const app = deployedApi();
export const server = createAdaptorServer({
fetch: app.fetch,
serverOptions: { requestTimeout: 0 },
});
export function listen() {
const port = Number(process.env.PORT) || 3000;
const hostname = process.env.HOST || "127.0.0.1";
server.listen(port, hostname, () => {
console.log(`Research analyst API running at http://${hostname}:${port}`);
});
}
if (!process.env.VERCEL || process.env.VERCEL_ENV === "development") listen();
export default server;

This adapts the Fetch-native Hono application to a Node HTTP server. Vercel imports the exported server in production, while the local development command starts it normally.

Now, install Vite and its React plugin as development dependencies:

Terminal
npm install --save-dev vite@^7 @vitejs/plugin-react@^5

Add these scripts alongside the existing ones in package.json:

package.json
{
"scripts": {
"dev:api": "tsx watch --env-file-if-exists=.env server.ts",
"dev:web": "vite --config vite.config.ts",
"build": "vite build --config vite.config.ts"
}
}

Then, configure Vite to read the frontend from web/ and write the production bundle to dist/. For this, create a new file called vite.config.ts at the project root:

vite.config.ts
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
root: "web",
plugins: [react()],
server: {
host: "127.0.0.1",
port: Number(process.env.PORT) || 5173,
strictPort: true,
},
build: {
emptyOutDir: true,
outDir: "../dist",
},
});

Finally, create a vercel.json file at the project root to configure Vercel Services and the rewrites:

vercel.json
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"services": {
"web": {
"root": ".",
"framework": "vite",
"buildCommand": "npm run build",
"outputDirectory": "dist",
"devCommand": "npm run dev:web"
},
"api": {
"root": ".",
"framework": "node",
"entrypoint": "server.ts",
"devCommand": "npm run dev:api"
}
},
"rewrites": [
{
"source": "/api/(.*)",
"destination": { "service": "api" }
},
{
"source": "/(.*)",
"destination": { "service": "web" }
}
]
}

With these changes in place, test the setup by running:

Terminal
vercel dev -L

This will start both services locally and give you a single URL to test the project. Once you are ready, you can deploy the project to Vercel by running:

Terminal
vercel deploy

Vercel builds the frontend into dist/ and serves it through the web service. Requests for the page and its generated assets are handled by the CDN-backed Vite deployment, while /api/* requests are routed to the Node service running the Hono application on Vercel Functions.

Copy link to heading2. Add production credentials

CLI credentials from ant auth login only exist on your machine, and the Anthropic client reads process.env at module scope, so the deployment needs its configuration set as environment variables before imports run. In your project settings, open Environment Variables and add:

VariableValue
ANTHROPIC_API_KEYThe key from platform.claude.com. The SDK also accepts ANTHROPIC_AUTH_TOKEN as the bearer-token form
CLAUDE_AGENT_IDThe agent_... ID printed by npm run setup
CLAUDE_ENVIRONMENT_IDThe env_... ID printed by npm run setup

deployedApi() checks these on every request and names the missing variable in its 500 response. Don't run npm run setup as part of the build: the agent and environment are persistent resources, and re-running setup creates duplicates. Provision once from your machine and reuse the IDs.

Copy link to heading3. Learn what stays per instance

The Managed Agents session holds every conversation, so createMemoryState() in src/bot.ts carries only Chat SDK internals, and the quickstart keeps memory state in production on a single instance.

Fluid compute runs many requests on one instance but scales to more instances under load, and three behaviors in the quickstart are process-local:

BehaviorWhere it livesOn multiple instances
Message dedupcreateMemoryState() in src/bot.tsDuplicate deliveries of the same message ID could each start a turn. useChat sends each message once, so this matters when you add retries or a second surface. The fix is createRedisState() from @chat-adapter/state-redis, which auto-detects REDIS_URL
Turn orderingenqueueTurn in src/managed-agents.tsTwo instances streaming the same session would each post every reply. The browser serializes sends per conversation, so this appears when something else writes into the same session concurrently
Activity feed fan-outsrc/activity.tsWhen GET /api/activity lands on a different instance than the turn's /api/chat, the feed shows empty for that turn. This is cosmetic: the chat lane is unaffected

For a demo or low-traffic deployment, ship as-is.

Before scaling past one instance, make the Redis swap and treat the other two rows as known behavior rather than bugs to chase.

Copy link to heading4. Raise the max duration for the chat route

The research turns hold the /api/chat response open for one to three minutes while the brief streams. Vercel Functions on fluid compute default to a 300-second maximum duration, which covers a typical turn, and Pro and Enterprise teams can raise the limit to 800 seconds for longer research runs.

To raise the project default, open the Functions page in your project settings. Under Advanced Settings, set a new value for Function Max Duration.

You can also set maxDuration per function in vercel.json if only the chat route needs the longer window. For runs beyond 800 seconds, extended max duration supports per-function limits up to 30 minutes on supported Node.js and Python runtimes. Project defaults stay capped at 800 seconds.

Fluid compute fits this workload well. Active CPU billing only applies while your code executes and pauses while the function waits on I/O, and this function spends nearly the entire turn waiting on Anthropic's event stream.

Copy link to heading5. Lock down authentication first

Deploying makes every route public, so complete the getUser replacement described in best practices before your first production deploy. The demo authentication treats every caller as the same user, which is safe on 127.0.0.1 and nowhere else: anyone who reaches the URL can run research turns on your bill, list your sessions, and replay every transcript.

Vercel's Deployment Protection can gate the URL while getUser is still the demo version, but treat it as a stopgap rather than a fix.

Copy link to headingBest practices

Copy link to headingReplace the demo authentication before exposing the server

The getUser function in src/bot.ts is the security boundary for every route: /api/chat, /api/sessions, /api/history, and /api/activity. The demo version accepts every caller as the same local user, which is why the server binds to 127.0.0.1 by default.

Before setting HOST=0.0.0.0 or deploying:

  • Replace getUser with your real session lookup (e.g., with Better Auth)
  • Scope sessions per user by writing the resolved user ID into session metadata

Copy link to headingNever trust a browser-supplied conversation ID

The browser sends session IDs, and the server's Anthropic credentials can see every session in the workspace. The quickstart's ownedSession() check in src/managed-agents.ts verifies that an incoming ID resolves, belongs to this agent, and isn't archived before any route touches it. If you fork the project, keep that function in the path of anything that accepts a conversation ID from a client.

Copy link to headingRaise idle timeouts for long-lived responses

The /api/chat response stays open for the whole research turn, often minutes at a time. A reverse proxy or load balancer with a default idle timeout (often 60s) will close the response right when the brief is due.

On Vercel, this limit is the function's max duration. If you host the app elsewhere or put your own proxy in front of it, raise the proxy's idle timeout for the /api/chat route and verify that your platform allows long streaming responses.

Copy link to headingTroubleshooting

SymptomLikely causeFix
Replies arrive whole but never streamThe organization doesn't have session streaming enabled, or the installed @anthropic-ai/sdk is older than 0.109.0Upgrade the SDK to 0.109.0 or later and confirm streaming is enabled for your organization
Every request fails with a 404The organization has no Managed Agents access. The SDK sends the managed-agents-2026-04-01 beta header on every call, so an org without access rejects the first requestUse an organization that has Managed Agents access
400 Invalid agent ID.env still contains the agent_... placeholderRe-paste the IDs printed by npm run setup
Acknowledgment arrives, then a network error replaces the briefSomething between the browser and the server closed the long-lived response. The turn still finished server-sideReopen the chat from the sidebar to see the reply, then raise the function's max duration or the proxy's idle timeout

Copy link to headingResources and next steps

Related documentation

More Chat SDK guides