Session latency: what an agent's code tool pays per call
Measured docker, runc (container tier, 15 repetitions, 2026-10-01 22:35 UTC); docker, gVisor (kernel tier, 15 repetitions, 2026-10-01 22:36 UTC); openshell (container tier, 15 repetitions, 2026-10-01 23:02 UTC). Machine: Intel(R) Core(TM) Ultra 9 275HX, 24 CPUs, linux/amd64. Image for plimsoll: plimsoll/sandbox-python:latest. plimsoll's providers were called directly, not through the daemon, whose overhead plus the client's was about 3 to 4 ms a round trip in the TypeScript client's tests. Each timed call followed an untimed pause of 250 ms, because an agent's calls are seconds apart (its model thinks between them) and a docker session finishes its cleanup after answering.
No network is in these numbers: the providers ran beside the program timing them. A daemon hosted away from its caller adds that network round trip to every call. The terms session, cell and relay are defined in the sessions guide (EXTERNAL · source repo ↗); the time a warm pool takes off a session's first call is on the warm-pool page (INTERNAL · measurement →).
What keeping state buys
A call that uses an array of 10 million values (80 MB) an earlier call loaded, against a call that has to build it again:
- docker, runc: 18.1x faster (20 ms against 352 ms)
- docker, gVisor: 31.3x faster (23 ms against 714 ms)
- openshell: 91.8x faster (10 ms against 939 ms)
The more an agent's data costs to load, the larger the difference; building this array takes about 0.1 s of plain compute, and parsing a real file of that size takes longer.
Every operation
Medians, with the 10th and 90th percentiles
| Operation | docker, runc | docker, gVisor | openshell |
|---|---|---|---|
| fresh snippet a new sandbox per call: console.log(1) | 221 ms 186 to 229 | 272 ms 246 to 306 | 554 ms 551 to 608 |
| fresh Python run a new sandbox per call: project, python3 main.py | 249 ms 235 to 426 | 429 ms 404 to 617 | 595 ms 545 to 602 |
| session open create, verify and list a session's sandbox | 228 ms 204 to 240 | 260 ms 233 to 436 | 554 ms 550 to 556 |
| session snippet call console.log(1) in an open session (openshell sweeps before answering, docker after) | 96 ms 84 to 106 | 92 ms 88 to 115 | 36 ms 35 to 63 |
| first JavaScript cell starts the interpreter (a new session, opened untimed) | 220 ms 200 to 235 | 302 ms 283 to 326 | 106 ms 104 to 108 |
| warm JavaScript cell n = (globalThis.n ?? 0) + 1 in a live interpreter | 14 ms 13 to 18 | 16 ms 15 to 18 | 6 ms 5 to 10 |
| first Python cell starts the interpreter (a new session, opened untimed) | 215 ms 197 to 225 | 372 ms 369 to 389 | 101 ms 98 to 117 |
| warm Python cell n = globals().get('n', 0) + 1 in a live interpreter | 14 ms 12 to 15 | 15 ms 14 to 18 | 5 ms 4 to 6 |
| reload: fresh run per call every call builds a 10-million-value array (80 MB) and takes its mean | 352 ms 326 to 538 | 714 ms 700 to 885 | 939 ms 884 to 1095 |
| reload: kept in the interpreter the array from an earlier cell: a.mean() | 20 ms 18 to 23 | 23 ms 21 to 25 | 10 ms 9 to 12 |
| call after a suspend Suspend, then console.log(1): the resume is in the call | 151 ms 134 to 159 | 144 ms 136 to 164 | 875 ms 794 to 1014 |
docker inspect that reads the
container's settings back, then, for a snippet, one docker exec that starts a node process. A cell starts
no process: it goes as one line to a relay the session keeps attached beside the interpreter (since 2026-10-01). The sweep that
proves no process of the call is left, a second docker exec, runs after the answer has gone back; a call that arrives
while it runs waits for it. OpenShell does the same since 2026-10-01: a relay on an exec stream held open, and the sweep after
answering.The data and how to rerun it
Raw samples beside this page: docker.json, docker-runsc.json, openshell.json. The
programs are in measurements/session-latency
(EXTERNAL · source repo ↗). From a checkout, with the images make docker-images builds (gVisor installed for
the runsc run, and an OpenShell gateway with the SANDBOX_OPENSHELL_* settings in the environment for the
openshell run):
go run ./measurements/session-latency -provider docker
go run ./measurements/session-latency -provider docker -runtime runsc
go run ./measurements/session-latency -provider openshell
go run ./measurements/session-latency/report > docs/measurements/session-latency/index.html
Your numbers will differ from these: they are one machine's. What should carry over is the order of the rows and the ratios between them.