Repository navigation
plimsoll v0.20.0
plimsoll now plugs into six agent frameworks: Agno, CrewAI and Google ADK in the Python client, and the Vercel AI SDK, Trigger.dev and Mastra in the TypeScript client. Each hands the model's code to a plimsoll daemon you run, behind the isolation tier you require (kernel by default), and answers with the run record the client checked. E2B now keeps sessions, with grants inside them, and a session on a provider billed by the second draws on the daily paid allowances. This release also carries the fixes from five security reviews and moves to Go 1.26.9. Read Upgrading before you deploy it: six changes need action.
Upgrading
- The egress guard has a listener of its own. With
E2B_GUARD_URLorSANDBOX_DOCKERCLOUD_GUARD_URLset,PLIMSOLL_GUARD_ADDRis required: the guard is served there and nowhere else, and the daemon refuses to start without it. Point the public guard URL at that address. Code in a granted sandbox may reach the guard, and on the shared listener it could also reach the RPC procedures and the health endpoints. E2B_GUARD_URLmust name a host, not an IP address: E2B's network rule takes a domain name and refuses an address at create.- Images that carry
/runner.mjsmust carry this release's. The runner now reports a step's output as base64 of its raw bytes, so output that is not UTF-8 survives exactly. A daemon of this release refuses a report from an older runner as a protocol error.make docker-imagesrebuilds the shipped images; rebuild an OpenShell image of your own the same way. - The session owner digest changed. Clients now key it with a value derived from the caller token, not the token itself (with a token over 64 bytes, the old digest could be tested against the clients file's stored hash). Upgrade every client of a daemon together: during a mixed rollout, one user counts as two owners.
- A docker session that mixes cells and project calls loses its cell state at each project call. Nothing of a session's own code may run while plimsoll starts the project runner, whose input carries the key its report is authenticated with, so the interpreters are killed first. The next cell reports
interpreter_started; the work directory's files are untouched. A session that runs only cells, or only project calls, is unaffected. - Go 1.26.9 or newer. Go 1.26.6 has eleven standard-library advisories published on 2026-10-08, among them an HTTP/2 server crash and HTTP/2 memory exhaustion in code the daemon calls.
golang.org/x/netmoves to v0.60.0.
Agent frameworks
- Python: Agno, CrewAI and Google ADK.
plimsoll_client.agno.PlimsollTools,plimsoll_client.crewai.PlimsollCodeToolandplimsoll_client.adk.PlimsollCodeExecutorrun each call in a fresh sandbox through one shared helper,plimsoll_client.execution.CodeExecutor(Python or JavaScript, the call's files written first, a kernel floor and a 30 second budget by default). Install with the extras:pip install 'plimsoll-client[agno]',[crewai]or[adk]. Guides: the Python client README and docs/google-adk.md. - TypeScript: the Vercel AI SDK.
@plimsollmark/client/ai-sdkgivesstreamTextanexecuteCodetool, fresh by default or, withforConversation({ userId, conversationId }), one sandbox per conversation whose variables and files survive between calls on a daemon that keeps sessions. docs/ai-sdk.md. - Trigger.dev and Mastra add-ons now require the kernel tier unless you lower it (they accepted any tier before), cap the sandboxes one user holds (
maxSessionsPerOwner, default 3, closing that user's least recently used idle one), and take the user from your own login:warm(runId, userId)on Trigger.dev, anowneroption on Mastra for agent networks. - Whether the code ran is never left to a guess. Every tool says, before anything else, "Nothing ran (reason)" when plimsoll refused the call before it started, and "The code may have run" when an answer was lost after it might have. Nothing is ever retried automatically. A run with no step report is an error that says it may have run, not an invented exit code.
- Cancelling a call cancels its request. In every Python tool,
AsyncClient, and ADK through its guard: a request not yet sent is never sent, and one in flight has its connection cut. On docker, a cancelled run's container was gone a quarter of a second after the cancel. - Framework traps closed. CrewAI's tool cache, when a crew turns it on, can no longer answer for plimsoll's tool. ADK's guard (
PlimsollCodeExecutorGuard, registered first on the Runner) refuses the setting that swaps plimsoll for Gemini's built-in executor, refuses a Runner with no artifact service before anything runs, and gives the model the code-block instruction ADK no longer adds; ADK's CSV preprocessing, which assumes state a fresh sandbox does not keep, is refused.
Sessions
- E2B keeps sessions. One microVM for many calls, files and interpreter state kept between calls; a suspend is E2B's pause, so files survive it and interpreters do not. The session conformance suite passed against the live service. docs/sessions.md#e2b.
- Grants inside E2B sessions, off unless
E2B_SESSION_GRANTSchooses one of two designs:session(one guard credential for the session's life, serving only the grant of the call in progress) orcall(a network rule with a fresh credential put on for each granted call and taken off after). Both passed live, before and after a suspend. - Paid session time is charged. On E2B and Docker Cloud a session draws on
SANDBOX_PAID_SECONDS_PER_DAYand each caller'spaid_seconds_per_dayfor the time its sandbox runs, reserving ahead what a call could cost. - A per-user cap in the daemon.
SANDBOX_MAX_SESSIONS_PER_OWNERcaps the open sessions of one user of one caller. Clients name the user inOpenSessionas a digest under a key derived from the caller's token, never the user's ID. At the cap, the open closes that user's least recently used idle session, whose later calls are refused asreplaced, not dispatched. An open the daemon refuses closes nothing.Describestates the per-caller and per-owner caps. - A session the daemon no longer knows (after a restart, say) is an end, not an error a client retries.
Grants
route_max_callscaps one allowed route, for a route whose call spends, such as a GPU job's submit: past the cap the broker answers 429 and the call never reaches the API. A call counts against every capped entry it matches, compared without regard to letter case, so neither a wider wildcard nor the order ofallowlets calls around a cap; in a session the count spans the session. docs/capability-grants.md.
Security
Fixes from the five security reviews of this release. None was critical.
- A project call in a docker session starts with nothing of the session's own code running. Before it, every process of the session's is killed, the interpreters it keeps included. Until a program of plimsoll's has loaded the library that makes it unreadable, a process of the same user could open its input, output and memory, and a project call's input carries the key its report is authenticated with. So a session that runs cells loses their interpreter state at its next project call, and the next cell reports
interpreter_started; its files are untouched, and a session that runs only project calls keeps no process of its own and pays nothing. Pausing the interpreters instead would not hold: a paused process can arrange its own resume before it is paused. A snippet and a cell carry no key and are not protected this way; a grader whose verdict the graded code must not be able to forge uses a project call. docs/sessions.md#docker. - A request refused before its body is read (bad credential, missing scope, decode capacity, an unknown path, the guard's refusals) answers at once and closes the connection, instead of first waiting for a body the client holds back. A declared body over the cap is refused from its header,
OPTIONS *reaches the daemon's own checks, and each listener holds at most its share of half the process's descriptor limit in open connections. - Every local setting, and every listener's bind, is checked before the startup smoke test, which on E2B and Docker Cloud creates a billed microVM; a listener that fails drains in-flight runs instead of exiting on the spot.
- A sandbox whose delete gave up stays charged to the paid allowances until the provider's own lifetime for it ends, including across UTC midnight, for runs, sessions and failed session opens.
- A sandbox ID from a provider's create answer is used, to run code or to delete, only once it is proven to be the sandbox that create made.
- A file planted in a session's work directory can no longer hold a later call until its deadline.
- An idle timer that fired late no longer suspends a session used since.
attestrefuses a bundle line without its signed link as it reads it.- The export to this repository stages committed content only, and a paid live test needs its make target's flag, not just a key in the environment.
- Development dependencies: the
wsthe Trigger.dev add-on's test environment resolved (throughsocket.io-client, which pins itsengine.io-clientexactly) moves to 8.21.3, and the example'star6 branch is gone, both through scopedoverrides; the Python build lock moves to setuptools 84.0.0, which builds the same wheel and sdist contents, checked file by file. The published packages have no dependencies, so none of this changes what you install. What remains has no fixed release upstream:bracesthrough the Trigger.dev CLI's ownchokidar3,sprintf-jsthrough@mastra/core, and ChromaDB, which CrewAI installs (thecrewaiextra; noted in the Python client's README).
Clients
- Python:
CancelHandleandRequestCanceledError;open_session(owner=...); a Describe refusal keeps the daemon's reason; a session end this client has no name for reads asunknown; a failure before any byte is sent is marked not dispatched. - TypeScript:
openSession({ owner });Info.maxSessionsPerOwner; a session is closed once however many holders close it; a call aborted while Describe or the open is pending settles at once; a warm never evicts a conversation in use. plimsoll-client0.20.0 on PyPI and@plimsollmark/client0.20.0 on npm.
Docker Cloud
SANDBOX_DOCKERCLOUD_STORE_IMAGEboots an image from the account's own Cloud Sandboxes image store by ID, checking the booted digest on every run.- The account's sandbox quota is refused as capacity, not dispatched, instead of an internal error.
Daemon
- An environment variable this daemon does not read is named in a startup warning, so a misspelt setting is not silently ignored.
- Module results accept every output width the simulation worker accepts.
Docs
- Guides for each framework, and the session caps with their one-process and per-daemon limits.
noexecon a run's writable mounts is a second layer, not a wall against native code; the container is the boundary.- The session latency and warm pool measurements are public, with their raw samples.
Contributors
- The framework suites run in their own environments, installed from hashed locks (
make py-frameworks);make clients-suiteruns them with the client suites. plimsoll-clientis published from GitHub Actions through PyPI's trusted publishing; no token exists.- Two docs tests fail on a relative link in a package README and on a release-status sentence with no version.