agentnode-sdk 0.24.1 — the sandbox does what it said
Released 2026-09-07 from candidate 82c96cc8028568c0ff024baabc4911e369ec0a73, tagged v0.24.1
at merge commit 2c513e743a393f242904b39f75d25b55f671493c, under founder authorisation.
Three defects in the local sandbox runtime, found by the backend conformance suite measuring a real
container rather than reading its configuration. Each was a property AgentNode reported and did not
enforce. This is a narrow patch: it corrects the runtime and nothing else.
What is fixed
A wall-clock timeout now ends the payload. It used to kill the docker run client — a pipe to a
daemon that owns the process — so the SDK reported a timeout while the container kept running, and
--rm never removed it because it never exited. Every run now carries an exact identity, a unique
name and a cidfile; a timeout removes that exact container, waits, and verifies that neither the id
nor the name remains. If any of that cannot be shown, SandboxContainmentError is raised instead of
an ordinary timeout result: a stop nobody could verify is not a stop.
The declared memory ceiling now binds. --memory was passed without --memory-swap, and a
runtime with only the first set grants a swap allowance of twice the limit — the suite watched a
768 MiB allocation finish with exit code 0 under a 512 MiB "limit". The two now carry the same value,
so the total of memory and swap is the limit.
A refusal now carries a way out. It said no container runtime (docker or podman) found on PATH
and nothing more. One classifier now distinguishes not installed, not running, not permitted,
an engine in the wrong mode, a machine that cannot hold the memory ceiling, a missing image, a
build without a pinned image, and a device where no local sandbox is possible — and offers only
actions that exist on the platform in front of you, each followed by the check that tells you whether
it worked. Joining the docker group is deliberately not among them: it is administrator-equivalent
power on the machine.
What this release does NOT fix
Declared network levels are still not distinguished from one another. A toolpack that declares
restricted receives the same open container network as one that declares unrestricted: six
recognised level names all reach the engine's default bridge. An unknown or missing level is still
correctly isolated (--network none), so this is not an unrecognised value being let through — the
recognised names simply do not differ in what they grant. The risk score meanwhile rates internal
below external, which points the opposite way from what the runtime does.
The correction requires a declared domain allowlist in the package manifest, registry validation for
it, and a refusal for packages that ask for restricted without one. That refuses packages which
work today, which is too large a change to carry inside a patch whose purpose is to get two proven
runtime fixes to people. It ships separately, at a version that reflects the break.
The artefacts
Built on Linux from the frozen candidate 82c96cc8028568c0ff024baabc4911e369ec0a73, with
SOURCE_DATE_EPOCH=1788542156:
sha256 4463b6ccdd2ec58d8540b8eb416002491dd3502053814590f933a66a736ee42b 414010 agentnode_sdk-0.24.1-py3-none-any.whl
sha256 d016ea75978de3b8d0283972a0292b1d7545d35e5e93459cf97c9f40815b2af8 865827 agentnode_sdk-0.24.1.tar.gz
They must be Linux-built. A Windows build of the same commit is not byte-identical, because git on
Windows hands the build CRLF.
What was measured against them
Run 33900377096, three jobs, all green:
- an independent runner rebuilt both files from the same commit and arrived at the same bytes;
- the verify job re-hashed what it downloaded and refused to continue unless it matched;
twine checkpassed on both; the wheel declaresName: agentnode-sdk,Version: 0.24.1,
Requires-Python: >=3.10and six runtime dependencies includingmcp<2,>=1.0.0;- the wheel holds 99 files under two top-level entries, with no bytecode, no tests, no dotenv and
no key material; - the sdist is a source archive and holds 277 entries, 154 of them the test suite, plus
.env.example— a template whose only value is the placeholderank_your-api-key-here. That is
what a source distribution is for, and an earlier draft of these notes wrongly described both
distributions as carrying neither tests nor dotenv material. The sdist carries no bytecode and no
key material; - the wheel was installed alone in a clean virtualenv: it imports from site-packages, reports
agentnode 0.24.1, resolvesmcp 1.29.1, andagentnode sandbox doctor --jsonemits valid JSON; - the sdist installs separately and reports the same version;
- the conformance suite ran against the installed wheel: 23 properties passed, 0 failed, 0
unmeasured, 1 not applicable (log-retention, which a local backend has nothing to retain for),
conformant; - the three corrections above were proved against the installed wheel, not against a checkout: a
payload that ignores every signal it may ignore was ended and its container was gone by id and
by name, with the runtime required to answer rather than merely fail; a 768 MiB allocation under
the 512 MiB ceiling was killed (rc=137); and all eight refusal cases carry an executable action
and a re-check, with actions requiring an absent tool withheld rather than printed.
Compatibility
Against 0.24.0, the public surface grows; nothing in it is removed or renamed. The wheel's own
inventory lists the additions: the agentnode_sdk/conformance/ package (__init__, checks,
doubles, probe, report, runner), agentnode_sdk/sandbox/refusal.py, and three new fields on
SandboxAvailability (probe_error, engine_os, memory_limit_enforceable). No module, class,
function or configuration key present in 0.24.0 is absent from this wheel. The shipped configuration
defaults are unchanged: sandbox.host_trust_policy remains curated_only.
One behaviour changes, and it is the point of the release. A run that reached its wall-clock
limit previously left its container running; it is now removed, and a run whose removal cannot be
verified raises SandboxContainmentError instead of returning an ordinary timeout result. Code that
treated a timeout as "nothing is still running" was relying on something that was not true; code that
catches the timeout path should also expect SandboxContainmentError.
A program that previously completed while exceeding the declared memory ceiling will now be killed by
the runtime. That is the ceiling beginning to bind, not a regression.
What these notes do not establish
Nothing about platforms other than the Linux runner the artefacts were built and measured on: the
wheel is py3-none-any, and no measurement here was taken on Windows, macOS or WSL2. Nothing about
behaviour on a machine without a container runtime beyond the refusal paths exercised in-process.
Nothing about publication — no tag, release or upload exists.