Files
rea/docs/process-capture.md
T

5.7 KiB

Process Capture v4

Process Capture records one approved command as deterministic Evidence. It is intended for authority-versus-reconstruction checks where terminal behavior, child lifetime, filesystem state, or dependency timing matters.

The harness records both raw PTY chunks and terminal states rendered by xterm. It also records scripted input, resize and signal delivery, sampled process metadata, named filesystem checkpoints, command-shim invocations, loopback HTTP/WebSocket exchanges, and process exit ownership. Missing or bounded observations remain explicit; a truncated capture is never treated as equivalent to another capture.

Every v4 capture includes a run manifest with canonical SHA-256 commitments for the secret-safe full scenario projection, comparison contract, executable, normalization rules, command-shim plan, and replay plan. The manifest also records the REA/provider versions, platform, architecture, PTY backend, and UTC start/completion timestamps. Capture creation, Evidence import, and comparison recompute the self-contained commitments and reject invalid lifecycle data.

Enable the capability

Process execution is disabled until the operator supplies policy roots and approves host networking:

export REA_PROCESS_CAPTURE_ENABLED=true
export REA_PROCESS_EXECUTABLE_ROOTS_JSON='["/usr/bin","/absolute/project/bin"]'
export REA_PROCESS_WORKING_ROOTS_JSON='["/absolute/project"]'
export REA_PROCESS_ALLOWED_ENV_JSON='["PATH"]'
export REA_PROCESS_ALLOW_EXTERNAL_NETWORK=true

Every scenario must also contain "approved": true. Executables, working directories, filesystem roots, and requested environment variables are checked against operator policy before launch. Process Capture is an observation tool, not a security sandbox: the target runs with the current user's permissions.

Capture a scenario

rea capture-process reads a JSON scenario and writes Process Capture v4 Evidence to stdout:

rea capture-process ./scenario.json > capture.json

A representative scenario is:

{
  "approved": true,
  "executable": "/absolute/path/to/node",
  "arguments": ["./signup.mjs"],
  "working_directory": "/absolute/project",
  "filesystem_roots": ["/absolute/project/state"],
  "terminal": { "columns": 80, "rows": 24, "scrollback": 1000 },
  "events": [
    {
      "type": "input",
      "at_ms": 250,
      "data": "[email protected]\r",
      "sensitive": true
    },
    { "type": "resize", "at_ms": 500, "columns": 100, "rows": 30 }
  ],
  "checkpoints": [
    {
      "name": "signup_ready",
      "trigger": { "type": "terminal_literal", "value": "All set!" }
    },
    { "name": "root_exited", "trigger": { "type": "root_exit" } }
  ],
  "command_shims": [
    {
      "name": "codex",
      "routes": [
        {
          "arguments": ["--version"],
          "outputs": [
            { "at_ms": 0, "stream": "stdout", "data": "codex 1.2.3\n" }
          ],
          "termination": { "type": "exit", "code": 0 },
          "max_calls": 1
        }
      ]
    }
  ]
}

Input events marked sensitive are dispatched to the PTY but stored only as a byte-count placeholder. Environment values listed in secret_aliases are also redacted from recorded text. The command-shim directory is placed first in the captured process PATH; any remaining PATH must be explicitly supplied or inherited under operator policy.

Checkpoints and deterministic shims

Checkpoint triggers may use an elapsed time, a terminal literal and occurrence count, root exit, or settlement. REA always includes before and after_settlement snapshots. Snapshot effects are relative to the preceding checkpoint, so transient files remain visible even when the final tree matches the initial tree.

Each command shim matches an exact argument array. Routes can emit timed stdout or stderr chunks and then exit or receive SIGINT, SIGTERM, or SIGKILL. Unmatched and exhausted calls are recorded. Shim observations are bounded by the scenario protocol-event limit; exceeding it marks the whole capture truncated while replay continues.

Compare captures

Compare two saved Evidence records with:

rea compare-process-captures authority.json reconstruction.json

The result classifies terminal, interaction, exit, filesystem, protocol, process, and shim behavior. first_divergence points to the earliest observed difference. Residual unknowns prevent a claim of complete equivalence, but they do not hide an observed difference in another dimension.

Captures must have equal comparison-contract commitments, but may identify different scenarios and executables. MCP callers may set max_capture_age_ms; the application clock then rejects stale captures before comparison.

Limits and lifecycle behavior

Scenarios bound output bytes, frames, files, file bytes, process observations, protocol/shim events, connections, filesystem depth, total runtime, idle time, and settlement time. Process trees are sampled rather than syscall-traced, and short-lived descendants may be missed. Filesystem observations are snapshots, not filesystem event streams.

REA tags launched processes with a private run identifier. During cleanup it revalidates that identifier before signaling sampled process groups, including groups detached from the original parent. Temporary HOME, replay, shim, terminal, checkpoint, and sampling resources are released on success, failure, timeout, and cancellation.

After root exit, REA checks token-owned process groups every 50 ms. Two consecutive empty observations establish quiesced; the settlement deadline otherwise produces alive_at_deadline, while inspection or identity failures produce unverifiable. Sampling can still miss short-lived or early-detached descendants, which remains a residual unknown.