Vercel, Netlify, Lambda — what works and what doesn't
Short answer: you can serve skein's HTTP surface from a serverless function, but you cannot run background runs there. If you want the full Agent Protocol, deploy the container (deploy.md) and keep serverless for your UI.
This page exists because "deploy LangGraph on Vercel" is a reasonable thing to try, and the failure mode is quiet — everything looks fine until a background run never finishes.
Why background runs can't work
POST /threads/{id}/runs enqueues a run and responds immediately. A background worker picks it up and executes it — after the HTTP response has been sent.
On a serverless platform, that's exactly when the execution context is frozen or destroyed. The request is over, so the work stops. The run sits at pending or running forever, and nothing about the response told you anything was wrong.
Several other things follow from the same property:
- The in-memory queue and event bus are per-invocation. Every cold start gets its own, so a run enqueued by one invocation is invisible to the next.
- Cancellation and the per-thread run guard are in-process (details), and "in-process" on serverless means "for the lifetime of one request".
- No
SIGTERMdrain. Nothing gets the chance to settle in-flight runs terminally. - Boot cost on every cold scale-out. skein runs migrations, seeds assistants, and warms graphs at startup — that's a container-lifetime cost, not a per-request one.
- One Postgres pool per instance. Serverless concurrency multiplies pools quickly; you'd need a pooler in front regardless.
What does work
If you keep to inline runs, the work happens during the request, and a function can serve it:
POST /threads,GET /threads/{id}/state, the assistants and store routes — ordinary request /response.POST /threads/{id}/runs/wait— runs to completion inside the request.POST /threads/{id}/runs/stream— works where the platform supports streaming responses and yourmaxDurationcovers the whole run.
Use Postgres for state (a serverless-friendly one with a pooled endpoint for the runtime, though note boot migrations want a direct endpoint), and accept that POST /threads/{id}/runs — the background variant — is off the menu.
The shape that actually works
Put the UI on Vercel and the agent server in a container:
Vercel (Next.js UI, useStream) ──► skein container on Cloud Run / Fly / Render
│
├── Postgres (state + checkpoints)
└── Redis (queue + stream fan-out)This is what examples/nextjs-app is shaped like, and it keeps the useStream developer experience while letting background runs, webhooks and long-running work behave. See react-sdk.md for the client side.
If you deploy the Next.js adapter to Vercel anyway
For an inline-only workload it can be a reasonable trade. Two specifics:
- The Next.js adapter registers no
/okroute. The other adapters add one; this one exposes route handlers only. Add your ownapp/ok/route.tsif something needs to probe it. - Set
maxDurationon the streaming route to at least your longest run, and remember skein imposes no run timeout of its own — the platform's limit is the only ceiling.
And revisit the auth warning: a Vercel deployment is public by default.