Commands
Updated 2026-10-13
Commands are how anyone acts on a run: run a program, read, write or edit a file, list a folder, copy a file in or out, open a terminal, and finish the run. You send them through the API or the CLI. Jinn's agent sends the same ones. Every run accepts every kind, with an agent or without one.
A key that can start a function can send commands to its runs.
Drive a run from the CLI
A function without an agent starts a VM and waits. jinn run returns once the VM is ready.
$ jinn run go-sandbox
run_3d9e1c07a5b28f64e0d71c92 queued · go-sandbox v2
run_3d9e1c07a5b28f64e0d71c92 ready · jinn finish run_3d9e1c07a5b28f64e0d71c92 ends it
$ jinn cp ./api.tar.gz run_3d9e1c07a5b28f64e0d71c92:api.tar.gz
$ jinn exec run_3d9e1c07a5b28f64e0d71c92 -- 'tar xzf api.tar.gz && cd api && go test ./...'
ok example.com/api/store 1.204s
ok example.com/api/http 0.588s
$ jinn cp run_3d9e1c07a5b28f64e0d71c92:api/coverage.html ./coverage.html
$ jinn finish run_3d9e1c07a5b28f64e0d71c92
run_3d9e1c07a5b28f64e0d71c92 succeeded
image img_8b2f04d6e1a93c75f0e2d418
jinn execprints the command's output as it comes and exits with the command's own status. It exits with 125 when Jinn could not run the command.jinn cp PATH RUN:PATHsends one file.jinn cp RUN:PATH PATHfetches a file, or a folder, which arrives as a folder.jinn finishends the run.--failed REASONends it as failed.
Send a command
POST /v1/runs/run_3d9e1c07a5b28f64e0d71c92/commands
{"kind": "bash", "arguments": {"command": "go test ./...", "workdir": "/workspace/api"}}
← 201 {"id": "cmd_51a7e2c09d84b36f1e0a2d97", "kind": "bash", "state": "queued", "source": "caller", "sequence": 4, …}
| Kind | Arguments | Result |
|---|---|---|
bash | command; workdir, timeout_ms (5 minutes when left out; at most 24 hours), env | exit_code, output (its first and last 32 KiB), output_bytes, truncated, timed_out, oom_killed |
read | file_path; offset, limit (lines) | output: the text. An image (PNG, JPEG, GIF or WebP, at most 3.75 MiB and 2000 pixels a side) comes as image_url. |
write | file_path, content | bytes written. Folders above it are made. |
edit | file_path, edits: [{old_text, new_text}] | applied. Each old_text must occur exactly once; all edits apply or none do. |
list | path | entries: each file and folder directly in it, files with their size, at most 1,000 (truncated says there are more). Links are listed in skipped, not followed. |
put | file (an upload, file_…), path | bytes written. It replaces a file at path. |
get | path | download_url: the file, or a folder as .tar.gz, valid for 15 minutes. |
finish | state (succeeded or failed); detail | accepted, and problems when the output does not match the manifest. See Finish a run. |
terminal | rows, cols, public_key; env | relay_url, route, public_key. See Open a terminal. |
- A command's arguments are at most 128 KiB of JSON. Send larger files with
put. - Commands run as root, at the same time, in the order they arrive. Relative paths resolve from
/workspace. - Each
bashcommand runs in its ownbash -c, with the run's environment and its ownenv. Nothing but files carries over between commands. - A
bashcommand that ran out of memory saysoom_killed. Its other processes are killed with it; the run goes on. - A command sent while the run is
queuedstarts when the run is ready. - A command that the VM answered
succeeded, even when its program exited with 1 or its result has anerror. One that could not runfailed, witherror. One cancelled, or still running when the run ended, iscancelled. - A run that has ended refuses commands with
run_ended.
Wait for a result
Three ways, use any:
- Long-poll one command:
GET /v1/runs/{id}/commands/{cid}?wait=25answers when the command ends, or after 25 seconds. - Read what changed:
GET /v1/runs/{id}/commands?since=N&wait=25returns the commands that changed after sequenceN, the run'sstate, and thesequenceto send next time. One read holds about 4 MiB at most; read again from itssequencefor the rest. - Get the
command.finishedevent at the run's webhook. It is sent for commands you send, not for the agent's.
wait is 0 to 25 seconds.
Follow a command's output
GET /v1/runs/{id}/commands/{cid}/output?after=0&wait=25
← 200 {"output": "ok \texample.com/api/store\t1.204s\n", "next": 33, "output_bytes": 33, "stored_bytes": 33, "done": false}
Send next as after to read on. One read returns at most 512 KiB, ending on a whole UTF-8 character. done is true once the command has ended and you have read everything kept. Jinn keeps the first 4 MiB of each command's output; the result keeps the first and last 32 KiB of all of it.
Cancel a command
POST /v1/runs/{id}/commands/{cid}/cancel stops a command: its processes are killed, and it ends as cancelled. Cancelling a command that has ended changes nothing.
Copy files
To send a file, upload it with POST /v1/files, as for a run's input, then send put with the upload's id. An upload is at most 5 GB. A get uploads the file or folder and returns a link; it is at most 5 GiB.
Open a terminal
$ jinn shell run_3d9e1c07a5b28f64e0d71c92
root@jinn:/workspace# go version
go version go1.19.8 linux/amd64
root@jinn:/workspace# exit
jinn shell opens a login shell as root in /workspace, with the run's variables, and exits with the shell's status. The console's Terminal tab does the same. Both reach the shell through Jinn's relay, end to end encrypted: only your end and the run's machine can read the session.
To open one from your own code, the SDKs' OpenTerminal and openTerminal do these steps:
- Make an ECDH P-256 key pair, and send
terminalwithrows,colsand your public key (uncompressed, base64url) aspublic_key. - The command's result holds
relay_url,routeand the host'spublic_key. Derive the shared secret, then 64 bytes with HKDF-SHA-256: the salt is the SHA-256 ofjinn-terminal-v1, a zero byte, your public key and the host's; the info isjinn-terminal-v1 keys. The first 32 bytes are the AES-256-GCM key for what you send, the last 32 for what you receive. - Open a WebSocket to
relay_url?route=…&side=caller. Each binary message is a frame: its kind (1 byte), its sequence (8 bytes, big-endian, from 0 in each direction), and the sealed data. The nonce is 4 zero bytes and the sequence; the kind and sequence are the associated data. - Send kind 1 for input and kind 3 to resize (rows and columns, two big-endian 16-bit numbers). You receive kind 2, the shell's output, and kind 4, its exit status (a big-endian 32-bit number), last.
The session ends when the shell exits, either end closes, or the run ends. The relay waits 2 minutes for your end, then closes the session. Each frame holds at most 32 KiB.
Use your own agent
GET /v1/tools returns the tools Jinn's agent shows its model: bash, read, write, edit, screenshot, web_search, complete and give_up, each with its description and JSON Schema. Your own loop can show its model the same tools, and send each call as a command:
| Tool | Command |
|---|---|
bash, read, write, edit | The command of the same name, with the call's arguments. |
screenshot | bash with grim /tmp/screen.png, then read of /tmp/screen.png. |
complete | finish with state: succeeded. |
give_up | finish with state: failed and the reason as detail. |
web_search | Not a command: search from your own code. |