Jinn docs Console

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

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, …}
KindArgumentsResult
bashcommand; workdir, timeout_ms (5 minutes when left out; at most 24 hours), envexit_code, output (its first and last 32 KiB), output_bytes, truncated, timed_out, oom_killed
readfile_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.
writefile_path, contentbytes written. Folders above it are made.
editfile_path, edits: [{old_text, new_text}]applied. Each old_text must occur exactly once; all edits apply or none do.
listpathentries: 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.
putfile (an upload, file_…), pathbytes written. It replaces a file at path.
getpathdownload_url: the file, or a folder as .tar.gz, valid for 15 minutes.
finishstate (succeeded or failed); detailaccepted, and problems when the output does not match the manifest. See Finish a run.
terminalrows, cols, public_key; envrelay_url, route, public_key. See Open a terminal.

Wait for a result

Three ways, use any:

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:

  1. Make an ECDH P-256 key pair, and send terminal with rows, cols and your public key (uncompressed, base64url) as public_key.
  2. The command's result holds relay_url, route and the host's public_key. Derive the shared secret, then 64 bytes with HKDF-SHA-256: the salt is the SHA-256 of jinn-terminal-v1, a zero byte, your public key and the host's; the info is jinn-terminal-v1 keys. The first 32 bytes are the AES-256-GCM key for what you send, the last 32 for what you receive.
  3. 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.
  4. 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:

ToolCommand
bash, read, write, editThe command of the same name, with the call's arguments.
screenshotbash with grim /tmp/screen.png, then read of /tmp/screen.png.
completefinish with state: succeeded.
give_upfinish with state: failed and the reason as detail.
web_searchNot a command: search from your own code.