npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bitkaio/langchain-kubernetes

v0.3.0

Published

Kubernetes sandbox provider for the DeepAgents framework

Downloads

41

Readme

@bitkaio/langchain-kubernetes

Kubernetes sandbox provider for the DeepAgents framework (TypeScript).

Supports two backend modes:

| Mode | Requirements | Best for | | ---- | ------------ | -------- | | agent-sandbox (default) | kubernetes-sigs/agent-sandbox controller + CRDs | Production — warm pools, gVisor/Kata, sub-second startup | | raw | Any cluster, @kubernetes/client-node + tar-stream | Dev / clusters where you can't install CRDs |

Installation

# agent-sandbox mode — no extra deps (fetch is built in)
npm install @bitkaio/langchain-kubernetes

# raw mode — additional deps required
npm install @bitkaio/langchain-kubernetes @kubernetes/client-node tar-stream

Quick start

agent-sandbox mode

import { KubernetesProvider } from "@bitkaio/langchain-kubernetes";

const provider = new KubernetesProvider({
  mode: "agent-sandbox",
  routerUrl: "http://sandbox-router-svc.default.svc.cluster.local:8080",
  templateName: "python-sandbox-template",
});

const sandbox = await provider.getOrCreate();

const result = await sandbox.execute("python3 -c 'print(2 + 2)'");
console.log(result.output);   // "4\n"
console.log(result.exitCode); // 0

await provider.delete(sandbox.id);

raw mode

import { KubernetesProvider } from "@bitkaio/langchain-kubernetes";

const provider = new KubernetesProvider({
  mode: "raw",
  image: "python:3.12-slim",
});

const sandbox = await provider.getOrCreate();
const result  = await sandbox.execute("python3 -c 'print(42)'");
await provider.delete(sandbox.id);

Configuration reference

Shared options

| Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | mode | "agent-sandbox" \| "raw" | "agent-sandbox" | Backend mode | | namespace | string | "deepagents-sandboxes" | Kubernetes namespace | | startupTimeoutMs | number | 120_000 | Ms to wait for sandbox to be ready | | executeTimeoutMs | number | 300_000 | Default ms per execute() call |

agent-sandbox mode options

| Field | Type | Required | Description | | ----- | ---- | -------- | ----------- | | routerUrl | string | Yes | URL of the sandbox-router service | | templateName | string | Yes | SandboxTemplate name to instantiate | | serverPort | number | No (8888) | Port the sandbox runtime listens on | | kubeApiUrl | string | No | Kubernetes API URL. Defaults to in-cluster URL. Use http://localhost:8001 for kubectl proxy. | | kubeToken | string | No | Bearer token for k8s API. Auto-read from service account if omitted. |

raw mode options

| Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | image | string | "python:3.12-slim" | Container image | | imagePullPolicy | string | "IfNotPresent" | Image pull policy | | workdir | string | "/workspace" | Working directory inside container | | command | string[] | ["sleep", "infinity"] | Container entrypoint | | env | Record<string, string> | — | Extra environment variables | | cpuRequest / cpuLimit | string | "100m" / "1000m" | CPU resources | | memoryRequest / memoryLimit | string | "256Mi" / "1Gi" | Memory resources | | ephemeralStorageLimit | string | "5Gi" | Ephemeral storage limit | | blockNetwork | boolean | true | Attach deny-all NetworkPolicy | | runAsUser / runAsGroup | number | 1000 / 1000 | UID/GID inside container | | seccompProfile | string | "RuntimeDefault" | seccomp profile type | | namespacePerSandbox | boolean | false | Give each sandbox its own namespace | | kubeconfigPath | string | — | Path to kubeconfig file | | context | string | — | Kubeconfig context to use |

Sandbox API

Every sandbox extends BaseSandbox from the deepagents package:

// Execute a shell command
const result = await sandbox.execute("ls -la /workspace");
// result.output    — combined stdout + stderr
// result.exitCode  — process exit code
// result.truncated — true if output was capped at outputLimitBytes

// Upload files
const results = await sandbox.uploadFiles([
  ["/workspace/script.py", Buffer.from("print('hello')")],
  ["/workspace/data.json", Buffer.from('{"key": "value"}')],
]);
// results[0].error — null on success, "file_not_found" | "permission_denied" etc. on failure

// Download files
const downloads = await sandbox.downloadFiles(["/workspace/output.txt"]);
// downloads[0].content — Uint8Array | null
// downloads[0].error   — null on success

// BaseSandbox also provides higher-level helpers built on execute():
// sandbox.ls(), sandbox.read(), sandbox.write(), sandbox.glob(), ...

Usage with DeepAgents

KubernetesSandbox plugs directly into DeepAgents via the backend parameter of createDeepAgent. When a sandbox backend is set, the agent automatically gains an execute tool for running shell commands in addition to the standard filesystem tools.

One-shot usage

import { ChatAnthropic } from "@langchain/anthropic";
import { createDeepAgent } from "deepagents";
import { KubernetesProvider } from "@bitkaio/langchain-kubernetes";

const provider = new KubernetesProvider({
  routerUrl: "http://sandbox-router-svc.default.svc.cluster.local:8080",
  templateName: "python-sandbox-template",
  // mode: "raw", image: "python:3.12-slim",  // raw mode alternative
});

const sandbox = await provider.getOrCreate();
const llm = new ChatAnthropic({ model: "claude-opus-4-6" });
const agent = createDeepAgent({ model: llm, backend: sandbox });

const result = await agent.invoke({
  messages: [{ role: "user", content: "Write and run a Python script that prints the Fibonacci sequence" }],
});
console.log(result.messages.at(-1)?.content);

await provider.delete(sandbox.id);

Multi-turn: persistent sandbox per conversation

For multi-turn applications you need the same sandbox to survive across turns — installed packages, written files, and shell state must all be retained between messages.

KubernetesSandboxManager.createAgent(llm) returns a ready-to-use DeepAgents agent that handles this automatically. Each turn it reconnects to the same sandbox using the conversation thread_id; if the sandbox has expired it provisions a new one transparently.

Behind Express / Hono

import express from "express";
import { MemorySaver } from "@langchain/langgraph";
import { ChatAnthropic } from "@langchain/anthropic";
import { KubernetesSandboxManager } from "@bitkaio/langchain-kubernetes";

const manager = new KubernetesSandboxManager(
  {
    routerUrl: "http://sandbox-router-svc.default.svc.cluster.local:8080",
    templateName: "python-sandbox-template",
  },
  { ttlIdleSeconds: 1800 },
);
const llm = new ChatAnthropic({ model: "claude-opus-4-6" });

// MemorySaver keeps state in-process.
// Replace with a persistent checkpointer for multi-process deployments.
const agent = await manager.createAgent(llm, { checkpointer: new MemorySaver() });

const app = express();
app.use(express.json());

app.post("/chat/:threadId", async (req, res) => {
  const result = await agent.invoke(
    { messages: [{ role: "user", content: req.body.message }] },
    { configurable: { thread_id: req.params.threadId } },
  );
  res.json({ reply: result.messages.at(-1)?.content });
});

process.on("SIGTERM", () => manager.shutdown());
app.listen(3000);

Each thread_id gets its own sandbox. The first request provisions a new one; every subsequent request reconnects to the same one.

With langgraph dev / LangGraph Platform

Yes — each LangGraph thread automatically gets its own persistent sandbox. createAgent() stores the sandboxId as a field in graph state. The LangGraph Platform (and langgraph dev) checkpoint the entire graph state — including sandboxId — between runs for each thread. When the same thread sends its next message, the platform restores the state and the agent reconnects to the same sandbox automatically. No extra configuration is needed.

Export the compiled agent from agent.ts and point langgraph.json at it:

agent.ts:

import { ChatAnthropic } from "@langchain/anthropic";
import { KubernetesSandboxManager } from "@bitkaio/langchain-kubernetes";

const manager = new KubernetesSandboxManager(
  {
    routerUrl: "http://sandbox-router-svc.default.svc.cluster.local:8080",
    templateName: "python-sandbox-template",
    warmPoolName: "python-pool",   // optional: sub-second startup
  },
  { ttlIdleSeconds: 1800, defaultLabels: { app: "my-agent" } },
);

const llm = new ChatAnthropic({ model: "claude-opus-4-6" });

// The platform provides the checkpointer — omit it here
export const graph = await manager.createAgent(llm);

langgraph.json:

{
    "graphs": {
        "agent": "./agent.ts:graph"
    }
}
npx @langchain/langgraph-cli dev
# → http://localhost:2024

Interact via the LangGraph SDK — threads and sandbox persistence are handled automatically:

import { Client } from "@langchain/langgraph-sdk";

const client = new Client({ apiUrl: "http://localhost:2024" });
const thread = await client.threads.create();

// First run — provisions a sandbox for this thread
await client.runs.create(thread.thread_id, "agent", {
  input: { messages: [{ role: "user", content: "Install pandas and analyse the iris dataset" }] },
});

// Second run — reconnects to the same sandbox automatically
await client.runs.create(thread.thread_id, "agent", {
  input: { messages: [{ role: "user", content: "Now plot a histogram of petal length" }] },
});

Custom systemPrompt and tools

Extra options passed to createAgent are forwarded to createDeepAgent:

const agent = await manager.createAgent(llm, {
  checkpointer: new MemorySaver(),
  systemPrompt: "You are a data analyst. Always save outputs to /workspace/output/.",
  // tools: [myCustomTool],
});

KubernetesSandboxManagerOptions:

| Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | ttlSeconds | number \| undefined | — | Absolute TTL from creation (seconds) | | ttlIdleSeconds | number \| undefined | — | Idle TTL from last execute() (seconds) | | defaultLabels | Record<string, string> \| undefined | — | Labels applied to every sandbox (auto-prefixed) |

Methods:

| Method | Returns | Description | | ------ | ------- | ----------- | | createAgent(model, options?) | Promise<CompiledGraph> | Returns a compiled DeepAgents agent with sandbox persistence (primary integration point) | | createAgentNode(model, options?) | AsyncNodeFn | Returns a single LangGraph node; use when building a multi-node graph | | getOrReconnect(sandboxId) | Promise<KubernetesSandbox> | Reconnect or create; for custom node logic | | cleanup(maxIdleSeconds?) | Promise<CleanupResult> | Delete expired sandboxes | | shutdown() | Promise<void> | Delete all sandboxes | | [Symbol.asyncDispose]() | Promise<void> | Called by await using |

Sandbox lifecycle management

Provider API — getOrCreate and reconnect

The provider is stateless: it does not cache sandboxes in memory. Callers are responsible for persisting sandbox.id between calls (LangGraph handles this via its checkpointer).

// Create new sandbox
const sandbox = await provider.getOrCreate();

// Reconnect to an existing sandbox, or create a new one if it no longer exists
const sandbox = await provider.getOrCreate({
  sandboxId: "existing-id",             // from LangGraph state
  labels: { customer: "acme" },         // auto-prefixed with langchain-kubernetes.bitkaio.com/
  ttlSeconds: 3600,
  ttlIdleSeconds: 600,
});

// List all managed sandboxes from the K8s API
const { sandboxes, cursor } = await provider.list();
const { sandboxes: running } = await provider.list({ status: "running" });
const { sandboxes: labelled } = await provider.list({ labels: { customer: "acme" } });
// Pagination:
const page2 = await provider.list({ cursor });

// Operational methods
const result = await provider.cleanup();               // CleanupResult
const result = await provider.cleanup(300);            // override idle threshold (seconds)
const status = await provider.poolStatus();            // WarmPoolStatus

// Delete (idempotent)
await provider.delete(sandbox.id);

GetOrCreateOptions:

| Field | Type | Description | | ----- | ---- | ----------- | | sandboxId | string \| undefined | Existing sandbox ID to reconnect (from graph state) | | labels | Record<string, string> \| undefined | Per-call labels (auto-prefixed) | | ttlSeconds | number \| undefined | Absolute TTL override (seconds) | | ttlIdleSeconds | number \| undefined | Idle TTL override (seconds) |

SandboxListResponse:

interface SandboxListResponse {
  sandboxes: SandboxInfo[];
  cursor?: string;  // Kubernetes continue token for pagination
}

interface SandboxInfo {
  id: string;
  namespace: string;
  labels?: Record<string, string>;
  annotations?: Record<string, string>;
  createdAt?: string;    // ISO-8601
  phase?: string;        // Kubernetes Pod phase
  status?: string;       // "running" | "warm" | "pending" | "terminated"
}

CleanupResult / WarmPoolStatus:

interface CleanupResult {
  deleted: string[];  // sandbox IDs that were deleted
  kept: number;       // sandboxes within their TTL / idle threshold
}

interface WarmPoolStatus {
  available: number;  // warm Pods ready to be claimed
  active: number;     // Pods currently assigned
  total: number;
  target: number;     // configured warmPoolSize
}

Additional configuration options

| Field | Type | Default | Description | | ----- | ---- | ------- | ----------- | | defaultLabels | Record<string, string> \| undefined | — | Labels applied to every sandbox (auto-prefixed with langchain-kubernetes.bitkaio.com/) | | ttlSeconds | number \| undefined | — | Default absolute TTL for getOrCreate() | | ttlIdleSeconds | number \| undefined | — | Default idle TTL for getOrCreate() | | warmPoolSize | number \| undefined | — | Pre-created warm Pods (raw mode only) | | warmPoolName | string \| undefined | — | SandboxWarmPool resource name (agent-sandbox only) |

Warm pool configuration

agent-sandbox mode:

const provider = new KubernetesProvider({
  mode: "agent-sandbox",
  routerUrl: "http://sandbox-router-svc.default.svc.cluster.local:8080",
  templateName: "python-sandbox-template",
  warmPoolName: "python-pool",   // name of a SandboxWarmPool CRD in the cluster
});

raw mode:

const provider = new KubernetesProvider({
  mode: "raw",
  warmPoolSize: 3,   // pre-create 3 idle Pods, replenish after each delete
});

See docs/warm-pool.yaml for cluster YAML examples.

agent-sandbox mode — setup

1. Install the controller

Follow the agent-sandbox installation guide.

2. Create a SandboxTemplate

# examples/k8s/sandbox-template.yaml
apiVersion: extensions.agents.x-k8s.io/v1alpha1
kind: SandboxTemplate
metadata:
  name: python-sandbox-template
  namespace: default
spec:
  podTemplate:
    spec:
      containers:
      - name: python-runtime
        image: us-central1-docker.pkg.dev/k8s-staging-images/agent-sandbox/python-runtime-sandbox:latest-main
        ports:
        - containerPort: 8888
        readinessProbe:
          httpGet: { path: "/", port: 8888 }
          periodSeconds: 1
        resources:
          requests: { cpu: "250m", memory: "512Mi" }
      restartPolicy: "OnFailure"
kubectl apply -f examples/k8s/sandbox-template.yaml

3. Apply RBAC

kubectl apply -f examples/k8s/sandbox-router-rbac.yaml

4. Connect

In-cluster (the primary use case):

const provider = new KubernetesProvider({
  routerUrl: "http://sandbox-router-svc.default.svc.cluster.local:8080",
  templateName: "python-sandbox-template",
});

Local development — forward the sandbox-router to localhost:

kubectl port-forward svc/sandbox-router-svc 8080:8080 -n default
const provider = new KubernetesProvider({
  routerUrl: "http://localhost:8080",
  templateName: "python-sandbox-template",
  // Optional: set kubeApiUrl for sandbox existence verification
  // kubeApiUrl: "http://localhost:8001",  // kubectl proxy
});

raw mode — setup

1. Create the namespace

kubectl create namespace deepagents-sandboxes

2. Apply RBAC

kubectl apply -f examples/k8s/raw-mode-rbac.yaml

3. Configure

const provider = new KubernetesProvider({
  mode: "raw",
  namespace: "deepagents-sandboxes",
  image: "python:3.12-slim",
  blockNetwork: true, // deny-all NetworkPolicy (requires NetworkPolicy-capable CNI)
});

For local kind clusters, kindnet does not support NetworkPolicy. Either use Calico or set blockNetwork: false.

Error handling

import {
  SandboxNotFoundError,
  SandboxStartupTimeoutError,
  SandboxRouterError,
  TemplateNotFoundError,
  MissingDependencyError,
} from "@bitkaio/langchain-kubernetes";

try {
  const sandbox = await provider.getOrCreate();
} catch (err) {
  if (err instanceof TemplateNotFoundError) {
    // SandboxTemplate doesn't exist in the cluster
  } else if (err instanceof SandboxStartupTimeoutError) {
    // Sandbox didn't become ready in time
  } else if (err instanceof SandboxRouterError) {
    // Router or Kubernetes API not reachable
  } else if (err instanceof MissingDependencyError) {
    // @kubernetes/client-node not installed (raw mode)
  }
}

Integration tests

Integration tests require a running cluster:

# raw mode (any cluster)
K8S_INTEGRATION=1 npm run test:integration

# agent-sandbox mode (requires controller + CRDs)
K8S_INTEGRATION=1 SANDBOX_TEMPLATE=python-sandbox-template \
  ROUTER_URL=http://localhost:8080 npm run test:integration

Requirements

  • Node.js ≥ 18
  • Kubernetes cluster (kind, GKE, EKS, AKS, OpenShift, …)
  • For agent-sandbox mode: kubernetes-sigs/agent-sandbox controller
  • For raw mode: @kubernetes/client-node + tar-stream; NetworkPolicy-capable CNI if blockNetwork: true