The container is disposable. The agent isn't.
skail · 2026-07-02 · 7 min
TL;DR
- The problem — a stateless container keeps nothing between requests. Kill it mid-wait and an AI agent's in-flight work is gone.
- The answer — skail is a durable agent runtime (SDK is C# today): plain linear code that hibernates at zero compute cost and resumes exactly where it left off.
- The proof — we killed one on real Vercel compute, mid-wait. It finished the job anyway.
The problem: "nothing in between" is the whole problem for agents
Vercel recently shipped Dockerfile support — any runtime, autoscaled, pay-per-active-CPU, with ASP.NET named. A real, welcome shift for .NET builders. But read the fine print:
Each container is a stateless process: it takes a request, returns a response, and keeps nothing in between.
For a normal web request, that's the whole point of serverless. For an AI agent, it's the whole problem — because agents don't answer once and return. They call an LLM, then they wait:
- for a human to click approve
- for a payment processor's callback
- for a government system's webhook
Sometimes for days. Kill the container mid-wait, and the work is gone — unless durability lives somewhere other than the container.
This isn't only a Vercel-shaped problem. It's the same question on any stateless container, autoscaling VPS, or host that can restart out from under a running process. skail is a durable agent runtime built for exactly that world: plain linear code — the SDK is C# today — that calls an LLM, waits on real-world events (SkailTask.WaitForEvent), sleeps (SkailTask.Delay), survives crash / redeploy / scale-to-zero, and resumes mid-line. The durability lives in the runtime, not in whichever box is running your code this minute.
Vercel knows durability is the missing half — it's why they built the Workflow Development Kit (TypeScript today, plus Python on managed). That's a workflow-shaped model of directives and steps; skail is plain linear code with real-world event waits, and it starts where WDK doesn't — a C# SDK.
How it works: deploy in 3 steps (this walkthrough uses Vercel — the pattern isn't Vercel-specific)
This is a deploy-ready template: one process that is the durable agent and the HTTP surface. No separate orchestrator, no message queue, no state table.
- Point Vercel at the repo and set the build to use
Dockerfile.vercel. It's a multi-stagesdk→aspnetbuild that copies a localpackages/feed sodotnet restoreworks offline (skail isn't on public NuGet yet). - Set env in Vercel project settings:
SKAIL_SIDECAR,SKAIL_WORKLOAD,SKAIL_NAMESPACE,SKAIL_KEY. The sidecar is a remote HTTPS endpoint — there's nothing to install locally. - Deploy. Kestrel binds
$PORT— the container is now a durable agent host.
The four endpoints it exposes:
POST /refunds/{id}— starts a durableProcessRefundrun (deterministic idempotency key). Returns 202.POST /webhook/approval— firesREFUND_APPROVEDat the hibernating run; inbound HTTP wakes the container. Returns 202.GET /wake— Vercel Cron nudge target for pure-timer agents. Returns 200.GET /healthz— liveness check; returns 200 even when skail is unreachable.
The durable function itself is plain, linear code — no workflow DSL, no step graph. This is the real, working C#:
The AnalyzeWithAI step is a mock — fully deterministic, zero external API dependencies, so the template builds and runs with no OpenAI or Anthropic key. Swap in your own model at the // plug your LLM here comment and the checkpointing story doesn't change: that call runs once, and replays reuse the recorded result instead of re-billing tokens.
This isn't a demo pattern invented for a blog post. The same shape runs a production agent today: it issues Brazilian electronic invoices (NFS-e), hibernates waiting for a government webhook, wakes up, and finishes the job.
The proof: we killed the container mid-wait, on purpose — on real Vercel compute
Talk is cheap, so we ran the experiment for real, against a live deployment: ProcessRefund — the exact agent above — running inside a Vercel Firecracker microVM, booted from the same Dockerfile.vercel image this template ships. We destroyed the VM mid-hibernation, fired the approval it was waiting on while nothing was running anywhere, then booted a fresh VM and watched it finish the job.
Request id: sandbox-kill-004 · Durable task id: cbfe8a6a-54db-4b66-9327-f22303dea7e4
Total elapsed, trigger → destroy → approve-while-dead → resume on a new VM: ~30 seconds. The five beats above are the real run; the raw logs below are the same five, unedited.
① Boot a Vercel VM from the Dockerfile image → trigger the agent → it hibernates
② Destroy the Vercel VM mid-hibernation
③ Fire the approval while nothing is running
④ Boot a fresh Vercel VM — the agent resumes and finishes (~1s after worker boot)
Note what's not in step ④: no re-run of AnalyzeWithAI (the LLM step) or NotifyApprover. Completed steps are checkpointed — replay reused their recorded results. That's the token-billing claim, visible in a log, not just asserted in a blog post.
What this proves:
- Kill the container mid-wait — the work survives. The VM was destroyed at WaitForEvent; a fresh VM resumed the same task (cbfe8a6a…).
- The event that resumes it is inbound HTTP. A plain POST /webhook/approval delivered it, and it matched the hibernating task with no worker connected anywhere.
- Events queue while the agent sleeps. The fire call returned 200 and bound to the durable task while the VM was dead; the next boot consumed it.
- Checkpointed steps are never re-run. The resumed VM issued the refund without re-billing the LLM step.
The honest note underneath all of it: the durable state lives in the skail runtime, not on the box — which is exactly why a disposable container (or VM, or process) can be killed and replaced freely. Vercel's HTTP front door for this template runs as a request-driven Functions container; the durable worker itself needs continuous execution while it's alive, which on Vercel today means a Sandbox microVM rather than a scale-to-zero Function — same image, both Vercel, and the honest limits below cover exactly this.
Watch it in the Monitor
Everything above isn't a diagram — it's a trace. This is the actual template agent (ProcessRefund) in the skail Monitor: the same run whose host we destroyed mid-wait.

- Every step is traced — type, status, duration and attempts, per step (root: attempts 1/16).
- One task id, trigger to finish — the id in the header is the one that was triggered before the VM was destroyed.
- 8.7s end-to-end, ~227ms of it compute — the rest is the hibernation, and the run still lands on Completed.
Want your agents traced like this? DM me on X — onboarding early users personally.
Honest limits
-
Not exactly-once for the LLM call. Completed steps are checkpointed in the skail runtime and are never re-run on replay — that's the token-billing win. But a crash mid-LLM-call can re-run that one call, same as any retry story. Finished work (and its spend) is never repeated; in-flight work can be. That's why side-effecting steps (
IssueRefund,Escalate,NotifyDenied) also guard themselves with an idempotency marker. -
State lives in the skail runtime, not the container. The container (or VM, or process) stays disposable. A fresh one replays the event history and resumes mid-function.
-
The workload must be admin-registered. The runtime can't auto-create a new
SKAIL_WORKLOADname; registername:versionfirst. -
skail is not serverless. It's a durable runtime layered on your process, wherever that process runs — a container, a VPS, a Vercel Sandbox.
-
On Vercel specifically, the durable worker needs continuous compute. A request-driven Function suspends background execution between requests, so the worker's consume loop doesn't stay up there. A Sandbox microVM (continuous execution while alive) runs it instead — same image, different Vercel compute flavor.
-
Pure-timer agents need a nudge. An agent whose only pending wait is a
Delayhas no inbound HTTP to wake a scaled-to-zero container. Approval- and webhook-driven agents wake naturally — the event that resumes them IS inbound HTTP, exactly what wakes a Vercel container. A pure timer doesn't have that, so the honest workaround is a one-line Vercel Cron hittingGET /wakeon a schedule:The refund agent above is approval-driven, so it wakes naturally;
/wakeis included for the timeout case (the 72h escalation) and for completeness.
FAQ
Why not Azure Durable Functions? Great tool if you live on Azure. skail's point is running anywhere: plain linear code on any container platform or VPS, Vercel included.
The programming model is different too. Durable Functions splits your agent into orchestrators and activity functions, and that decomposition is yours to manage. Versioning is the sharp edge: per Microsoft's own guidance, breaking changes can leave in-flight orchestrations failing or stuck, and the recommended fixes are side-by-side deployments, deployment slots, or renaming functions (MyOrchestrator_v1, _v2). In skail, every deploy is a versioned workload (name:v0.0.2) and dispatch is version-scoped, so side-by-side is the default behavior, not a pattern you build by hand. Observability follows the same theme: with Durable Functions you assemble a run's story from App Insights telemetry; the skail Monitor shows the whole run as one trace, out of the box. The screenshot above is exactly that.
Doesn't Vercel's Workflow Development Kit do agents too? It does — for TypeScript (plus Python on the managed platform). The model is different too: WDK is workflow-shaped (directives and steps); skail is plain linear code with real-world event waits. If you're on TypeScript, WDK is a solid choice.
Is skail .NET-only? The runtime is language-agnostic; the SDK we ship today is C#. That's also where the durability gap on platforms like Vercel is widest today. More SDKs as we grow.
Where does the state live if the container is stateless? Not in the container — that's the point. Every completed step is checkpointed in the skail runtime; a fresh container replays the history and resumes mid-function. Containers stay disposable, exactly as they're designed to be.
Are LLM calls exactly-once, then? Fair to push on. Completed steps are checkpointed and never re-run on replay — that's the token-billing win. A crash mid-call can re-run that one call, same as any retry story, which is why side effects want idempotency keys. The guarantee: finished work (and its spend) is never repeated.
Can I try it? Yes — see below.
Building AI agents? DM me on X — we're onboarding early users personally.