Concepts

The receipt

Every run returns the same versioned record. Its job is to tell you not just what happened, but how PyBox knows it, and what it doesn't know.

Four kinds of fact#

The receipt (formally, the inspection record, schema version 1) is organized by where each fact came from, because that's what decides how much you can trust it.

SectionWhat's in itTrust it?
configuredWhat PyBox asked for: limits, image, user, networkIt's a request, not proof
observedWhat the container engine reported, plus counters from the Linux kernelYes. Independent of the program
captureWhat the program printedNo. A program can print anything
unknownWhat can't be determined from this runAlways present, never guessed away

Two more sections describe PyBox's own actions: lifecycle (which deadlines fired, whether you cancelled) and cleanup (whether the box is confirmed gone). For runs that move files, files lists what went in and came back, with sizes and SHA-256s. Returned files count as the program's word, like stdout.

pybox exec prints a short view of this record in the terminal, grouped the same way: from the engine, from the kernel, from the program, and not known. pybox exec --json prints the record itself.

Why "the program said" is kept apart#

Imagine a program that prints All tests passed! and exits with code 0, while a child process it started was killed for using too much memory. PyBox recorded exactly that situation. The receipt shows the exit code 0 from the engine, the engine's own out-of-memory flag, and the kernel's count of one out-of-memory kill, side by side. Reading only the exit code, or only the output, would have told you the wrong story.

Output is also capped (64 KiB per stream by default; 1–8,192 bytes per stream over MCP) and marked when it was cut short.

The fields you'll use most#

FieldMeaning
observed.policy_verified_before_starttrue if every rule was read back and matched before the program started
observed.engine_state.ExitCodeThe program's exit code, from the engine
observed.engine_state.OOMKilledThe engine says the box ran out of memory
observed.resources.metricsKernel counters (memory, processes, CPU): first, last and largest values seen
observed.termination.mechanismWhy it ended, only when PyBox has evidence (labels)
capture.stdout, capture.stderrThe program's output: untrusted
capture.stdout_truncatedOutput was longer than the cap and the rest was dropped
lifecycle.timeoutsWhich deadline fired, e.g. ["wall"]
cleanup.statusREMOVED when the box was removed and confirmed gone (all statuses)
explanationsShort, evidence-tagged reasons, e.g. OOM_KILL_COUNTER_OBSERVED

What's always unknown#

  • Final resource counters. The watcher reads the counters while the program runs, but the box can disappear between two reads. The receipt gives the last value it actually saw and says the final value is unknown.
  • The exact cause of termination. Even a well-supported label is narrower than proof. A program could, in principle, kill itself at the exact moment a timer would have.
  • Clock alignment. Your Mac, the container engine and the kernel each keep their own clock. PyBox never pretends they line up exactly.

Explanation codes#

CodeEvidence behind it
TARGET_EXIT_OBSERVEDThe engine's final state
DEADLINE_REACHEDPyBox's own clocks
SUPERVISOR_KILL_ATTEMPTEDPyBox's recorded stop command
OOM_REPORTED_BY_ENGINEThe engine's out-of-memory flag
OOM_KILL_COUNTER_OBSERVEDThe kernel's memory event counter
TASK_LIMIT_EVENT_OBSERVEDThe kernel's process-limit counter
CPU_THROTTLING_OBSERVEDThe kernel's CPU counter (slowing down, not stopping)
CANCELLEDYou interrupted the run
RUNNER_ERRORSomething in PyBox itself went wrong; read the detail
COUNTERS_UNAVAILABLEThe watcher couldn't collect counters
POLICY_NOT_VERIFIED_BEFORE_STARTThe rules weren't confirmed, so the program didn't start

Checked against the source on 20 October 2026.

esc