Contracts

vero.script/v1

The protocol between vero and a script step's child process (chapter 9.2). It is deliberately dull: one JSON object in, newline-delimited JSON events out, stderr as logs. No RPC, no calls back into vero, no shared memory. The child never sees the graph, cannot create tasks, and reaches another task's results only through what the plan templated into input. That is what keeps the graph static.

Running

vero starts <runtime> <entry> as an argv, with no shell, in its own process group, with the plan file's directory as the working directory. The child's environment is PATH, HOME and LANG from vero's own, when set, plus VERO_SCRIPT_TOKEN, a value unique to the step that vero uses after the step to find a process that left the group and kept running. The runtime is the user's own interpreter found on PATH: node for runtime: node, and python3, then python, for runtime: python. The binary found and what its --version printed go into the run header and the run.start event. A plan with a script step whose runtime is not on PATH fails to load (exit 64) naming the runtime and the task; a plan with no script step never looks for one.

stdin: one message

vero writes exactly one line and closes stdin:

{"protocol":"vero.script/v1","input":{...},"deadline":"2026-09-05T12:00:00Z"}
Field Type Meaning
protocol string always vero.script/v1
input object the step's input, with every template rendered; {} when the step has none
deadline string, RFC 3339 UTC when vero will stop the process group; the step's deadline

stdout: events, one JSON object per line

{"type":"log","level":"info","msg":"navigating to /checkout"}
{"type":"step","name":"fill address","status":"passed","durationMs":312}
{"type":"result","ok":true,"result":{"orderId":"01J8Z…","checkoutMs":3820}}
Type Fields Meaning
log level (string), msg (string) a line for the report and the event log
step name (string), status (string), durationMs (number) something the script checked; see "unverified" below
result ok (boolean, required), result (any JSON) the step's result; the last result event is the one used

result.result is decoded like a response body: numbers are exact, and a key that appears twice is an error. Assertions see it as result, and result.ok as ok; results: extract from it as from any step.

What vero does with a stream that breaks the rules

  • An unknown type: recorded as a warn log line naming the type and the line number. Never a failure, so a newer script library does not break an older vero.
  • A line that is not JSON, or has no type: the step is errored, naming the stdout line number.
  • A line longer than 1 MiB: the step is errored, naming the line number.
  • A result event without ok: the step is errored.
  • stdout ends with no result event: the step is errored ("the script's stdout ended without a result event").
  • More than 1 MiB of log events: the rest are read and dropped, and the count of dropped events is reported. step and result events are always kept.
  • A non-zero exit code: the step is errored ("the script exited with code 2"), even when a result event arrived first. A result with ok: false and exit code 0 is failed.

stderr

Captured, capped at 1 MiB like an exec step's output, and reported as log lines.

Unverified

Assertions inside a script are invisible to vero (chapter 9.4). A script that returns ok: true and emitted zero step events is reported as passed (unverified): it counts as passed for the exit code, and every report says it was not verified. Emitting one step event per thing checked is what makes a script's pass a pass.

Secrets and cancellation

Every known secret value is scrubbed from stdout events and stderr before they reach any output; a screenshot or any other file the script writes is not scrubbed. On the step's deadline, a run timeout or a signal, vero signals the whole process group; a process that detached itself from the group (for example with setsid) survives. vero gives it a second to exit on its own, as a browser started in its own group does once its parent's pipe closes, and reports what is still running after that as a process it could not account for.

This page is docs/content/contracts/script-v1.md in the repository.

verodocs