Kubernetes Agent Sandbox integration for LangChain Deep Agents
Project description
langchain-k8s
Kubernetes Agent Sandbox integration for LangChain Deep Agents.
Implements the BaseSandbox / SandboxBackendProtocol contract using kubernetes-sigs/agent-sandbox as the execution backend. Agents get isolated, ephemeral Kubernetes pods for running shell commands and file operations — fully self-hosted, no vendor lock-in.
Installation
pip install langchain-k8s
Or with uv:
uv add langchain-k8s
Prerequisites
- A Kubernetes cluster with the agent-sandbox controller installed
- A
SandboxTemplateresource defining the pod spec for your sandboxes kubectlconfigured with cluster access
Quick start
from langchain_anthropic import ChatAnthropic
from deepagents import create_deep_agent
from langchain_k8s import KubernetesSandbox
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
)
agent = create_deep_agent(
model=ChatAnthropic(model="claude-sonnet-4-20250514"),
system_prompt="You are a Python coding assistant with sandbox access.",
backend=backend,
)
result = agent.invoke(
{"messages": [{"role": "user", "content": "Create a Python script that prints the Fibonacci sequence"}]}
)
backend.stop()
Usage
Context manager (recommended)
with KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
) as backend:
agent = create_deep_agent(model=model, backend=backend, ...)
result = agent.invoke({"messages": [...]})
# Sandbox pod is automatically cleaned up
Explicit lifecycle
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
)
backend.start()
try:
resp = backend.execute("python3 --version")
print(resp.output)
finally:
backend.stop()
Direct execution
with KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
) as backend:
# Execute commands
resp = backend.execute("echo 'Hello from K8s!'")
print(resp.output, resp.exit_code)
# Upload files
backend.upload_files([("/workspace/script.py", b"print('hello')\n")])
# Download files
results = backend.download_files(["/workspace/script.py"])
print(results[0].content)
Connection modes
| Mode | Configuration | Use case |
|---|---|---|
| Production | gateway_name="my-gateway" |
Cluster with Gateway API |
| Development | (default — no gateway, no api_url) | Auto kubectl port-forward |
| Advanced | api_url="http://localhost:8080" |
Pre-existing port-forward or in-cluster |
# Production — cluster Gateway
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
gateway_name="sandbox-gateway",
)
# Development — automatic port-forward
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
)
# Advanced — existing port-forward or in-cluster routing
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
api_url="http://localhost:8080",
)
Sandbox lifecycle strategies
Control how sandboxes are managed across multiple agent invocations via the reuse_sandbox parameter:
Persistent (default)
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
reuse_sandbox=True, # default
)
One sandbox pod is created lazily and reused across all calls. Fast for cached, long-lived agents. Filesystem state persists between invocations. Auto-reconnects if the pod dies.
Ephemeral
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
reuse_sandbox=False,
)
A fresh sandbox is created for each start()/stop() cycle. Maximum isolation between invocations at the cost of cold-start latency.
Enterprise features
Path access policy
Restrict which directories agents can write to using allow_prefixes. When set, only write() and edit() operations targeting paths under the specified prefixes are permitted. All other paths return an error without executing a command.
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
allow_prefixes=["/workspace/", "/tmp/"],
)
When allow_prefixes is None (the default), no write restrictions are applied.
Note: This is a tool-level policy. It does not block
execute("echo bad > /etc/passwd"). Use the Kubernetes podsecurityContext(e.g.readOnlyRootFilesystem) for system-level protection.
Virtual filesystem
When virtual_mode=True, all file-operation paths (read, write, edit, ls, grep, glob, uploads, downloads) are resolved under root_dir (default /workspace). Path traversal (.., ~) is rejected.
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
virtual_mode=True,
root_dir="/workspace", # default when virtual_mode=True
)
# Agent sees virtual paths — resolved under /workspace automatically:
# write("/src/main.py", ...) → writes to /workspace/src/main.py
# read("/src/main.py") → reads from /workspace/src/main.py
# upload_files([("/data/input.csv", content)]) → /workspace/data/input.csv
In virtual mode, download_files() uses the native SDK HTTP transfer (SandboxClient.read()) for better performance. Uploads continue to use shell commands because the SDK write() method only preserves the file basename.
When combined with allow_prefixes, the policy check runs against the resolved path:
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
virtual_mode=True,
root_dir="/workspace",
allow_prefixes=["/workspace/"],
)
# Virtual path "/src/main.py" resolves to "/workspace/src/main.py" — allowed
# Virtual path "../../etc/passwd" — rejected (path traversal)
Horizontal scaling and sticky sessions
When deploying a service that uses KubernetesSandbox behind a load balancer with multiple replicas, requests from the same user or session must be routed to the same service instance. The sandbox state (pod, port-forward) is held in-process, so different instances cannot share a sandbox.
Configure sticky sessions using one of these approaches:
Kubernetes Service with session affinity:
apiVersion: v1
kind: Service
metadata:
name: my-agent-service
spec:
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 3600
selector:
app: my-agent
ports:
- port: 80
targetPort: 8080
NGINX Ingress with cookie affinity:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-agent-ingress
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "AGENT_SESSION"
nginx.ingress.kubernetes.io/session-cookie-max-age: "3600"
spec:
rules:
- host: agent.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-agent-service
port:
number: 80
Preserving sandboxes across restarts
Set skip_cleanup=True to prevent sandbox pod destruction when stop() is called. The Kubernetes SandboxClaim is preserved so the sandbox pod continues running. Use sandbox_id for stable identification across service restarts.
backend = KubernetesSandbox(
template_name="python-sandbox-template",
namespace="agent-sandbox-system",
skip_cleanup=True,
sandbox_id="user-session-123",
)
The sandbox pod must be cleaned up externally (e.g. Kubernetes TTL controller, CronJob, or manual deletion).
Note: Full sandbox reconnection (multiple service instances sharing the same Kubernetes pod) requires upstream SDK support for deterministic claim names. Currently each
start()call creates a newSandboxClaim.
Configuration reference
| Parameter | Type | Default | Description |
|---|---|---|---|
template_name |
str |
(required) | SandboxTemplate CRD name |
namespace |
str |
"default" |
Kubernetes namespace |
gateway_name |
str | None |
None |
Gateway name (production mode) |
gateway_namespace |
str |
"default" |
Gateway namespace |
api_url |
str | None |
None |
Direct router URL (advanced mode) |
server_port |
int |
8888 |
Sandbox runtime port |
reuse_sandbox |
bool |
True |
Reuse sandbox across calls |
max_output_size |
int |
1048576 |
Max output bytes before truncation |
command_timeout |
int |
300 |
Command timeout in seconds |
allow_prefixes |
list[str] | None |
None |
Restrict write/edit to these path prefixes |
root_dir |
str | None |
None |
Root directory for virtual filesystem mode |
virtual_mode |
bool |
False |
Resolve all paths under root_dir |
sandbox_id |
str | None |
None |
Stable identifier (overrides auto-generated ID) |
skip_cleanup |
bool |
False |
Preserve SandboxClaim on stop() |
Development
# Clone and install
git clone https://github.com/uesleilima/langchain-k8s.git
cd langchain-k8s
uv sync
# Run unit tests (no cluster needed)
uv run pytest tests/unit/ -v
# Lint and type check
uv run ruff check src/ tests/
uv run mypy src/
Integration tests with Kind
Integration tests require a Kubernetes cluster. The repository includes scripts and manifests to set up a Kind cluster with everything needed.
Prerequisites: kind, kubectl, docker
# Create the Kind cluster and deploy agent-sandbox components
./scripts/kind-setup.sh
# Run integration tests
uv run pytest tests/integration/ -v -m integration
# Tear down when done
./scripts/kind-teardown.sh
The setup script will:
- Create a Kind cluster named
langchain-k8s - Install the agent-sandbox controller and extension CRDs (v0.1.1)
- Enable the extensions controller
- Deploy the sandbox router
- Apply the
python-sandbox-templateSandboxTemplate
k8s/
├── sandbox-router.yaml # Router Deployment + Service
└── sandbox-template.yaml # SandboxTemplate for Python runtime
License
MIT
Project details
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_k8s-0.2.0.tar.gz.
File metadata
- Download URL: langchain_k8s-0.2.0.tar.gz
- Upload date:
- Size: 198.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6c2b59b21d92138a69325edc03d95845235e731bef6db95b22195b2952a6e51
|
|
| MD5 |
af34e8285f12fe3e9ce3fa5cc9d0c59f
|
|
| BLAKE2b-256 |
57914e889474ff8e963782ba753c4979a0e7f39022f84e259ecf29429c4480fc
|
Provenance
The following attestation bundles were made for langchain_k8s-0.2.0.tar.gz:
Publisher:
publish.yml on uesleilima/langchain-k8s
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langchain_k8s-0.2.0.tar.gz -
Subject digest:
c6c2b59b21d92138a69325edc03d95845235e731bef6db95b22195b2952a6e51 - Sigstore transparency entry: 1003328281
- Sigstore integration time:
-
Permalink:
uesleilima/langchain-k8s@2dc0daa8745fae24d23d6c8eff0a08b532f7875e -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/uesleilima
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2dc0daa8745fae24d23d6c8eff0a08b532f7875e -
Trigger Event:
release
-
Statement type:
File details
Details for the file langchain_k8s-0.2.0-py3-none-any.whl.
File metadata
- Download URL: langchain_k8s-0.2.0-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e38c33bbac9dbdc3bb59a1f810db852e70ce77ff0867b62f43645050b10c5d72
|
|
| MD5 |
76d70ac2cd7124fe6fb950e882f3a564
|
|
| BLAKE2b-256 |
38db2bc160c8847ba9b337e3bf9804f36eb8f39f7ee241bae72b54d6b40a2b6d
|
Provenance
The following attestation bundles were made for langchain_k8s-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on uesleilima/langchain-k8s
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langchain_k8s-0.2.0-py3-none-any.whl -
Subject digest:
e38c33bbac9dbdc3bb59a1f810db852e70ce77ff0867b62f43645050b10c5d72 - Sigstore transparency entry: 1003328286
- Sigstore integration time:
-
Permalink:
uesleilima/langchain-k8s@2dc0daa8745fae24d23d6c8eff0a08b532f7875e -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/uesleilima
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@2dc0daa8745fae24d23d6c8eff0a08b532f7875e -
Trigger Event:
release
-
Statement type: