Skip to content

Drive it from a script or an agent

Terminal window
microvm manifest # the whole surface, as JSON, with no credentials
microvm --json run --exec "make test" # one envelope on stdout, progress on stderr
microvm --dense ls # tab-separated, token-lean

Use --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.

--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.

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).

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.

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.

--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.

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.

The shipped scripts read envelope fields with a python3 one-liner, which every machine that runs the examples already has:

Terminal window
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.