A sandboxed agent calls the model and a tool
Code in a sandbox granted infer:run and actions:run calls the model and the Open-Meteo action with no key, timed from inside the sandbox.
Grant a sandbox infer:run and actions:run and the code inside it can call the model and the action catalog with no
key: the sandbox gets OPENAI_BASE_URL, a placeholder OPENAI_API_KEY and B3OS_MODEL, and B3OS answers at
http://127.0.0.1:7700. The agent (agent.mjs in the recipe) asks the model for Berlin's temperature, runs the tool
call it gets back through the Open-Meteo action and returns the model's answer:
jsconst first = await post(`${process.env.OPENAI_BASE_URL}/chat/completions`, { model: process.env.B3OS_MODEL, messages, tools, tool_choice: "auto", chat_template_kwargs: { enable_thinking: false },});const call = first.choices[0].message.tool_calls[0];const weather = await post("http://127.0.0.1:7700/v1/actions/call", { ref: "io.b3os/open-meteo/get-current-weather@1", input: JSON.parse(call.function.arguments),});
Verified 2026-11-01: this recipe's last run passed against https://beta-api.b3os.org, label live-recorded (recorded from production), from commit 771a9508. The recipe harness runs it again, and a failed run takes this page out of the cookbook.
Run it
Each tab creates the sandbox with the grant, copies agent.mjs in, runs it and deletes the sandbox.
bashSBX=$(curl -s https://beta-api.b3os.org/v1/sandboxes \ -H "Authorization: Bearer $B3OS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"template": "base", "timeout_s": 300, "api": {"scopes": ["infer:run", "actions:run"], "spend_cap_usd": 1}}' \ | jq -r .id)curl -s -X PUT "https://beta-api.b3os.org/v1/sandboxes/$SBX/files?path=/tmp/agent.mjs" \ -H "Authorization: Bearer $B3OS_API_KEY" \ -H "Content-Type: application/octet-stream" \ --data-binary @agent.mjscurl -s "https://beta-api.b3os.org/v1/sandboxes/$SBX/exec" \ -H "Authorization: Bearer $B3OS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"cmd": "node /tmp/agent.mjs", "timeout_ms": 60000}' | jq -r .stdoutcurl -s -X DELETE "https://beta-api.b3os.org/v1/sandboxes/$SBX" -H "Authorization: Bearer $B3OS_API_KEY"
tsimport { readFileSync } from "node:fs";const API = "https://beta-api.b3os.org";const auth = { authorization: `Bearer ${process.env.B3OS_API_KEY}` };const json = { ...auth, "content-type": "application/json" };const sbx: any = await ( await fetch(`${API}/v1/sandboxes`, { method: "POST", headers: json, body: JSON.stringify({ template: "base", timeout_s: 300, api: { scopes: ["infer:run", "actions:run"], spend_cap_usd: 1 }, }), })).json();try { await fetch(`${API}/v1/sandboxes/${sbx.id}/files?path=/tmp/agent.mjs`, { method: "PUT", headers: { ...auth, "content-type": "application/octet-stream" }, body: readFileSync("agent.mjs"), }); const exec: any = await ( await fetch(`${API}/v1/sandboxes/${sbx.id}/exec`, { method: "POST", headers: json, body: JSON.stringify({ cmd: "node /tmp/agent.mjs", timeout_ms: 60000 }), }) ).json(); console.log(exec.stdout);} finally { await fetch(`${API}/v1/sandboxes/${sbx.id}`, { method: "DELETE", headers: auth });}
pythonimport osimport requestsAPI = "https://beta-api.b3os.org"s = requests.Session()s.headers["Authorization"] = f"Bearer {os.environ['B3OS_API_KEY']}"sbx = s.post(f"{API}/v1/sandboxes", json={ "template": "base", "timeout_s": 300, "api": {"scopes": ["infer:run", "actions:run"], "spend_cap_usd": 1},}).json()try: with open("agent.mjs", "rb") as f: s.put(f"{API}/v1/sandboxes/{sbx['id']}/files", params={"path": "/tmp/agent.mjs"}, data=f.read(), headers={"Content-Type": "application/octet-stream"}) run = s.post(f"{API}/v1/sandboxes/{sbx['id']}/exec", json={"cmd": "node /tmp/agent.mjs", "timeout_ms": 60000}).json() print(run["stdout"])finally: s.delete(f"{API}/v1/sandboxes/{sbx['id']}")
On the remote B3OS MCP server, sandbox_create takes the same body as the API, api grant included.
Ask your agent:
textCreate a base sandbox that may call the model and run actions (infer:run, actions:run, spend cap 1 USD), writeagent.mjs into /tmp, run it with node, show me its output, then delete the sandbox.
The agent calls sandbox_create, sandbox_write_file, sandbox_exec and sandbox_delete.
model_first_ms, action_ms and model_second_ms below are measured inside the sandbox, on the same machine as the
model. A buffered exec waits at most 60 seconds; a longer agent streams its output or runs in the background.
Owner: Docs. Products: Docs, Sandboxes, Infer, Actions and triggers.
Output of the last green run
bash$ node run.mjs# exit 0
json{ "model": "b3os/qwen3.5-9b", "tool": "get_current_weather", "action_ref": "io.b3os/open-meteo/get-current-weather@1.0.0", "action_upstream_status": 200, "temperature_c": 7.2, "answer": "The current temperature in Berlin is 7.2 degrees Celsius.", "model_first_ms": 338.4, "action_ms": 786.7, "model_second_ms": 113.9, "agent_ms": 1239.2, "create_ms": 159.9, "exec_ms": 1444, "deleted": true}
The outputs this recipe publishes (its recipe.json publish.outputs), with workspace identifiers redacted. The run's stdout, stderr and other outputs are not published.
Captured 2026-11-01 from commit
771a9508
, label
live-recorded
(recorded from production). Case
recipes/sandbox-agent-weather
.