Drive it from a script or an agent
microvm manifest # the whole surface, as JSON, with no credentialsmicrovm --json run --exec "make test" # one envelope on stdout, progress on stderrmicrovm --dense ls # tab-separated, token-leanUse --json to read results in a script and microvm manifest to learn what
the installed microvm can do. Check the error code and cleanup report before
starting more work. For a stream, read each event and then the final result.
1. One envelope on stdout
Section titled “1. One envelope on stdout”--json is a global flag, so microvm --json ls and microvm ls --json are the same invocation. It emits the typed JSON envelope on stdout instead of human output and wins over every other format, including an interactive terminal. Progress always goes to stderr, and --quiet suppresses progress but never a warning, so a leaked resource is still reported in quiet mode.
A success envelope carries status, apiVersion, type, and data. type is the discriminant to branch on first, and each command’s data keys are published in the manifest as responseKeys:
{ "status": "ok", "apiVersion": "1", "type": "microvm.run", "data": {}}A failure envelope carries status, apiVersion, error, code, exitCode, finding, suggestions, and data. Every field is always present, so a consumer never guards against a missing key:
{ "status": "error", "apiVersion": "1", "error": "human readable, may be reworded between releases", "code": "ERR_PROTOCOL", "exitCode": 5, "finding": "", "suggestions": [], "data": { "kind": "Conflict" }}Branch on code, never on error. data carries partial results on the failure path, most importantly leaked, the identifiers a teardown could not delete. data.kind carries the daemon’s own status name when the exit code is coarser than the failure: several wire kinds collapse onto ERR_PROTOCOL, because a shell branching on $? cannot act differently on a 400 than on a 409, and a consumer that can reads data.kind. A request rejected locally reports no data.kind, because nothing reached the daemon. apiVersion is bumped when a field’s meaning changes, never when a command is added. Envelope and Response types are the generated references.
2. The exit codes
Section titled “2. The exit codes”exitCode in the envelope matches the process exit status. Branch on the
stable code and use its details to decide what to do next.
Exit codes lists the full table.
| Code | Next step |
|---|---|
ERR_RETRYABLE (3) |
Reconcile any accepted launch or execution, then retry the same operation when safe. |
ERR_CREDENTIALS (4) |
Check the identity and credentials named in the error. Waiting does not fix them. |
ERR_TIMEOUT (10) |
Inspect the operation and its cleanup report. A caller deadline alone does not cancel guest work; a surrounding workflow may still request teardown. |
ERR_PRECONDITION (12) |
Read the missing prerequisite and run microvm doctor when relevant. |
ERR_EXEC_FAILED (13) |
Inspect the guest command’s outcome and output. |
ERR_NAME_TAKEN (14) |
Choose another local name or resolve the VM already using it. |
ERR_CONFIG (15) |
Correct the project file or select another config. |
ERR_SYNC (16) |
Check local input or output files. This can occur after a VM ran, when collecting artifacts. |
ERR_UNEXPECTED (1) |
Preserve the error details for a bug report. |
Argument-parse failures exit with ERR_INVALID_ARG (2). A failure’s code
classifies the error; it does not prove that no remote action took place.
A reader that closes early never changes the exit code. microvm manifest | head -c 100 exits 0, and a run whose output nobody reads still tears its VM down and exits with its own outcome, so a closed pipe can never look like a failure that invites a second launch. The CLI never panics or dies by SIGPIPE on a closed stdout or stderr. The one exception is exec --stream: when its stdout reader leaves, streaming stops at the next event, the exec keeps running in the VM, stderr names the exec id and the --exec-id … --from-offset command that reattaches, and the exit is ERR_INTERRUPTED (11).
3. Discover the surface
Section titled “3. Discover the surface”microvm manifest emits the whole command surface, its exit codes, and its envelope schema, generated from the CLI’s own argument tree rather than written down. It is always JSON, and it needs no credentials, no region, and no network, so it doubles as a liveness check. Each command’s entry carries its parameters with type, default, choices, required, and positional, its responseType and responseKeys, and for exec an alternateResponse naming when it applies. The conventions list is the contract in prose. microvm manifest --dense prints one line per command with its parameters.
The agent layer’s commands follow the same rules: agent-up --json is one microvm.agent envelope whose agents list carries each installed agent’s model and the exact headlessCommand agent-prompt will run, and agent-prompt --json is a microvm.agent.prompt envelope, exec’s keys plus agent and model. An agent that declines a task and exits 0 is an ok envelope, so read the effect back with exec rather than trusting the prompt’s exit alone.
microvm constants --emit-json emits every service constraint this client believes, unwrapped by an envelope, for the drift gate that compares them against the pinned service model.
4. The one exception: exec --stream
Section titled “4. The one exception: exec --stream”exec --stream is the one invocation that writes more than one object to stdout. Under --json it emits NDJSON, one event object per line, and then the envelope as the final line:
{"event":"output","stream":"stdout","offset":0,"bytes":12,"text":"hello world\n","lossy":false}{"event":"exit","exitCode":0,"signal":null,"truncated":false,"writersMayBeAlive":false,"offset":12}{"status":"ok","apiVersion":"1","type":"microvm.exec.stream","data":{"execId":"x-1","events":2,"bytes":12,"nextOffset":12,"gaps":0,"exitCode":0,"truncated":false}}A stream’s final result has type: "microvm.exec.stream". The manifest lists
this as exec’s alternateResponse with when: "--stream". Read each event
as one line of JSON, then read the final envelope as the last line. The stream
keeps that envelope on one line too.
Event kinds are output (with stream, offset, bytes, text, lossy), gap (with from and to, the only report of lost bytes), and exit. The envelope’s keys summarize the stream rather than repeating it: events and bytes let a caller assert it read everything, and nextOffset is where --from-offset would resume. A stream cut before its exit event reports exitCode: null rather than 0, because zero would turn a truncated stream into a passing build, and the command exits ERR_EXEC_FAILED. A stream that fails part-way through gets the failure envelope as its compact last line. The stream chunks are the command’s output, so they cannot go to stderr; microvm exec --stream build.sh > log has to write the log.
5. Token-lean output
Section titled “5. Token-lean output”--dense is another global flag: token-lean output, for a consumer paying per token. It renders tab-separated text, one field per column; a dense failure is the code, then the message, tab-separated, so field one is always the code. --json wins over --dense, and --dense --json together emit the compact one-line JSON document rather than the pretty one. Neither depends on whether stdout is a terminal; without either, a terminal gets a human rendering and a pipe gets plain text.
6. Retries and idempotency
Section titled “6. Retries and idempotency”Pass your own --exec-id on an exec you may have to retry: a start carrying a known id returns the original exec without spawning a second child, so a caller whose process died between sending the start and reading the answer sends the identical start again. A retryable error does not make a whole CLI invocation idempotent: a new
run can request a new VM. Preserve resource identifiers and reconcile the
original operation before deciding to start again. build --reuse makes a repeated build idempotent through a content-hash image name, and run defaults to a per-invocation image name because reusing one is how a clientToken replay wedges an image. A --vm-name is local state, refused with ERR_NAME_TAKEN before any call if a live VM holds it.
7. Extracting a field without jq
Section titled “7. Extracting a field without jq”The shipped scripts read envelope fields with a python3 one-liner, which every machine that runs the examples already has:
jqr() { python3 -c "import json,sys; print(json.load(sys.stdin)['data']$1)"; }LAUNCH=$(microvm run --json --keep --image "$IMAGE_ARN")EP=$(echo "$LAUNCH" | jqr "['endpoint']")TOK=$(echo "$LAUNCH" | jqr "['agentToken']")ID=$(echo "$LAUNCH" | jqr "['microvmId']")For an agent. Prefer microvm manifest to any page here wherever the two could disagree; it is generated from the source of truth. Read type before data, code before error, and data.leaked on every failure. Treat ERR_EXEC_FAILED as the workload’s result and every other non-zero exit as a problem with the run itself. Use --exec-id on anything you might retry, and --dense when you are paying per token and do not need the envelope’s structure.
For agents names which surface answers which question, and the daemon’s own machine surface is GET /v1/schema, committed as the wire schema.