Your Agent Run Takes 40 Minutes. Vercel Ends the Request at 300 Seconds

The documented ceilings: 300 seconds on Vercel Hobby, 900 on AWS Lambda, 30 seconds of CPU by default on a paid Cloudflare Worker. Here are all four side by side, the run length that picks your runtime, and the request shape that makes the ceiling stop mattering.

An agent that loops over tools does not fit inside an HTTP request, and the first symptom is a 504 rather than a bug report. The ceilings are published and they are lower than most people assume: 300 seconds is both the default and the maximum on Vercel's Hobby plan, a paid Cloudflare Worker gets 30 seconds of CPU time by default, and AWS Lambda stops any function at 900 seconds. The fix is not a bigger plan. It is to stop returning the answer in the same request that started the work: accept the job, return an identifier, do the work somewhere with no request attached, and let the client poll. That shape costs an afternoon and removes the ceiling from your architecture permanently.

The four ceilings, from the vendors' own limit pages

PlatformDefaultDocumented maximumWhat runs longer
Vercel Functions (fluid compute)300s all plansHobby 300s; Pro and Enterprise 800s, 1800s extended in betaVercel Workflows, described as having no duration limits
Cloudflare WorkersPaid: 30s CPU; Free: 10ms CPUPaid: 5 min CPU per requestDurable Objects, unlimited wall time while the caller is connected; Workflows, unlimited wall time per step; cron, queue consumers and DO alarms, 15 min each
AWS Lambda-900s (15 minutes)Lambda MicroVMs at 8 hours, not adjustable; durable executions, up to 3,000 operations each
Render--Background workers, which "run continuously, but they don't receive any incoming network traffic"

Method: each figure is taken from the vendor's own limits page, all opened 29 July 2026: Vercel Functions limits (page last updated 1 July 2026), Cloudflare Workers limits, AWS Lambda quotas and Render background workers. Limits change; check the page before you rely on a number.

Three details in the small print do more damage than the headline numbers. Vercel counts the whole response: the maximum duration "includes time spent processing the request and sending the response, including streamed responses," so streaming tokens does not buy you time, it spends it. Vercel's Edge runtime must begin sending a response within 25 seconds, then may stream for up to 300. And Cloudflare's ctx.waitUntil(), the usual trick for finishing work after responding, extends execution by up to 30 seconds, which is a tidy way to send a webhook and not a way to run an agent.

Pick the runtime from the run length

Measure your 95th percentile run, not your median, then read the band. This rule is ours; the numbers under it are the vendors'.

  • Under 25 seconds. Anything works. Keep it in the request and move on. Do not build a queue for this.
  • 25 seconds to 5 minutes. Still fits a normal function on every platform in the table, but you are now one slow model response from the ceiling. Add the run record and the polling endpoint even if you keep the work inline. That is the cheap version of the migration you will otherwise do under pressure.
  • 5 minutes to 15 minutes. Out of the request. Lambda at 900 seconds, a Cloudflare queue consumer at 15 minutes, or a Vercel Pro function at 800 seconds will hold a single run, but only just, and a retry after a partial failure starts from zero unless you checkpoint.
  • Beyond 15 minutes. You need something built for it: a long-lived worker process, a durable execution product, or a workflow engine. Render's framing is the honest one here, that agents can run for hours or even days.

Build the accept-and-poll shape

Seven steps, in order. Steps one to four are the whole thing; five to seven are what stops it hurting later.

  1. Write the run row before you do any work. One table: id, status (queued, running, succeeded, failed), input, output, error, created_at, updated_at, attempt count. The row is the contract with the client and the source of truth for retries.
  2. Return 202 with the id. The POST that starts a run inserts the row, signals a worker, and returns immediately. It never waits for a model.
  3. Run the work with no request attached. A Render background worker, a queue consumer, a Lambda invocation, or a workflow step. The choice matters less than the fact that nothing is holding a socket open.
  4. Poll a GET endpoint by id. Every 2 to 5 seconds, with the server returning the status and, once finished, the output. Server-sent events are nicer, and they are an optimisation, not the foundation.
  5. Make each step idempotent, keyed by run id and step index. Non-determinism is the whole difficulty: the model picks the next action at runtime, so a retry that re-executes a side effect sends a second email or takes a second payment. Write the step result before performing the side effect where you can, and check for a completed record before repeating one.
  6. Checkpoint after every tool call. The failure mode that costs real money is a 38-minute run dying at minute 37 and starting again at zero, paying for every token twice. If you are not already attributing spend per run, that is the instrumentation to add first.
  7. Give the run a deadline and a kill switch. A wall-clock cap and a maximum step count, both enforced by your code rather than by the platform's timeout, so that a loop ends as a clean failed status instead of a mystery.

You probably do not need a new database for this

The word queue makes people reach for Redis or SQS on day one. For a small product the run table plus a notification in the database you already run will carry you a long way past launch, and it has the property that the job and the business row it belongs to commit in the same transaction. We put numbers on that in how far one Postgres instance goes before a second system earns its keep. The reason to add a workflow engine is not throughput, it is that you want history, branching and replay handed to you rather than written by hand.

Two other limits worth knowing before they surprise you. Vercel caps request and response bodies at 4.5 MB, returning 413 FUNCTION_PAYLOAD_TOO_LARGE, and Lambda allows 6 MB synchronous and only 1 MB asynchronous. Agent runs accumulate transcripts, and a full trace can exceed those. Store the transcript in object storage or a text column and pass a reference, never the payload. Cloudflare's subrequest cap is the other one: 50 per request on the free plan, 10,000 on paid. A tool-calling loop with retries and embeddings can burn through 50 in a single run.

Worked example, to make the bands concrete. A research agent that makes 30 tool calls, each averaging 8 seconds of model and network time, runs for about 4 minutes at the median. Give it a 3x tail for a slow provider and one retry, and the 95th percentile is around 12 minutes. That sits above every default in the table and above Vercel's Hobby maximum entirely, so on that plan the feature cannot ship in a request at all, no matter how the code is written. Doing the multiplication before you deploy is cheaper than reading it off an error rate, and the same tail is where a provider outage turns one slow run into a queue of them.

Discussion

Sign in with Google or just a name. No email link, no password to remember.