# Running commands

Run a command and wait, stream its output, or start it in the background.

`POST /v1/sandboxes/{id}/exec`

```shell
curl -X POST https://agental.io/v1/sandboxes/{id}/exec \
  -H "Authorization: Bearer $AGENTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command":["sh","-c","cd /opt/agental && npm test"],"timeoutMs":90000}'
```

- `command` is a list of arguments, not a line of shell. For pipes, `&&`, redirects or variables, pass `["sh","-c","..."]`.
- The answer is HTTP 200 with `exitCode`, `stdout`, `stderr` and `truncated`, even when the command fails.

## Environment

Every command starts fresh: as root, in `/root`, with an environment that holds `PATH` and little else. A `cd` or `export` does not carry over. Begin each command with `cd /opt/agental`, and set `HOME=/root` when a tool asks for it. What is on that `PATH` is in [Languages and tools](https://agental.io/docs/tools).

The variables of the sandbox's [template](https://agental.io/docs/templates) and its own are there too, and three that agental gives every command, so that a program or an agent in the machine can name its own sandbox in a call to the API and move its stop:

| Variable | What it holds |
| --- | --- |
| `AGENTAL_SANDBOX_ID` | The sandbox's id, such as `sbx_2a9a47e50daec64a` |
| `AGENTAL_API_URL` | Where this API answers, `https://agental.io` |
| `AGENTAL_SANDBOX_TOKEN` | The sandbox's own token. It reads and moves when the sandbox stops, and does nothing else. |

- A template or a sandbox cannot hold a variable of any of these names: setting one answers `400 invalid_request`.
- What a command starts has them as well, as a coding agent run with `exec` does. A shell that logs in, over [SSH](https://agental.io/docs/ssh) or by `su -`, has them from the machine's `/etc/environment`, which agental writes as the sandbox is made, forked or started and as SSH is turned on. A fork's shells name the fork, not its source.
- A variable [kept out of the sandbox](https://agental.io/docs/templates#keep-a-variable-out-of-the-sandbox) holds its stand-in, `agental_hidden_` and 32 letters and digits, in place of its value.

## Timeouts and output

| Limit | Value |
| --- | --- |
| Default timeout | 60 seconds |
| Maximum `timeoutMs` | `90000` (90 seconds) for `/exec`, which answers only when the command ends; `600000` (10 minutes) for `/exec/stream` and `/commands` |
| Exit code on timeout | `124` |
| Output per stream | 1 MB, then `truncated: true` |

## Streaming

`POST /v1/sandboxes/{id}/exec/stream`

Same body. Answers with server-sent events: `stdout` and `stderr` events carrying `{"data":"..."}`, then one `exit` event carrying `{"exitCode":0}`. A stream that fails ends with an `error` event that says why, and no `exit` event.

## Background work

A proxy in front of the API stops waiting for an answer after 100 seconds, so `/exec` takes a `timeoutMs` of up to `90000`, and refuses a larger one with 400. No command may run longer than 10 minutes. For servers and long builds, start the process in the background inside the machine and return at once:

```json
{"command":["sh","-c","cd /opt/agental && nohup npm run dev > dev.log 2>&1 < /dev/null &"]}
```

Check on it with ordinary commands such as `tail -n 50 dev.log` or `pgrep -f "npm run dev"`.

`POST /v1/sandboxes/{id}/commands`

Takes the same body, with `timeoutMs` up to `600000`, and answers at once with an `id`. Use it for anything that may take longer than 90 seconds. `GET /v1/sandboxes/{id}/commands/{commandId}` returns its `status` (`running`, `succeeded`, `failed` or `timed_out`), its `exitCode` and its output so far.

To run a command again and again, every so often or at a time of day, give it a [schedule](https://agental.io/docs/schedules): the sandbox is started for each run when it is stopped.

---

Every page of these docs as Markdown: [llms.txt](https://agental.io/llms.txt)
