A LangChain sandbox tool that runs agent-generated code inside Kubernetes pods
Project description
langchain-kubernetes-agent-sandbox
Run agent-generated code inside Kubernetes pods via
k8s-agent-sandbox.
KubernetesAgentSandbox adapts a k8s_agent_sandbox.Sandbox to the
deepagents.backends.sandbox.BaseSandbox protocol, so it can be used in two
ways:
- As a
deepagentsbackend — the pod is the agent's filesystem and shell; state persists across turns. - As a LangChain tool — the agent keeps an in-memory virtual filesystem
and calls an
execute_pythontool that spawns an ephemeral pod per run.
| Sandbox as backend | Sandbox as tool | |
|---|---|---|
| Lifetime | Lives for the whole agent session | Spawned per run, terminated at end |
| State across turns | Yes (files + shell) | No |
| Best for | Long, stateful coding sessions | Stateless code execution, lower idle cost |
| Wiring | One kwarg on create_deep_agent |
Custom @tool + VFS seeding |
Installation
pip install langchain-kubernetes-agent-sandbox
Requires Python 3.12+. You also need access to a Kubernetes cluster that the
k8s-agent-sandbox client can reach (your local kubeconfig context is used by
default).
Pattern 1: Sandbox as a deepagents backend
The sandbox is created once and passed to create_deep_agent as backend=.
The agent's shell and file tools are routed to the pod for the lifetime of the
agent.
from deepagents import create_deep_agent
from k8s_agent_sandbox import SandboxClient
from k8s_agent_sandbox.models import SandboxLocalTunnelConnectionConfig
from langchain_kubernetes_agent_sandbox import KubernetesAgentSandbox
SANDBOX_TEMPLATE = "python-3.12" # any template available in your cluster
SANDBOX_NAMESPACE = "agent-sandboxes"
def build_sandbox() -> KubernetesAgentSandbox:
client = SandboxClient(connection_config=SandboxLocalTunnelConnectionConfig())
raw_sandbox = client.create_sandbox(
template=SANDBOX_TEMPLATE,
namespace=SANDBOX_NAMESPACE,
)
return KubernetesAgentSandbox(sandbox=raw_sandbox)
sandbox = build_sandbox()
agent = create_deep_agent(
model="claude-sonnet-4-6",
backend=sandbox,
)
result = agent.invoke({
"messages": [
{"role": "user", "content": "Write a Python script that prints the first 10 primes and run it."},
],
})
# When you're done with the session:
sandbox.sandbox.terminate()
Files uploaded with sandbox.upload_files([...]) (absolute paths only) are
visible to the agent for the rest of the session.
Pattern 2: Sandbox as a tool (execute_python)
The agent runs with an in-memory virtual filesystem (state["files"]). When it
calls execute_python, the tool lazily spawns a sandbox, copies the VFS into
/app/, runs the code, and the caller terminates the sandbox at the end of the
run.
import base64
from typing import Annotated
from deepagents import create_deep_agent
from k8s_agent_sandbox import SandboxClient
from k8s_agent_sandbox.models import SandboxLocalTunnelConnectionConfig
from langchain_core.tools import tool
from langgraph.prebuilt import InjectedState
from langchain_kubernetes_agent_sandbox import KubernetesAgentSandbox
SANDBOX_TEMPLATE = "python-3.12"
SANDBOX_NAMESPACE = "agent-sandboxes"
UPLOAD_DIR = "/app"
TOOL_MODE_PROMPT_SUFFIX = (
"\n\nYou have an `execute_python(code)` tool that runs Python 3 in a fresh "
"Kubernetes sandbox. The sandbox is spawned on the first call within this "
"turn and terminated when the turn ends — do not rely on state across "
"tool calls. Any files in your virtual filesystem are copied into `/app/` "
"inside the sandbox before each call, so reference uploaded files by their "
"`/app/<name>` path."
)
def _new_sandbox() -> KubernetesAgentSandbox:
client = SandboxClient(connection_config=SandboxLocalTunnelConnectionConfig())
raw = client.create_sandbox(template=SANDBOX_TEMPLATE, namespace=SANDBOX_NAMESPACE)
return KubernetesAgentSandbox(sandbox=raw)
# Per-run sandbox handle. Replace this module-level dict with whatever
# request-scoped storage your app uses (e.g. Streamlit session_state, a FastAPI
# request, an asyncio context var).
_run: dict = {}
def _get_or_spawn_run_sandbox(vfs_files: dict) -> KubernetesAgentSandbox:
sb = _run.get("sandbox")
if sb is not None and sb.sandbox.is_active:
return sb
sb = _new_sandbox()
_run["sandbox"] = sb
payload: list[tuple[str, bytes]] = []
for path, data in (vfs_files or {}).items():
content = data.get("content", "")
encoding = data.get("encoding", "utf-8")
raw = base64.b64decode(content) if encoding == "base64" else content.encode("utf-8")
sb_path = path if path.startswith("/") else f"{UPLOAD_DIR}/{path}"
payload.append((sb_path, raw))
if payload:
sb.upload_files(payload)
return sb
def _terminate_run_sandbox() -> None:
sb = _run.pop("sandbox", None)
if sb is not None:
try:
sb.sandbox.terminate()
except Exception:
pass
@tool
def execute_python(code: str, state: Annotated[dict, InjectedState]) -> str:
"""Execute a Python 3 snippet in an ephemeral Kubernetes sandbox.
Files in the agent's virtual filesystem are copied into /app/ before
execution. Returns combined stdout/stderr with the exit code.
"""
sb = _get_or_spawn_run_sandbox(state.get("files") or {})
b64 = base64.b64encode(code.encode("utf-8")).decode("ascii")
cmd = (
"python3 -c "
"'import base64,sys;"
f"exec(compile(base64.b64decode(\"{b64}\").decode(),\"<agent>\",\"exec\"))'"
)
resp = sb.execute(cmd)
return f"exit={resp.exit_code}\n{resp.output}"
agent = create_deep_agent(
model="claude-sonnet-4-6",
tools=[execute_python],
system_prompt="You are a helpful assistant." + TOOL_MODE_PROMPT_SUFFIX,
)
try:
result = agent.invoke({
"messages": [{"role": "user", "content": "Plot the first 10 primes."}],
# Seed the VFS with files the agent should see at /app/<name>:
# "files": {"/app/data.csv": {"content": "...", "encoding": "utf-8"}},
})
finally:
_terminate_run_sandbox()
Key points:
- The sandbox is created lazily on the first
execute_pythoncall within a run and reused for subsequent calls in the same run. - VFS files are seeded into
/app/before each call; the agent should reference them by absolute path (/app/<name>). - The caller is responsible for terminating the per-run sandbox in a
finallyblock — the tool itself does not own the run's lifecycle.
Configuring the command timeout
Each execute call is bounded by a timeout (default 300 seconds). Override
the default per sandbox, or per call:
sandbox = KubernetesAgentSandbox(sandbox=raw_sandbox, timeout=120)
sandbox.execute("pytest -q", timeout=600)
Commands that exceed the timeout return an ExecuteResponse with exit code
124 instead of raising.
Capabilities
KubernetesAgentSandbox implements the BaseSandbox interface:
execute(command, *, timeout=None)— run a shell command in the pod.upload_files([(path, bytes), ...])— write files into the pod (absolute paths only).download_files([path, ...])— read files from the pod (absolute paths only).id— the underlying sandbox ID.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langchain_kubernetes_agent_sandbox-0.2.1.tar.gz.
File metadata
- Download URL: langchain_kubernetes_agent_sandbox-0.2.1.tar.gz
- Upload date:
- Size: 118.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ac5c4c83c0413a15e3f9c05989dfadc9780668e1e5a9d87acaefa5c32836db3
|
|
| MD5 |
54c4a5ed5a6a086b217a9919d0b6d85f
|
|
| BLAKE2b-256 |
aa152280f960b4102de2c28a27d63c8d6cb1a5446099715865c1b1fc32230b83
|
Provenance
The following attestation bundles were made for langchain_kubernetes_agent_sandbox-0.2.1.tar.gz:
Publisher:
publish-pypi.yml on irwinding/langchain-kubernetes-agent-sandbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langchain_kubernetes_agent_sandbox-0.2.1.tar.gz -
Subject digest:
0ac5c4c83c0413a15e3f9c05989dfadc9780668e1e5a9d87acaefa5c32836db3 - Sigstore transparency entry: 1545357131
- Sigstore integration time:
-
Permalink:
irwinding/langchain-kubernetes-agent-sandbox@0d9793d55ab0df55f2a745879ade27c6f93bcf38 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/irwinding
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@0d9793d55ab0df55f2a745879ade27c6f93bcf38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file langchain_kubernetes_agent_sandbox-0.2.1-py3-none-any.whl.
File metadata
- Download URL: langchain_kubernetes_agent_sandbox-0.2.1-py3-none-any.whl
- Upload date:
- Size: 6.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6cfdf9239622c4213bb29e73445c5bcd4b462a566e037b4d43fab38752685649
|
|
| MD5 |
a0d014ec114814cb7a3443084009fc86
|
|
| BLAKE2b-256 |
fd0d70d848eb58894675606b464dd4679f7b354134124731b2f72fa20ef8aefd
|
Provenance
The following attestation bundles were made for langchain_kubernetes_agent_sandbox-0.2.1-py3-none-any.whl:
Publisher:
publish-pypi.yml on irwinding/langchain-kubernetes-agent-sandbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langchain_kubernetes_agent_sandbox-0.2.1-py3-none-any.whl -
Subject digest:
6cfdf9239622c4213bb29e73445c5bcd4b462a566e037b4d43fab38752685649 - Sigstore transparency entry: 1545357291
- Sigstore integration time:
-
Permalink:
irwinding/langchain-kubernetes-agent-sandbox@0d9793d55ab0df55f2a745879ade27c6f93bcf38 -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/irwinding
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@0d9793d55ab0df55f2a745879ade27c6f93bcf38 -
Trigger Event:
release
-
Statement type: