> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# 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:

```js
const 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.

**curl**

```bash
SBX=$(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.mjs

curl -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 .stdout

curl -s -X DELETE "https://beta-api.b3os.org/v1/sandboxes/$SBX" -H "Authorization: Bearer $B3OS_API_KEY"
```
**TypeScript**

```ts
import { 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 });
}
```
**Python**

```python
import os
import requests

API = "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']}")
```
**MCP**

On the remote [B3OS MCP server](/sdks/mcp), `sandbox_create` takes the same body as the API, `api` grant included.
Ask your agent:

```text
Create a base sandbox that may call the model and run actions (infer:run, actions:run, spend cap 1 USD), write
agent.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

**Command**

```bash
$ node run.mjs
# exit 0
```
**Outputs**

```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`.