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.
kill-resume · task cbfe8a6a…
trigger → hibernating at WaitForEvent
✕ Vercel VM destroyed — nothing running
POST /webhook/approval → 200 · while dead
resumed on a fresh VM → refund issued
An approval — accepted and delivered while nothing was running anywhere.

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.
Vercel, on Dockerfile deployments

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:

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.

  1. Point Vercel at the repo and set the build to use Dockerfile.vercel. It's a multi-stage sdkaspnet build that copies a local packages/ feed so dotnet restore works offline (skail isn't on public NuGet yet).
  2. 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.
  3. Deploy. Kestrel binds $PORT — the container is now a durable agent host.

The four endpoints it exposes:

The durable function itself is plain, linear code — no workflow DSL, no step graph. This is the real, working C#:

csharp · 31 lines · the whole file
[SkailFunction]
public async SkailTask ProcessRefund(string requestId)
{
// LLM step — runs exactly once, even across retries and redeploys
var analysis = await AnalyzeWithAI(requestId);
if (analysis.AutoApprovable)
{
await IssueRefund(requestId);
return;
}
// Ask a human, then hibernate. Zero compute while waiting.
await NotifyApprover(requestId, analysis.Summary);
var approval = SkailTask.WaitForEvent<Approval>("REFUND_APPROVED", requestId);
var timeout = SkailTask.Delay(TimeSpan.FromHours(72));
// The container can scale to zero here. The approve click is
// inbound HTTP — exactly what wakes a Vercel container back up.
if (await SkailTask.WhenAny(approval, timeout) == timeout)
{
await Escalate(requestId);
return;
}
if ((await approval).Approved)
await IssueRefund(requestId); // idempotent — safe on replay
else
await NotifyDenied(requestId);
}

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.

trigger
POST /refunds
hibernating
WaitForEvent · zero compute
VM destroyed
sandbox.stop()
event fired
nothing running
resumed
fresh VM · refund issued
Real run · task cbfe8a6a… · ~30s end to end · Vercel Sandbox microVM

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

text · 7 lines
POST /api/v1/trigger/mktskailingworld/v0.0.2/ProcessRefund/<key> → HTTP 200
{"target_task_id":"cbfe8a6a-…"}
[sandbox-kill-004] CMD AnalyzeWithAI: autoApprovable=False (mock analyzer)
[sandbox-kill-004] CMD NotifyApprover: Refund sandbox-kill-004: needs a human — …
[sandbox-kill-004] CMD NotifyApprover: approve at -> …/webhook/approval?requestId=sandbox-kill-004
(now hibernating at WaitForEvent, zero compute)

② Destroy the Vercel VM mid-hibernation

text · 1 lines
sandbox.stop() → VM DESTROYED — no worker exists anywhere now

③ Fire the approval while nothing is running

text · 2 lines
POST /api/v1/fire/REFUND_APPROVED/sandbox-kill-004 → HTTP 200
{"target_task_id":"cbfe8a6a-…"} ← same task id, held across the VM's death

④ Boot a fresh Vercel VM — the agent resumes and finishes (~1s after worker boot)

text · 2 lines
[startup] skail-on-vercel host starting…
[sandbox-kill-004] CMD IssueRefund: issuing refund

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:

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.

skail Monitor timeline for task dc2a5d32: ProcessRefund and its child steps — AnalyzeWithAI, NotifyApprover, WaitForEvent, REFUND_APPROVED, WhenAny, IssueRefund — all completed and green; status Completed; end-to-end 8.7 seconds
The run whose host we destroyed mid-wait — one task id, every step green, status: Completed. · click to enlarge

Want your agents traced like this? DM me on X — onboarding early users personally.

Honest limits

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.