Governed Python Runtime¶
Audience: operators turning on, observing, or recovering the
Python-language mediation layers that sit between an untrusted
Python workload and /v1/tool — subprocess.Popen, socket.connect,
urllib/http.client/requests, open / io.open / pathlib /
os.open, plus the sys.meta_path anti-reflection finder that
defeats reload-based bypass attempts. This is the language-level
companion to the syscall-level work in
Container Hardening + Syscall Mediation:
that runbook covers execve / dlopen / connect at the libc layer;
this runbook covers the Python interpreter boundary above libc.
The two compose. The adk-python-runtime image bakes both layers:
the Python shim auto-activates via sitecustomize.py and the
LD_PRELOAD libc shim is present at
/usr/local/lib/libautonomy_preload.so for operators to enable per
invocation. Operators turn the LD_PRELOAD layer on with --ld-preload
on autonomy run python.run; the Python shim is on by default for
every interpreter start inside the image.
Walking through it first? The
demo/python-runtime/README.mdwalks the operator entry point end-to-end with the docker-run direct path and the canonicalautonomy run python.rundispatcher. This runbook assumes you already have a workload to govern and need to ship it.
Prerequisites¶
dockeronPATH. Every layer in this runbook applies via docker flags or container env injection — the Python shim runs inside the container (auto-activated bysitecustomize.py), and the hardening flags (LD_PRELOAD, seccomp, cap-drop, read-only rootfs, tmpfs) need docker to dispatch (autonomy run python.runfails loudly withErrHardeningRequiresContainerrather than silently degrade — see Failure modes below).ghcr.io/autonomyops/adk-python-runtime:<version>present locally. Pull withdocker pull ghcr.io/autonomyops/adk-python-runtime:latest, or build from source viadocker build -t ghcr.io/autonomyops/adk-python-runtime:local -f demo/python-runtime/Dockerfile .(#961 Phase 5a). The shipped image bakes theautonomyops.runtime_shimpackage +sitecustomize.pyinto the bare site-packages + the LD_PRELOAD shim at/usr/local/lib/libautonomy_preload.so. Custom images need to bake these themselves; the shim source lives underadapters/python/autonomyops/runtime_shim/.A policy bundle (or the embedded default) the in-process runtime evaluates against. The in-process runtime is the default — operators who already run an external
autonomy runtimepass--runtime-urlon the launch and the in-process startup is skipped (see Operator entry point below).
Phases and threat model¶
The Python shim is layered defense-in-depth: each phase closes a
specific class of bypass that the others cannot statically
discriminate. The LD_PRELOAD libc shim is the bottom layer — it
catches FFI escapes (ctypes.CDLL(None).execve(...)) that route
around Python’s module surface entirely. Operators choose how many
layers to apply based on the workload’s trust level.
Phase |
Layer |
Closes |
Default |
|---|---|---|---|
1 |
|
Workload spawning processes ( |
on — |
2a |
|
Direct raw-socket egress with an IP literal (and refuses hostname-based connect at the wrapper, not the gate — DNS poisoning vector) |
on |
2b |
|
Higher-level HTTP clients that resolve hostnames off-box before reaching |
on |
3 |
|
Workload reads/writes outside an allowlist ( |
on |
4 |
|
Reflection-based bypass: |
on |
5a |
|
Ships CPython 3.12 + shim baked into bare site-packages + |
shipped |
5b |
|
Two-mode runtime resolution (in-process default vs |
shipped |
6 |
|
Proof that all layers compose against a deliberately-malicious workload |
hermetic test |
7 |
Framework adapters: LangChain / AutoGen / CrewAI / LlamaIndex / DSPy |
Tool-dispatch interception when the operator writes agents against any of the five supported frameworks. Each adapter has |
optional |
What this does NOT do¶
Does not replace
/v1/tool. The Python shim is a transport layer that POSTs intent to/v1/toolBEFORE the libc call; application-layer policy mediation remains the primary trust boundary. The shim catches calls the workload doesn’t explicitly route through/v1/tool.Does not provide a guaranteed sandbox against a fully hostile attacker willing to write CPython interpreter exploits. That’s a hardened-interpreter / WASM-runtime problem out of scope. Defense-in-depth with the LD_PRELOAD layer (#960) is the realistic ceiling for a CPython shim.
Does not catch
ctypes.CDLL("libc.so.6").execve(...)—dlsym(libc_handle, "execve")finds libc’sexecvesymbol directly, bypassing LD_PRELOAD (LD_PRELOAD overrides only the global symbol resolution chain). Mitigation is seccomp via--seccomp-profileonautonomy run python.run. The shim catchesctypes.CDLL(None).execve(...)(process global namespace, RTLD_DEFAULT semantics) — the canonical LD_PRELOAD-intercepted FFI path. Both forms documented indemo/python-runtime/README.md.Does not extend to compiled languages. C++/Rust/Go workloads need the syscall layer (Container Hardening + Syscall Mediation), not language-level shims.
Does not gate the runtime’s own Python code (the framework adapters’ HTTP client, etc.). Only the workload Python subprocess(es)
autonomy runspawns underadk-python-runtime.
Operator entry point¶
The recommended path is autonomy run python.run (#961 Phase 5b),
which mirrors autonomy run ros2.launch. The dispatcher has two
modes for the /v1/tool runtime the in-container shim talks to:
Mode |
When |
What happens |
|---|---|---|
In-process (default) |
|
Dispatcher loads |
External |
|
Dispatcher skips in-process startup; |
In-process mode is the “batteries-included” default — operators get a working policy gate without standing up a second process. External mode is for production deployments where the runtime is a long-lived service (likely on a different host).
Enable (in-process default)¶
autonomy run python.run \
--image ghcr.io/autonomyops/adk-python-runtime:latest \
--policy ./bundle.tar \
--ld-preload /usr/local/lib/libautonomy_preload.so \
--read-only-rootfs \
--tmpfs /tmp \
--seccomp-profile starter \
--cap-drop NET_RAW \
--preload-exec-allowlist /usr/local/bin/python3.12 \
-- python /work/script.py
What happens, in order:
The dispatcher loads
--policy ./bundle.tar(or the embedded default if omitted), builds an evaluator + interceptor + WAL emitter, and starts an in-process tool server on a random loopback port. It announces[autonomy] runtime listening on http://127.0.0.1:<port>on stderr.The hardening flags resolve to docker dispatcher options:
--read-only,--tmpfs /tmp,--security-opt seccomp=<starter-path>,--cap-drop NET_RAW, plus the LD_PRELOAD setup (LD_PRELOAD=/usr/local/lib/libautonomy_preload.so+AUTONOMY_PRELOAD_EXEC_ALLOWLIST=/usr/local/bin/python3.12env).The container is spawned with
--network host(so the in-container shim reaches the loopback runtime) andAUTONOMY_RUNTIME_URL=http://127.0.0.1:<port>injected.python /work/script.pystarts CPython; CPython runssitecustomize.py(baked into site-packages) which callsautonomyops.runtime_shim.install(). Everysubprocess.Popen,socket.connect,open,urllib.urlopen, etc. the script does now POSTs to/v1/toolbefore any libc call.
Enable (external runtime)¶
autonomy run python.run \
--image ghcr.io/autonomyops/adk-python-runtime:latest \
--runtime-url http://10.42.0.5:7777 \
--ld-preload /usr/local/lib/libautonomy_preload.so \
--read-only-rootfs \
--tmpfs /tmp \
-- python /work/script.py
Same workload contract; the dispatcher skips in-process startup
and injects AUTONOMY_RUNTIME_URL=http://10.42.0.5:7777 into the
container instead. Default bridge network — no host-network exposure.
WAL flags do nothing in external mode.
--dir/--keep/AUTONOMY_RUN_WAL_DIRonly affect the WAL that the in-process runtime emits. In external mode the dispatcher doesn’t own a tool server, so it never observes decisions and never writes WAL frames. The external runtime owns its own audit trail — configure WAL storage on that runtime, not on the dispatcher. Combining WAL flags with--runtime-urltriggers a stderr warning (autonomy run python.run: WARN: WAL flags ... are ignored when --runtime-url is set) so the misconfiguration doesn’t silently no-op into an empty WAL directory.
Verify the loop is closed¶
On the host (or wherever the runtime is running), watch the WAL for gated calls:
# Show every decision recorded by the in-process runtime since the
# launch. Re-run after each test invocation — `autonomy wal inspect`
# reads the WAL file end-to-end on each call.
autonomy wal inspect --kind autonomy.decision --json | jq '.'
# Filter to just the Python-shim-originated POSTs (every kind the
# shim emits starts with "tool."):
autonomy wal inspect --kind autonomy.decision --json \
| jq 'select(.event.attrs.tool | startswith("tool."))'
You should see one frame per gated call, each carrying:
tool— the wire kind (e.g.tool.subprocess.spawn,tool.network.connect,tool.fs.open,tool.network.http_request,tool.os.exec,tool.os.system)outcome=allow(ordenyif the policy rejected it)policy_refmatching the bundle’smanifest.policy_refparams— the per-kind intent payload (argv for subprocess, host/port/family for connect, path/mode/intent for open, etc.)
If a known-gated call (e.g. subprocess.Popen) is NOT producing a
WAL frame, the shim did not activate. See
Step 1 — Shim did not activate below.
Common operator situations¶
1. Shim did not activate¶
Symptom: the workload runs subprocess.Popen([...]) against a
disallowed binary and the child process starts — no
PermissionError, no WAL frame.
Triage:
Verify the image actually bakes the shim:
docker run --rm ghcr.io/autonomyops/adk-python-runtime:latest \ python -c 'from autonomyops.runtime_shim import is_installed; \ print("installed:", is_installed())'
Expected:
installed: True. The image’s build-time sanity check should have caught this atdocker buildtime — if you’re seeingFalsehere, you’re running a stale image (rebuild) or a custom image that didn’t bake the shim.Check
sitecustomize.pyis in bare site-packages:docker run --rm ghcr.io/autonomyops/adk-python-runtime:latest \ python -c 'import sitecustomize; print(sitecustomize.__file__)'
Expected:
/usr/local/lib/python3.12/site-packages/sitecustomize.py. IfModuleNotFoundError: sitecustomize, the file wasn’t baked.Check for opt-out: the env var
AUTONOMYOPS_RUNTIME_SHIM_DISABLE=1on the container skipsinstall(). If your operator wrapper is setting this for debug and forgot to unset it, the shim is intentionally inert.
2. Workload sees PermissionError: autonomyops runtime unreachable (fail-closed)¶
Symptom: every gated call raises
PermissionError: autonomyops runtime unreachable (fail-closed); kind=....
The shim is fail-closed by design — when the runtime is
unreachable (connection refused, timeout, malformed response), it
raises PermissionError rather than allowing the call through.
Triage:
Did the dispatcher start the in-process runtime? Look for the
[autonomy] runtime listening on http://127.0.0.1:<port>line on the dispatcher’s stderr. The dispatcher only enters external mode when--runtime-url <url>is passed explicitly on the command line — it does NOT source the URL from any environment variable. (TheAUTONOMY_RUNTIME_URLenv var is read by the in-container Python shim’sRuntimeClientto reach the runtime; the host-side CLI’s mode selection is purely flag-driven.) Cases to check:You did pass
--runtime-urland expected in-process mode → drop the flag.You did not pass
--runtime-urlbut no listening line appears → the in-process startup failed (policy bundle parse error, port-bind failure, etc.). Re-run with verbose stderr and look for an error before the workload spawn line.
Is
--network hostactive? The default in-process mode injects--network hostso the container reaches the loopback runtime. If you’re seeing connection refused with--runtime-url http://127.0.0.1:7777set, the container’s loopback is the container’s own — the loopback runtime lives on the HOST. Either remove--runtime-url(default in-process picks the right URL automatically) or change tohttp://host.docker.internal:7777on macOS/Windows or add--add-host=host.docker.internal:host-gatewayon Linux.Did
AUTONOMY_RUNTIME_URLget injected into the container? The shim reads it on the workload side at first gate call. Check the env on the container:docker exec <container-id> env | grep AUTONOMY_RUNTIME_URL
The host-side dispatcher injects this from either the in-process bound URL (default mode) or the
--runtime-urlflag value (external mode); if it’s missing entirely, the dispatcher crashed before workload spawn — re-run and inspect stderr.
3. del sys.modules["subprocess"]; import subprocess succeeded — the wrapper got stripped¶
Symptom: the workload did del sys.modules["subprocess"]; import subprocess; subprocess.Popen(...) and the call succeeded against a disallowed binary.
This is Phase 4’s domain — the sys.meta_path post-import finder
should re-apply the wrapper on the fresh import. If it didn’t:
The Phase 4 finder is installed by
runtime_shim.install()at sitecustomize time. Check the meta_path:docker exec <container-id> python -c \ 'import sys; print([type(f).__name__ for f in sys.meta_path])'
Expected:
_PostImportFindersomewhere in the list. If absent, the shim’sinstall()either crashed mid-init or was opted out viaAUTONOMYOPS_RUNTIME_SHIM_DISABLE=1.The Phase 6 bypass-resistance test pins this exact contract. Run it locally to confirm the layers compose:
go test ./runtime/python_composition/ -v -timeout 600s
The test should PASS — every vector (including
del + reimport) asserted DENIED.
4. ctypes.CDLL("libc.so.6").execve(...) succeeded — workload escaped¶
Symptom: the workload called
ctypes.CDLL("libc.so.6").execve(b"/bin/sh", ...) and /bin/sh
ran successfully.
This is expected when only the Python shim + the LD_PRELOAD
layer are enabled. dlsym(libc_handle, "execve") finds libc’s
execve symbol directly, bypassing LD_PRELOAD (LD_PRELOAD overrides
only the global symbol resolution chain, not dlsym lookups against
specific .so handles).
The operator-facing mitigation is seccomp — it gates the syscall, not the libc symbol, so the bypass at the library layer doesn’t escape kernel mediation:
autonomy run python.run \
--image ghcr.io/autonomyops/adk-python-runtime:latest \
--seccomp-profile starter \
-- python /work/script.py
--seccomp-profile starter is the embedded starter profile (denies
21 syscalls — see Container Hardening §Phase 1).
For tighter custom profiles, point at a JSON file:
--seccomp-profile /path/to/myprofile.json.
The shim DOES catch ctypes.CDLL(None).execve(...) (the canonical
LD_PRELOAD-intercepted FFI path). Verified by Phase 6’s
vector 4 test.
5. A legitimate workload is being denied (false positive)¶
Symptom: an operator-authored Python tool that should run is being
denied with PermissionError: autonomyops policy denied <kind>: <reason>.
Triage:
Identify the wire kind from the error:
tool.subprocess.spawn— argv check failedtool.network.connect— IP+port check failedtool.network.http_request— URL-layer check failedtool.fs.open— path/mode check failedtool.os.system— shell-command check failedtool.os.exec— exec-family check failed
Find the most recent WAL frame for that kind:
autonomy wal inspect --kind autonomy.decision --json \ | jq 'select(.event.attrs.tool == "tool.subprocess.spawn") | {tool: .event.attrs.tool, outcome: .event.attrs.outcome, reason: .event.attrs.reason, argv: .event.attrs.argv}'
Update the policy bundle to allow the legitimate intent. Re-deploy: pass the updated bundle via
--policyon the nextautonomy run python.runinvocation.
For development iteration where the bundle is a moving target,
operators run an external runtime with the live bundle attached and
pass --runtime-url so the dispatcher doesn’t re-snapshot the bundle
on every launch.
6. The dispatcher refuses to start with ErrHardeningRequiresContainer¶
Symptom: autonomy run python.run exits with
ErrHardeningRequiresContainer: <flag> requires --image (or a
similar variant).
The hardening flags (--seccomp-profile, --cap-drop,
--read-only-rootfs, --tmpfs, --ld-preload) only apply via
docker — they have no native-subprocess analog. The dispatcher
fails loudly rather than silently dropping them.
Triage:
Pass
--image ghcr.io/autonomyops/adk-python-runtime:<ver>(or your custom image) explicitly. Thepython.rundispatch token has no embedded default image (this is intentional — the operator’s image choice is the trust anchor).If you need to debug without the hardening flags, drop them one at a time until the dispatcher accepts the invocation; that’s the layer that needed an image.
7. The framework adapter’s _run is not being gated¶
Symptom: an operator-authored LangChain / AutoGen / CrewAI /
LlamaIndex / DSPy tool body runs without /v1/tool ever seeing
the intent.
Triage:
Identify which adapter is in use:
autonomyops.langchain.GuardedTool— wraps an existing LangChainBaseToolinstance.autonomyops.langchain.RuntimeTool— full-delegation; no Python body runs.And similarly for
autogen,crewai,llamaindex,dspysubpackages.
Confirm the agent actually invokes the wrapped tool (not the pre-wrap one). The operator code path is:
from autonomyops.dspy import GuardedTool # or any framework safe = GuardedTool(tool=my_tool, action="tool.my_action") agent = SomeFramework.ReAct(signature, tools=[safe]) # NOT [my_tool]
If the framework auto-discovers tools (e.g. by introspecting a registry), make sure the WRAPPED instance is registered and the bare one is not. The wrappers do NOT replace the bare tool in any external registry — that’s the operator’s job.
Every adapter has 14–17 unit tests pinning the “deny is not overridable” invariant. Run the suite locally to confirm the adapter itself is intact:
pytest adapters/python/tests/test_<framework>_tool_guard.py \ adapters/python/tests/test_<framework>_runtime_tool.py -v
Failure modes and recovery¶
Mode A — shim fails to install at sitecustomize time¶
Trigger: from autonomyops.runtime_shim import install crashed
during import (e.g. corrupt site-packages, missing transitive
dependency, ABI mismatch between CPython and a baked extension).
The image’s Dockerfile runs a build-time sanity check (python -c "import autonomyops.runtime_shim; assert is_installed()")
so if the wiring is broken at build time, the image build FAILS
before shipping. If you’re hitting this at run time on a shipped
image, the image’s environment was mutated post-build (most
commonly: an operator-supplied wrapper pip install-ed a
package that overwrote sitecustomize.py).
Recovery:
Confirm the image’s stock
sitecustomize.pyis still in place:docker run --rm <your-image> \ cat /usr/local/lib/python3.12/site-packages/sitecustomize.py
If
pip installfrom your operator wrapper is the culprit, move the install AHEAD of the shim install OR use apthfile that chains bothsitecustomizemodules (the bare one + yours).If the shim itself is broken (rare), pin to a known-good image tag and file a bug — the build-time sanity check should have caught it.
Mode B — runtime fail-closed on a transient network blip¶
Trigger: the in-process runtime briefly stalled (GC pause, file I/O on a slow disk, etc.) and a gated call timed out.
The shim fail-closes (PermissionError) — by design, the runtime
is the sole policy authority and a missed verdict cannot be
treated as allow. The workload’s call propagates the
PermissionError to the caller.
Recovery:
Inspect the runtime’s logs (stderr of the dispatcher) for
gate_callwarnings — the shim logs the transport failure before raising. Look for stalls of >10s.If the in-process runtime is on the same host as the workload (in-process mode default), CPU contention from the workload itself can starve the runtime. Pin the dispatcher’s process to a separate CPU set via
tasksetif you see chronic timeouts under load.For production deployments, run an external runtime and pass
--runtime-url— that gives the runtime its own resource envelope.
Mode C — --read-only-rootfs blocks a legitimate write¶
Trigger: the workload tries to write under / (the rootfs is
read-only) outside the --tmpfs /tmp mount, hitting EROFS
(distinct from the Phase 3 tool.fs.open deny — that’s a policy
verdict; this is a kernel-level read-only refusal).
The workload sees OSError: [Errno 30] Read-only file system
from the syscall path. The Phase 3 wrapper still POSTs the
tool.fs.open intent, so the operator sees the gated intent in
the WAL even though the underlying syscall would have failed
anyway.
Recovery — two operator-exposed knobs on
autonomy run python.run cover the common cases:
Ephemeral scratch (lost on container exit) →
--tmpfs. Add the path to the tmpfs mount set:--tmpfs /tmp --tmpfs /var/cache/myapp. Each--tmpfsflag adds one writable in-memory mount. Pick this for/tmp-style scratch the workload doesn’t need to outlive the container.Persistent storage (must survive container exit) →
--writable-mount(#1005). Bind-mount a host path into the container:--writable-mount /var/log/myapp:/var/log/myapp(writable by default) or--writable-mount /srv/data:/data:ro(read-only — operator-supplied material the workload should NOT modify, e.g. signed certs, immutable config). Each flag takes one<host>:<container>[:ro|:rw]spec; repeat for multiple mounts or comma-separate. Both paths must be absolute, the host path must exist at parse time, and..segments in the container path are rejected. Works alongside--read-only-rootfs: the rootfs stays read-only while the named paths are writable.Host-side WAL audit — in the default (in-process runtime) mode the dispatcher emits one
writable_mount_declaredWAL event per mount BEFORE workload start, soautonomy wal inspectshows the (host, container, mode) tuple even if the workload crashes during startup.CAVEAT (external-runtime mode): when
--runtime-urlis set the dispatcher does NOT own a host WAL, so thewritable_mount_declaredemit is skipped — the mounts still land in the container, but the host-side audit trail does not. The dispatcher prints a loudWARN: --writable-mount ... SKIPPED when --runtime-url is setline on stderr so the operator notices. Mitigations: (a) configure equivalent audit on the external runtime’s own WAL, or (b) drop--runtime-urlto use the in-process runtime that emits the per-mount audit event before dispatch. Theautonomy ros2 runsurface andautonomy run ros2.launchare unaffected — both always own their WAL emitter and audit unconditionally.Last-resort: omit
--read-only-rootfsand accept the looser posture for this workload — the container rootfs becomes writable, reverting to Docker’s default. The Phase 3tool.fs.openwrapper still gates all path-based opens through/v1/tool, so policy enforcement remains intact; only the kernel-level read-only enforcement is dropped. Pick this only when the workload’s writable surface is too ad-hoc to pre-declare via--tmpfs/--writable-mount(rare; usually means the workload is doing something unaudited).
Mode D — image build fails the sanity check¶
Trigger: docker build -t adk-python-runtime -f demo/python-runtime/Dockerfile .
fails at the RUN python -c "import autonomyops.runtime_shim; assert is_installed()"
line.
This is intentional — the image’s Dockerfile fails the build BEFORE shipping rather than at runtime in the operator’s container.
Recovery:
Check the Dockerfile’s COPY paths against your local checkout. The build context is the repo root (not
demo/python-runtime/) because the preload-builder stageCOPYsruntime/preload/src/and the runtime stageCOPYsadapters/python/. Run from repo root:docker build -t adk-python-runtime:local \ -f demo/python-runtime/Dockerfile .
If
adapters/python/is missing files (typically a partialgit cloneorgit sparse-checkout), thepip install ./autonomyops-adapterstep fails earlier with a setuptools error. Re-clone or rungit checkout HEAD -- adapters/python/.If the build succeeds but
is_installed()returns False, thesitecustomize.pyplacement is off. Confirm the Dockerfile’sCOPY adapters/python/autonomyops/runtime_shim/sitecustomize.py /usr/local/lib/python3.12/site-packages/sitecustomize.pyline matches the actual location ofsitecustomize.pyin your checkout (it moved between #961 phases).
Verification¶
Every Python-runtime change has a regression-test pair: the hermetic Phase 6 composition test for the layers, and the per-framework adapter tests for the wrappers. Run them locally to confirm a clean checkout works end-to-end.
Hermetic bypass-resistance composition test¶
go test ./runtime/python_composition/ -v -timeout 600s
Builds the adk-python-runtime image at test time, derives a tiny
test image with a deliberately-malicious Python workload, stands up
an in-process mock tool server with a narrow allowlist, and asserts
every documented bypass vector is caught by ONE of the shipped
layers. Gated on linux + docker daemon + !testing.Short();
skips cleanly on hosts without docker. See
runtime/python_composition/bypass_resistance_test.go
for the full vector matrix.
Per-framework adapter unit tests¶
cd adapters/python
pytest tests/ -v
262 tests with pytest.mark.skipif gating on each optional
framework extra. Hosts without a framework installed cleanly skip
that framework’s adapter tests — no CI changes needed. The central
“deny is not overridable” DoD invariant is pinned per-class for
all five frameworks.
Image bake sanity check¶
docker run --rm ghcr.io/autonomyops/adk-python-runtime:latest \
python -c 'from autonomyops.runtime_shim import is_installed; \
assert is_installed(), "shim not active"'
echo "shim OK: $?"
If this exits non-zero, the image is broken — re-pull or re-build (#961 Phase 5a).
Cross-references¶
Container Hardening + Syscall Mediation — the libc-layer companion runbook (
#960). Covers seccomp + LD_PRELOAD + cap-drop + read-only rootfs that the python.run dispatcher inherits.demo/python-runtime/README.md— operator-facing reference for the image. Has the full wire-format table for eachtool.*kind the shim emits.adapters/python/autonomyops/runtime_shim/— shim source. One file per phase (_subprocess.py,_network.py,_http.py,_fs.py,_meta_path.py,_os_exec.py,_gate.py).runtime/python_composition/bypass_resistance_test.go— the hermetic composition test the operator-facing verification step above runs.runtime/preload/README.md— LD_PRELOAD shim contract + supported allowlist forms.adapters/python/autonomyops/{langchain,autogen,crewai,llamaindex,dspy}/— framework adapters. Each subpackage has aGuardedTool(local execution) andRuntimeTool(full delegation).#961— epic this runbook documents.#960— container hardening epic the LD_PRELOAD layer comes from.