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?
- See the full 5-tool surface handed to a real agent — langgraph_agent/ ↗
- Fastest possible "it works", smallest amount of code — langchain_tool_calling/ ↗
- Wire boxkite directly into OpenAI/Gemini/Mistral/Groq's native function calling, no agent framework — openai_function_calling/ ↗, gemini_function_calling/ ↗, mistral_function_calling/ ↗, groq_function_calling/ ↗
- Use boxkite from LlamaIndex or the OpenAI Agents SDK — llamaindex_agent/ ↗, openai_agents_sdk/ ↗
- Run Claude Code itself, headlessly, inside a sandbox — claude_code_sandbox/ ↗ (a pre-built self-hosted image) or claude_code_declarative_builder/ ↗ (build the same image at runtime via the declarative builder API)
- Integrate from a non-LangChain framework or another language — raw_api/ ↗
- Understand the hosted, multi-tenant control-plane API — hosted_control_plane/ ↗
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.
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.
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.
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.
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.
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.
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.
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:
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 ↗