Execution model and steps
Requests
Section titled “Requests”POST /api/v1/execute takes an envelope:
{ "steps": [ ... ], "cache": false, "stop_on_failure": false}| Field | Default | Meaning |
|---|---|---|
steps | required | One or more steps, run in order |
cache | false | Default for tasks that do not set their own cache flag. See step cache |
stop_on_failure | false | Skip the remaining steps once a step has a task that did not exit 0 |
and answers with:
{ "steps": [ ... ], "cache": { "enabled": true, "hits": 1, "stored": 0 }}steps holds one result per step, in order. cache says whether the server’s
step cache is enabled, how many steps were replayed from it and how many this
request stored.
A step is either:
- a task (a JSON object), or
- a parallel step (a JSON array of tasks) whose tasks run at the same time.
{ "steps": [ { "cmd": "gcc", "args": ["-O2", "-o", "main", "main.c"], "files": { "main.c": "..." } }, [ { "cmd": "./main", "stdin": "1 2\n" }, { "cmd": "./main", "stdin": "3 4\n" } ], { "cmd": "ls", "args": ["-l"] } ]}All steps of a request run in one sandbox: they share the workspace
/faber and /tmp. Files an earlier step writes are visible to later steps.
A step starts only after every task of the previous step has finished.
Each task of a parallel step has its own PID namespace and cgroup, so the tasks cannot see each other’s processes and each has the full per-task limits. They still share the workspace; do not let two parallel tasks write the same path.
A parallel step of N tasks needs N free task slots
for the whole request, and N may not exceed MAX_PARALLEL_TASKS.
| Field | Type | Meaning |
|---|---|---|
cmd | string, required | Program to run. Looked up on PATH unless it contains a / |
args | string array | Arguments, without the program name |
env | string map | The complete environment. Only PATH is added if you leave it out |
stdin | string | Written to standard input, which is then closed. Without it, stdin is empty |
files | map of path to file | Files to create before the task starts. See below |
working_dir | string | Absolute path inside the sandbox to start in. Default /faber |
sandbox_profile | string | Seccomp profile: compile_v2, native_v2, compile_v1, native_v1. Default from the server, normally compile_v2. See profiles |
cache | boolean | Declares the task deterministic, so its step may be cached. Overrides the request’s cache |
files maps a workspace-relative path to its content. Paths must be
normalized: no leading /, no .., no . or empty components. Parent
directories are created as needed. Three forms are accepted, and can be
mixed:
"files": { "main.c": "#include <stdio.h>\n...", "data/input.bin": { "content": "AAEC", "encoding": "base64" }, "run.sh": { "content": "#!/bin/sh\nexec ./main\n", "executable": true }, "tests/big.txt": { "file_id": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" }}- A string is UTF-8 text written as is, mode
0644. - An inline file has
content, an optionalencoding(utf8orbase64) and an optionalexecutableflag (mode0755). - A file reference names a file uploaded with
POST /fileby its SHA-256file_id, plus an optionalexecutableflag. An unknown ID fails the whole request with404(file_not_found) before anything runs. See the file store.
Files are created with the task user as owner, without following symlinks, so a task cannot use them to write outside the workspace.
Task results
Section titled “Task results”Each step result has the same shape as its step: an object for a task, an array in the same order for a parallel step.
A task that ran has a completed result:
{ "stdout": "3\n", "stderr": "", "exit_code": 0, "cached": false, "stats": { "outcome": "exited", "execution_time_ms": 3, "memory_peak_bytes": 524288, "...": "..." }}A task that never ran (skipped, deadline passed, setup failed) has a
failed result, with error in place of the output:
{ "error": "Skipped because an earlier step failed", "cached": false, "stats": { "outcome": "not_started", "...": "..." }}Tell them apart by the presence of exit_code (or error). The SDKs expose
both as typed variants. cached: true means the result was replayed from the
step cache; its stats are those of the original run.
stats.outcome explains how the task ended; resource
limits lists every
value.
Failure handling
Section titled “Failure handling”Faber separates three kinds of failure:
- The program failed: non-zero exit, timeout, OOM, policy violation. The
request succeeds with
200; the task’s result says what happened. - The request is not acceptable: malformed JSON, too many steps, a
profile the server does not allow, a missing
file_id, a wrong API key. Nothing runs; you get a4xxwith an error code. - Faber could not run it: no free slots (
503, retry later), shutdown, sandbox setup failure (500), the hard deadline backstop (504).
stop_on_failure
Section titled “stop_on_failure”Without it, every step runs regardless of earlier results. With
stop_on_failure: true, once a step has a task whose result is not a
completed exit_code: 0, every later step is returned without running, each
task as:
{ "error": "Skipped because an earlier step failed", "cached": false, "stats": { "outcome": "not_started", "...": "..." }}The step that failed is still reported in full, including all of its parallel tasks. Use it whenever later steps depend on earlier ones: there is no point running tests against a binary that failed to compile.
Limits on the request itself
Section titled “Limits on the request itself”The number of steps, the width of parallel steps, the body size, the total output and the overall deadline are all bounded; see resource limits.
Go deeper
Section titled “Go deeper”- Request lifecycle: everything that happens between the request and the response, cancellation included.
- Outcomes and failure handling: task outcomes and request errors, and where each comes from.