boxkite

Guide

Examples & cookbook

Eleven runnable examples in examples/, each targeting boxkite's actual, current APIs — not illustrative pseudocode. Every snippet below is excerpted from the real file; follow the link under each one for the complete, runnable script.

Which one should I start with?

Prerequisites (all eleven)

Every example needs a running boxkite sandbox runtime first — see the Quickstart guide (boxkite up, no Kubernetes required). Every agent/model-provider example additionally needs that provider's own API key (Anthropic, OpenAI, Gemini, Mistral, or Groq depending on which one); raw_api, hosted_control_plane, and claude_code_declarative_builder(which builds an image but doesn't itself call an LLM) need no LLM API key at all.

LangGraph agent — the full 5-tool surface

create_sandbox_tools(...) returns plain LangChain @tool-decorated functions, handed directly to LangGraph's prebuilt create_react_agent — no adapter layer, no boxkite-specific graph nodes. The task: write a CSV, write a script that computes per-region revenue from it, run the script, and report back the exact output.

langgraph_agent/agent.py
from langchain.chat_models import init_chat_model
from langgraph.prebuilt import create_react_agent

from boxkite import SandboxManager
from boxkite.tools import create_sandbox_tools

model = init_chat_model("anthropic:claude-sonnet-4-5")
manager = SandboxManager()
session_id = str(uuid4())
await manager.create_session(organization_id=uuid4(), session_id=session_id)

tools = create_sandbox_tools(sandbox_manager=manager, session_id=session_id)
agent = create_react_agent(model, tools)

result = await agent.ainvoke({"messages": [{"role": "user", "content": TASK}]})
print(result["messages"][-1].content)

Full script (env setup, the exact TASK prompt, cleanup, and an independent re-verification step after the agent finishes): langgraph_agent/agent.py

Minimal LangChain agent — 2 tools, fastest path

Deliberately smaller: langchain.agents.create_agent(the same prebuilt ReAct loop LangGraph's create_react_agent uses under the hood) wired to just bash_tool and file_create, to show the tools are independently usable without pulling in the rest of the surface.

langchain_tool_calling/agent.py
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model

from boxkite import SandboxManager
from boxkite.tools.bash_tool import create_bash_tool
from boxkite.tools.file_tools import create_file_create_tool

model = init_chat_model("anthropic:claude-sonnet-4-5")
manager = SandboxManager()
session_id = str(uuid4())
await manager.create_session(organization_id=uuid4(), session_id=session_id)

tools = [
    create_bash_tool(session_id=session_id, sandbox_manager=manager),
    create_file_create_tool(session_id=session_id, sandbox_manager=manager),
]
agent = create_agent(model, tools)

result = await agent.ainvoke({"messages": [{"role": "user", "content": TASK}]})

Full script: langchain_tool_calling/agent.py

Native OpenAI/Gemini/Mistral/Groq function calling — no agent framework

boxkite.tools.adapters.to_openai_functions() is pure stdlib — it builds the {"type": "function", "function": {...}}schema list every one of these providers' APIs accepts, no framework required. Each provider's own tool-calling response shape still differs (OpenAI/Groq use tool_calls on an assistant message; Gemini uses Content/Part/FunctionCall; Mistral is close to OpenAI's shape but its own client class), so each example handles its provider's actual dispatch loop rather than pretending they're identical.

openai_function_calling/agent.py
from openai import OpenAI

from boxkite.tools.adapters import to_openai_functions
from boxkite.tools.bash_tool import create_bash_tool_spec
from boxkite.tools.file_tools import create_file_create_tool_spec

client = OpenAI()
specs = [create_bash_tool_spec(...), create_file_create_tool_spec(...)]
specs_by_name = {spec.name: spec for spec in specs}
tool_schema = to_openai_functions(specs)

response = client.chat.completions.create(model="gpt-5", messages=messages, tools=tool_schema)
for tool_call in response.choices[0].message.tool_calls or []:
    spec = specs_by_name[tool_call.function.name]
    result = await spec.handler(**json.loads(tool_call.function.arguments))

Full scripts: openai_function_calling/agent.py, gemini_function_calling/agent.py, mistral_function_calling/agent.py, groq_function_calling/agent.py

LlamaIndex ReActAgent

to_llamaindex_tools()converts boxkite's framework-agnostic ToolSpecs into LlamaIndex FunctionToolobjects (building a pydantic model from each tool's JSON-schema parameters), handed straight to llama_index.core.agent.workflow.ReActAgent.

llamaindex_agent/agent.py
from llama_index.core.agent.workflow import ReActAgent
from llama_index.llms.openai import OpenAI

from boxkite.tools.adapters import to_llamaindex_tools

specs = [create_bash_tool_spec(...), create_file_create_tool_spec(...)]
tools = to_llamaindex_tools(specs)

agent = ReActAgent(tools=tools, llm=OpenAI(model="gpt-5"))
result = await agent.run(user_msg=TASK)

Full script: llamaindex_agent/agent.py

OpenAI Agents SDK

to_openai_agents_tools() wraps ToolSpecs as agents.tool.FunctionTool objects for agents.Agent(tools=[...]), driven by agents.Runner.run(...). This is a function-tool-level integration — a lighter-weight thing than the OpenAI Agents SDK's deeper native BaseSandboxSession provider protocol some other sandbox vendors implement.

openai_agents_sdk/agent.py
from agents import Agent, Runner

from boxkite.tools.adapters import to_openai_agents_tools

tools = to_openai_agents_tools(specs)
agent = Agent(name="boxkite-sandbox-agent", tools=tools, model="gpt-5")
result = await Runner.run(agent, TASK)

Full script: openai_agents_sdk/agent.py

Claude Code, headless, inside a sandbox — two ways

claude_code_sandbox/ clones a repo with the git tool set, then runs Claude Code headlessly via bash_tool against a self-hosted sandbox built from a hand-maintained image (deploy/sandbox-claude-code.Dockerfile, verified locally: claude --version works as the non-root sandbox user, npm stripped post-install). claude_code_declarative_builder/ builds the equivalent capability at runtime instead, via the declarative builder API — verified end to end against a real, locally-run control-plane instance through the full build/poll/complete flow.

claude_code_declarative_builder/build_claude_code_image.py
image = client.create_image(
    base="boxkite-minimal",
    apt_packages=["git==2.54.0-r0", "openssh-client==10.0_p1-r2"],
    npm_packages=["@anthropic-ai/claude-code==2.0.1"],
)
# poll until status == "completed", then:
sandbox = client.create_sandbox(image_id=image["id"])

Full scripts: claude_code_sandbox/run_claude_code.py, claude_code_declarative_builder/build_claude_code_image.py

Raw HTTP — no framework, no boxkite package

Talks directly to the sidecar boxkite upstarts — useful if you're integrating from a different language or writing your own tool-calling layer. Request/response shapes match sidecar/main.py's Pydantic models exactly.

raw_api/curl_examples.sh
curl -sS -X POST "$SIDECAR_URL/exec" \
  -H "Content-Type: application/json" \
  -H "X-Sidecar-Auth-Token: $SIDECAR_AUTH_TOKEN" \
  -d '{"command": "python3 -c \"print(21 * 2)\"", "timeout": 30}'

curl -sS -X POST "$SIDECAR_URL/file-create" \
  -H "Content-Type: application/json" \
  -H "X-Sidecar-Auth-Token: $SIDECAR_AUTH_TOKEN" \
  -d '{"path": "hello.txt", "content": "hello from the raw sidecar API\n"}'

Full script (plus a Python requests equivalent): raw_api/curl_examples.sh

Hosted control-plane — signup to sandbox

The multi-tenant HTTP API in control-plane/ — not the local sidecar raw_api talks to directly. Walks signup through API-key creation to a sandbox session:

hosted_control_plane/hosted_flow.py
signup_resp = httpx.post(
    f"{CONTROL_PLANE_URL}/v1/auth/signup",
    json={"email": email, "password": password},
)
dashboard_token = signup_resp.json()["access_token"]

# API keys are minted with the short-lived dashboard JWT, not another API key
key_resp = httpx.post(
    f"{CONTROL_PLANE_URL}/v1/api-keys",
    json={"name": "cookbook-example"},
    headers={"Authorization": f"Bearer {dashboard_token}"},
)
api_key = key_resp.json()["key"]

Full script (sandbox create → exec → file read → teardown): hosted_control_plane/hosted_flow.py