Guides / SDKs / Native HTTP API
Native HTTP API
All five SDKs are thin clients for one HTTP API that the kosa8 daemon serves under
/kosa8/v1. This page documents that API directly: how to reach it, the headers it reads,
how streaming output is framed, how errors come back, and the request and response of every endpoint
the SDKs use. Use it to write a client in another language, to call kosa8 from curl, or
to understand exactly what an SDK call does on the wire.
The daemon is a small web server that only listens on a file on your own disk, not on the network. You send it JSON, and it answers with JSON. Commands that produce output answer with a stream of small JSON lines, one per piece of output, ending with one that says the command has finished. The responses shown on this page came from a kosa8 0.9.36 daemon on an Apple silicon Mac.
Reaching the daemon
Local: the unix socket
The daemon listens on $HOME/.kosa8/run/kosa8.sock. Any HTTP/1.1 client that can dial
a unix socket works. The host name in the URL is ignored.
$ curl -s --unix-socket ~/.kosa8/run/kosa8.sock http://kosa8/kosa8/v1/health
{"commit":"f6124c0","go":"go1.26.9","phase":"0-foundations","status":"ok","uptime_seconds":3396,"version":"0.9.36"}
The socket file is the access control: it is created mode 0600 in a directory only
your user can enter, so only processes running as you can connect. No token is needed or checked
locally.
Remote: a worker over HTTPS
A daemon can also serve other machines as a remote worker (kosa8
worker). Clients then send every request with
Authorization: Bearer <token>; a missing or wrong token gets 401 with
WWW-Authenticate: Bearer. The SDKs choose the endpoint the way the CLI does:
KOSA8_HOST (or the SDK's host option) | Endpoint |
|---|---|
| unset or empty | the local socket |
https://host[:port][/path] | HTTPS to that host; a path is kept as a prefix |
http://host:port | plain HTTP, refused unless the host is loopback (localhost, 127.x, ::1) or KOSA8_INSECURE=1 / the SDK's insecure option is set |
tcp://host:port | the same as http:// |
host:port | the same as http:// |
unix:///path/to.sock | that socket file |
| any other scheme | an error |
KOSA8_TOKEN is only sent to a host that also came from KOSA8_HOST. A host
passed in code gets only a token passed in code, so a token in your environment never leaks to a
host chosen somewhere else. Plain HTTP is refused because the token, and every command you run,
would cross the network in clear text; put the worker behind a TLS-terminating proxy or tunnel
instead.
Request headers
| Header | Meaning |
|---|---|
Content-Type: application/json | For JSON request bodies. Endpoints that take raw bytes (stdin) use application/octet-stream. |
X-Kosa8-Actor | Who is acting: human, agent or tool. The SDKs send tool unless told otherwise. It is recorded on the audit entry and policies can treat actors differently. |
X-Kosa8-Agent-Token | An agent identity minted by kosa8 agent (Agent identity). The SDKs read it from KOSA8_AGENT_TOKEN. Under a policy with require_agent_identity or require_agent_owner, requests without a valid one are refused. |
X-Kosa8-Approval | A signed approval for one action, when your organisation's policy requires one (Company rules). |
Authorization: Bearer … | Remote workers only. |
Responses and errors
Successful answers are JSON with status 200 (or 201 for a created
sandbox). Every error has the same shape:
$ curl -s --unix-socket ~/.kosa8/run/kosa8.sock -X POST \
-d '{"egress":"bogus"}' http://kosa8/kosa8/v1/sandboxes
{"error":"egress policy \"bogus\": want any, deny, fakenet, or allow:HOST[,HOST...]","remediation":""}
error says what went wrong and remediation, when not empty, says what to
do about it. The SDKs join them as error (remediation) and choose an error type by status:
| Status | Meaning | SDK error |
|---|---|---|
| 400 | The request is malformed or a value is invalid. | BadRequest |
| 401 | Missing or wrong bearer token (remote workers). | Auth |
| 403 | Refused by policy. When the body also has an approval object with a why, a signed approval would allow it. | Policy, or ApprovalRequired |
| 404 | No such sandbox, snapshot or container. | NotFound |
| 409 | The request conflicts with the current state: a name or snapshot id is taken, a sandbox is not running, a sealed snapshot fails verification. | Conflict |
| 501 | Not supported on this platform or build. | Unsupported |
| 500 | The operation failed; the remediation names where to look. | Api |
Streaming output: NDJSON frames
Endpoints that run something (run, exec, attach) answer
200 immediately and then stream newline-delimited JSON, one frame per line, as the
output is produced:
{"stream":"status","data":"UHVsbGluZy4uLg=="}
{"stream":"stdout","data":"aGVsbG8K"}
{"stream":"stderr","data":"d2FybmluZwo="}
{"done":true,"ok":true,"exit":0}
| Field | Meaning |
|---|---|
stream | stdout, stderr, or status (progress such as pulls) |
data | The bytes, base64-encoded, so binary output survives |
done | Present and true on the last frame only |
exit | The process's exit code, on the last frame |
ok | Whether the operation itself worked (a non-zero exit is still ok) |
error | On the last frame, when the operation failed after streaming began |
A non-zero exit code is a normal result, not an error. A stream that ends without a
done frame means the connection broke; the SDKs report that as a stream error and keep
the output received so far. Errors found before streaming starts (no such sandbox, policy refusal)
come back as an ordinary JSON error with its status code.
Daemon
GET /kosa8/v1/health
Liveness and version. Returns status, version, commit,
go, uptime_seconds.
POST /kosa8/v1/pull
Pulls an image into the host's shared engine. Body {"ref":"alpine:latest"}.
Sandboxes
GET /kosa8/v1/sandboxes
Lists sandboxes as an array of sandbox objects:
| Field | Meaning |
|---|---|
id, name | Identifier and the name you gave it; either can be used in paths |
state | running, paused or stopped |
created | RFC 3339 time |
parent | For a fork, the sandbox it was copied from |
cpus, mem_mb, disk_gb | Resources |
mounts | Host folders shared in |
egress | The network policy string |
ip | Its address on the private network |
labels | Classification labels |
capture, broker | Packet capture file and credential broker, when set |
POST /kosa8/v1/sandboxes
Creates and boots a sandbox. Every field is optional.
| Field | Type | Meaning |
|---|---|---|
name | string | Name; generated when omitted |
cpus | int | Virtual CPUs |
mem_mb | int | Memory in MiB |
disk_gb | int | Private disk size in GiB |
mounts | array | Host folders to share, host:guest[:ro] |
egress | string | deny, any, fakenet or allow:HOST[,HOST…]. Hosts are names or IPv4 addresses and networks. When omitted, your organisation's default applies if a signed policy sets one, otherwise any. |
capture | object | Write the sandbox's packets to a pcapng file |
labels | object | Classification labels, checked against the org policy |
broker | object | Credential broker: real API keys stay on the host and the sandbox gets placeholders (Keep API keys out) |
auto_stop_min | int | Stop after this many minutes without use. 0 or omitted: never. |
ttl_min | int | Stop this many minutes after creation, in use or not |
auto_delete_min | int | Delete once it has been stopped this many minutes |
ephemeral | bool | Delete it, disk and all, whenever it stops |
template | string | Start with a template's image (see Templates below) |
Answers 201 with sandbox (the object above) and boot_ms.
With a broker it also returns broker.url, broker.placeholders and
broker.decide_token, only in this response.
Lifecycle: auto-stop, TTL, auto-delete, ephemeral
Agents forget to clean up. A sandbox with a lifecycle cleans up after itself instead of holding memory and disk until someone notices. The daemon checks every 30 seconds.
- Auto-stop (
auto_stop_min) stops a sandbox nobody is using. It is in use while any of these is true: a request for it arrived within the window or is still being served (a long run, an open terminal); a container other than a session's idle shell is running in it; or a session has a process besides its own placeholder, such as something started withnohup. So a build left running keeps the sandbox up even when no request comes. If the daemon can't reach the guest to check, it counts the sandbox as in use. - TTL (
ttl_min) is a deadline: the sandbox stops that long after it was created, in use or not. - Auto-delete (
auto_delete_min) deletes a sandbox that has been stopped that long, however it was stopped. - Ephemeral deletes instead of stopping. When the sandbox is stopped by hand, by auto-stop or by its TTL, it and its disk are deleted. One left stopped by a daemon restart is deleted too.
The settings are kept in the sandbox's record, so they survive a daemon restart. A restored or
forked copy starts with none. Change them on a live or stopped sandbox with
POST /kosa8/v1/sandboxes/{id}/lifecycle. Fields you leave out keep their value, and 0
turns one off. The answer is the sandbox, whose lifecycle field shows the result. From
the CLI:
$ kosa8 sandbox create web --auto-stop 30m --auto-delete 24h
$ kosa8 sandbox create scratch --ephemeral --ttl 2h
$ kosa8 sandbox lifecycle web --auto-stop 0
Under an org policy that requires signed approvals, changing a lifecycle needs one, because a
lifecycle can delete a sandbox. Over MCP, sandbox_create takes auto_stop_min,
ttl_min and ephemeral.
POST /kosa8/v1/sandboxes/{id}/down and /up
down stops a sandbox and keeps its disk; stopping one that is already stopped answers
{"ok":true,"already_stopped":true}. up boots it again from its own disk and
returns sandbox, boot_ms and any warning.
POST /kosa8/v1/sandboxes/{id}/pause and /resume
Freeze a running sandbox where it is, and continue it. POST /kosa8/v1/sandboxes/pause
pauses every running sandbox at once and returns the ones it paused.
DELETE /kosa8/v1/sandboxes/{id}
Deletes a sandbox. With ?wipe=1 its disks are overwritten and verified first, and the
answer includes a wipe summary and per-file files. Checkpoints are kept by
the client, not the daemon, so the SDKs also delete the sandbox's checkpoint snapshots unless told to
keep them, and add checkpoints_dropped to the result.
POST /kosa8/v1/sandboxes/{id}/pull
Pulls an image into the sandbox's own engine; code in a sandbox can only use images pulled into
it. Body {"ref":"python:3.12-alpine"}, answer {"ok":true,"image":"…"}.
GET /kosa8/v1/sandboxes/{id}/images, /containers, /egress
The images in the sandbox (array of strings), its containers (array of objects), and its network policy with the decisions the host has made under it.
Running code
POST /kosa8/v1/sandboxes/{id}/run
Runs a container to completion and streams its output as frames. Body:
{"image":"alpine:latest","args":["sh","-c","echo hi"],"env":["A=1"]}.
args replaces the image's command; env entries are
KEY=VALUE.
POST /kosa8/v1/sandboxes/{id}/code
Runs a snippet of Python, JavaScript or TypeScript and answers with one JSON object once it ends. You don't pick an image or quote anything for a shell: send the language and the source.
$ curl -s --unix-socket ~/.kosa8/run/kosa8.sock -X POST \
http://kosa8/kosa8/v1/sandboxes/demo/code \
-d '{"language":"python","code":"import sys\nprint(sys.version)"}'
{"language":"python","image":"docker.io/library/python:3.12-slim","exit":0,
"stdout":"3.12.…\n","stderr":"","artifacts":[],"duration_ms":412}
| Field | Meaning |
|---|---|
language | python, javascript or typescript
(also py, js, node, ts). |
code | The source, up to 96 KiB. For a bigger program, copy it into the
sandbox and use /run. |
image | Optional. Defaults to python:3.12-slim or
node:22-slim. If the sandbox doesn't have it yet, the daemon pulls it on the host
and loads it in, so the sandbox needs no network access for that. |
env | Optional map of extra environment variables. |
timeout_s | Optional. Default 300, at most 3600. When the time runs out
the snippet is killed and the answer has "timed_out":true and exit 124. |
The snippet starts in the sandbox's workspace, /workspace, so files it writes there
can be read back with the file API below, and files you upload are there for it to open.
Python runs the source as __main__. If the snippet uses matplotlib, the daemon
switches it to the non-GUI backend so plt.show() doesn't hang. When the snippet ends,
every open figure comes back in artifacts as
{"type":"image/png","name":"figure-1.png","data":"<base64>"}. TypeScript runs on
Node's built-in type stripping, so there is no compile step and types aren't checked. A snippet
that fails still answers 200: read exit and stderr. Output beyond 32 MiB is
dropped and "truncated":true is set. If the caller hangs up, the snippet is
killed.
Over MCP the same call is the sandbox_code_run tool. Its result is the output as
text, followed by each chart as an image the model can look at.
POST /kosa8/v1/sandboxes/{id}/start
Starts a detached container and returns {"ok":true,"id":"…"}. Use it for a
long-running process you then exec into.
POST /kosa8/v1/sandboxes/{id}/exec
Runs a command in a running container and streams frames. Body
{"container":"<id>","args":["ls","-la"]}.
POST /kosa8/v1/sandboxes/{id}/start-interactive
Starts a container with stdin held open: {"image","args","env","tty"}, returning its
id. Feed it with stdin and read it with attach.
POST /kosa8/v1/sandboxes/{id}/containers/{cid}/stdin
The request body is written to the container's stdin as raw bytes. Send it with
Transfer-Encoding: chunked to stream input as it is produced. Answers
{"ok":true}.
POST /kosa8/v1/sandboxes/{id}/containers/{cid}/attach
Streams the container's output as frames until it exits.
Files and git
Every sandbox has a workspace: a folder on its own data disk that code runs see at
/workspace. It survives a stop, and a snapshot or fork takes it along. These routes
read and write it directly, without starting a container. A path is written as a container sees it
(/workspace/src/app.py) or relative to the workspace (src/app.py). Any other
absolute path is refused, and no path can leave the folder: the guest resolves every path, symlinks
included, inside it.
| Call | What it does |
|---|---|
GET …/files?path= | Lists a folder: {"path","files":[{"name","path","type","size","mode","mod_time","link"}]}.
type is file, dir, symlink or other. On a file it describes that file. |
GET …/files/stat?path= | Describes one entry without following a symlink. |
GET …/files/content?path= | Downloads a file, raw, with its Content-Length. |
PUT …/files/content?path=&mode=644 | Uploads the body as the file, replacing it in one step (readers never see half a file) and making its folders. Up to 4 GiB. Answers with the new entry. |
DELETE …/files?path=&recursive=true | Removes a file, or a folder with recursive. The workspace itself can't be removed. |
POST …/files/mkdir | {"path"}: makes a folder and its parents. |
POST …/files/move | {"from","to"}: renames, making the destination's folders. |
$ S=~/.kosa8/run/kosa8.sock; B=http://kosa8/kosa8/v1/sandboxes/demo
$ curl -s --unix-socket $S -X PUT --data-binary @data.csv "$B/files/content?path=/workspace/data.csv"
$ curl -s --unix-socket $S -X POST $B/code -d '{"language":"python","code":"print(open(\"data.csv\").read()[:80])"}'
A missing path is 404. A path outside the workspace, a folder where a file was expected, or a
non-empty folder removed without recursive is 400. A sandbox whose guest image predates
the file API answers 501: upgrade kosa8, then stop and start the sandbox.
POST /kosa8/v1/sandboxes/{id}/git
Runs one git operation on a repository in the workspace. git runs in a container inside the
sandbox, so a clone or push goes out under the sandbox's own network policy. A hook in a cloned
repository runs sealed in there too. The image is alpine/git, which the daemon pulls on
first use. Body: {"op", "path", …}, with the fields each operation reads:
| op | Fields | Parsed result |
|---|---|---|
clone | url (http or https), path (default: the repository's name), branch, depth | |
status | path | status: {"branch","upstream","ahead","behind","clean","files":[{"path","from","index","worktree"}]} |
add | files (default all) | |
commit | message, author_name, author_email | |
push, pull | remote (default origin), branch | |
checkout | branch, create | |
branches | branches: [{"name","current"}] | |
log | n (default 20) | log: [{"hash","author_name","author_email","date","subject"}] |
diff | staged | the diff is stdout |
Every answer has ok, exit, stdout and stderr. A git
failure (a conflict, an unknown branch) is still a 200 with "ok":false, so read
stderr. For a private remote, pass token (and username, which
defaults to x-access-token, what GitHub, GitLab and Gitea accept). The token reaches git as an
HTTP header through its environment. It never appears on a command line, in the audit log or in
an approval, and it is scrubbed from the output.
$ curl -s --unix-socket $S -X POST $B/git -d '{"op":"clone","url":"https://github.com/kosa8ai/kosa8-py","depth":1}'
$ curl -s --unix-socket $S -X POST $B/git -d '{"op":"status","path":"kosa8-py"}'
Over MCP these are the sandbox_file_list, sandbox_file_read,
sandbox_file_write, sandbox_file_delete and sandbox_git tools.
Sessions and terminals
Sessions: a shell that remembers
An agent runs one command at a time, and expects cd src in one command to still hold
in the next, and export too. A session gives it that. Each session is a long-lived
container in the sandbox, which starts in /workspace. Each command runs in it with
sh, and afterwards the shell's working directory and exported variables are saved for the
next command. The daemon keeps no session state of its own: a session is its container, so a
daemon restart, a snapshot or a fork carries it along.
| Call | What it does |
|---|---|
POST …/sessions | {"name","image","env"}, all optional. The image defaults to
python:3.12-slim, pulled on first use. Answers {"name","id","image","status"}; 409 if the name is taken. |
GET …/sessions | The sandbox's sessions. |
POST …/sessions/{name}/exec | {"command","timeout_s"}. Answers {"exit","stdout","stderr","cwd","duration_ms","timed_out","truncated"}.
cwd is where the shell is now. |
DELETE …/sessions/{name} | Ends the session and everything it started. |
$ curl -s --unix-socket $S -X POST $B/sessions -d '{"name":"main"}'
$ curl -s --unix-socket $S -X POST $B/sessions/main/exec -d '{"command":"cd kosa8-py && export N=3"}'
$ curl -s --unix-socket $S -X POST $B/sessions/main/exec -d '{"command":"echo $N; pwd"}'
{"exit":0,"stdout":"3\n/workspace/kosa8-py\n","stderr":"","cwd":"/workspace/kosa8-py","duration_ms":31}
Commands in one session run one at a time, in the order they arrive. Only exported variables
carry over; shell functions and aliases don't. A command that runs longer than
timeout_s (default 300) is killed, and the session keeps the state it had before that
command. To leave something running, start it in the background with its output redirected, for
example nohup ./server > server.log 2>&1 &. Over MCP this is
sandbox_shell, which makes the session (default main) on first use, and
sandbox_session_delete.
Terminals (PTY)
For a program that needs a real terminal: a REPL, an editor, a TUI, or a person typing.
| Call | What it does |
|---|---|
POST …/pty | {"image","command","cols","rows","env"}, all optional. Starts a container with a terminal
(by default a login shell in /workspace, with TERM=xterm-256color) and answers {"id"}. |
POST …/containers/{cid}/resize | {"cols","rows"}: sets the terminal's size. |
GET …/containers/{cid}/tty | A WebSocket that is the terminal itself. |
On the WebSocket, send keyboard input as binary messages and control messages as
text, for example {"type":"resize","cols":120,"rows":40}. The terminal's output
arrives as binary messages. When the program ends, a text message
{"type":"exit","code":0} arrives and the socket closes. A terminal's input can be opened
once. A request that carries an Origin header, which every browser sends, is refused,
so a web page can't open a terminal in your sandbox. A guest image from before resize support
answers 501 to resize.
Previews: a link to a web server in a sandbox
An agent starts a web app in a sandbox and wants you to see it. A preview is a link that opens it in your browser and stops working when it expires.
$ curl -s --unix-socket $S -X POST $B/sessions/main/exec -d '{"command":"nohup python3 -m http.server 8000 > web.log 2>&1 &"}'
$ curl -s --unix-socket $S -X POST $B/previews -d '{"port":8000,"session":"main","expires_in_s":1800}'
{"url":"http://p-3f9c…a1.localhost:8791/?kosa8_preview=eyJz…","token":"eyJz…",
"expires_at":"2026-11-03T13:30:00Z","container":"session-main","port":8000,"listen":"127.0.0.1:8791"}
$ kosa8 sandbox preview demo 8000 --session main --expires 30m
Body: port (required); session or container for where the server
runs, defaulting to the main session or the only running container; and
expires_in_s (default 3600, at most 7 days). The server must listen on
0.0.0.0, not 127.0.0.1, so the sandbox's network can reach it.
How it works
- The daemon serves previews on a second listener,
127.0.0.1:8791by default. SetKOSA8_PREVIEW_ADDRto move it. When kosa8 sits behind a proxy or tunnel with wildcard DNS, setKOSA8_PREVIEW_URLto a template such ashttps://{host}.preview.example.com. - Each preview has its own origin,
p-<id>.localhost, so one preview's page can't read another's cookies. Browsers resolve*.localhostto your own machine. - The link carries a token the daemon signs with a key only it holds
(
~/.kosa8/keys/preview.key). The token names the sandbox, the container, the port and the expiry. On the first visit the daemon trades the token for an HttpOnly cookie on that origin and redirects to the same page without it, so the token doesn't stay in the address bar or leak through aReferer. A program can send the token in theX-Kosa8-Preview-Tokenheader instead. - The app sees ordinary requests, WebSockets included, with its own cookies. The preview's token is removed before they reach it. Preview traffic counts as use for auto-stop.
- Nothing is stored per preview. A link works until it expires, until its sandbox is deleted
(a new sandbox with the same name doesn't inherit it), or until
POST /kosa8/v1/previews/rotate(kosa8 sandbox preview --revoke-all) replaces the key and ends every link at once.
Under an org policy that requires signed approvals, making a preview needs one, because the
link opens the sandbox's port to whoever holds it. Revoking does not. Over MCP this is
sandbox_preview, which returns the link for the agent to give you.
Templates: what a sandbox starts with
A template is an image built from your Dockerfile and kept on your machine. A new sandbox
created with "template": "NAME" starts with it already loaded. Its sessions, terminals
and code runs then use that image unless a call names another. So an agent's
sandbox_shell or sandbox_code_run finds your libraries, tools and data already
there.
$ cat Dockerfile
FROM python:3.12-slim
RUN pip install --no-cache-dir pandas matplotlib scikit-learn
$ kosa8 template build py-data .
$ kosa8 sandbox create analysis --template py-data --auto-stop 30m
$ kosa8 template ls
$ kosa8 template rm py-data
The build runs in a sandbox made for it and deleted after it. That way the Dockerfile's
RUN steps (an install script, a curl | sh) run sealed, never on your machine or
the shared engine. They run under the egress you give: any by default, because builds
fetch packages, or an allow-list such as
--egress allow:pypi.org,files.pythonhosted.org. Base images are pulled by the host, under
your organisation's registry allowlist when it has one.
| Call | What it does |
|---|---|
POST /kosa8/v1/templates?name=&dockerfile=&buildarg=&egress=&replace=1 |
The body is the build context as a tar, as for /kosa8/v1/build. Streams NDJSON frames
(build output, then {"done":true,"ok":true,"image":…}). Names are lower-case. 409 if the
template exists, unless replace=1. Optional cpus, mem_mb and
disk_gb size the build sandbox. |
GET /kosa8/v1/templates | [{"name","image","size_bytes","created","dockerfile"}] |
DELETE /kosa8/v1/templates/{name} | Deletes it. Sandboxes made from it keep their copy. |
A sandbox made from a template carries the labels kosa8.io/template and
kosa8.io/image, and so do its snapshots, restores and forks. Templates live in
~/.kosa8/templates/ as OCI archives.
Snapshots and forks
POST /kosa8/v1/sandboxes/{id}/snapshot
Captures memory, running processes and disks while the sandbox keeps running. Body
{"id":"optional-id","encrypt":false}. Answers snapshot and
snapshot_ms; with encryption, also encryption and encrypt_ms.
An id already in use is 409. If the org policy requires encryption, it happens anyway
and the answer says encrypted_by_policy.
GET /kosa8/v1/snapshots
Lists snapshots: id, sandbox, created, cpus,
mem_mb, size_bytes and more.
POST /kosa8/v1/snapshots/{id}/restore
Boots a new sandbox from a snapshot with its processes resumed. Body
{"name":"…","label":"…","allow_tampered":false}. Answers sandbox and
restore_ms, plus seal for a sealed snapshot. A sealed snapshot whose seal no
longer verifies is refused with 409 unless allow_tampered is set, and the
override is written to the audit log.
DELETE /kosa8/v1/snapshots/{id}
Deletes a snapshot, shredding its key first if it is encrypted. ?wipe=1 overwrites
its files; ?force=1 is required to delete a sealed one.
POST /kosa8/v1/sandboxes/{id}/fork
Makes n live copies of a running sandbox from one snapshot. Body
{"n":3,"prefix":"try","label":""}. Answers forks (sandbox objects),
snapshot (the id they were made from), fork_ms and count. A
failed fork keeps nothing, so it can be retried. Disks are cloned copy-on-write where the
filesystem allows (APFS, XFS, btrfs).
Built on the API: checkpoints and explore
Two SDK features are client-side logic over the endpoints above, done the same way as the CLI:
- Checkpoints are snapshots with ids
<sandbox>-ckpt-N, tracked in~/.kosa8/checkpoints.json(mode 0600, rewritten atomically, locked withflockoncheckpoints.json.lock). Ten are kept per sandbox; the oldest snapshot is deleted only after its replacement exists. Rewind deletes the sandbox, restores the chosen checkpoint under the same name, and discards checkpoints taken after it. - Explore pulls the image, forks the sandbox once per variant, runs
sh -c VARIANTin each copy concurrently, keeps the fastest copy that exited 0 and deletes the rest. A copy that refuses its first connection while it settles is retried up to four times.
Other endpoints
The daemon serves more than the SDKs use today: networks, volumes, images (build, tag, save,
load, push), ranges, labels, the audit log (GET /kosa8/v1/audit and
/audit/verify), packet capture, snapshot sealing and encryption, and the malware lab.
The command reference documents them through the CLI, which is built on
the same API. The daemon also serves the Docker Engine API on the same socket
(For developers).