skip to content
Sessions

Parallax

Sessions

What each host sends, what the door does with a session once it ends, the nightly batch, and the one line that tells you why a sweep could not run code.

What is captured

A session is captured only when its git root carries parallax.yml or was named at install. The capture is the transcript your agent already writes on disk, plus what the sandbox later needs to run it: the repository, its commit, and the files the session changed. Nothing from the transcript is copied into the agent’s context.

hosthow it is captured
Claude CodeThe guard (~/.parallax-client/guard.mjs) runs on the SessionStart, PostToolUse, PostToolUseFailure and Stop hooks. It sends the transcript's new bytes to POST /v1/ingest/transcript. At Stop it reads the source snapshot. At SessionStart it asks for the brief (see Memory).
CodexThe same guard with --host=codex on the SessionStart, PostToolUse and Stop hooks. Codex asks you to trust new hooks under /hooks. The saved transcript is parsed: turn_context.model, function and custom tool calls, their outputs.
CursorHooks on sessionStart, beforeSubmitPrompt, postToolUse, postToolUseFailure, afterAgentResponse, stop and sessionEnd. Cursor does not expose reply boundaries, so one assistant turn is recorded per completed tool. No usage, cost or thinking is invented.
OpenCodeA plugin reads completed messages from the session when it becomes idle. Only completed assistant messages and their finished tools are sent; reasoning parts are omitted.
Prime AgentA Python skill (parallax_capture) tails the session's own JSONL log and maps it to the same events. It records every nested repository before the first edit and attaches the source snapshot at turn boundaries and at end. Thinking text is counted, never sent.
Any client on the proxyA client whose base URL is the Parallax proxy is captured per request. The header x-parallax-capture: off excludes one request and keeps its messages out of later history.

Reasoning text is redacted on every host before the bytes leave the machine; capturing it is a per-account opt-in on the door. Images are noted as [image] and never carried.

Source: scripts/team/thin-guard.mjs · scripts/team/install.sh · packages/events/cards/hosts.input.json · packages/prime-capture/cards/prime-capture.input.json · packages/session-capture/cards/session-capture.input.json

The source snapshot

Proofs run on the code as it was. The guard collects the changed files relative to the session’s first observed commit: regular UTF-8 files, at most 256 files, 512 KiB each, 4 MiB in total, within ten seconds; secrets by conventional path and symlinks are left out and named as omissions. The door checks the manifest digest, the transcript boundary, every file’s before-image hash and the repository identity before it records the snapshot. A snapshot the door refuses is answered with a reason, and the guard sends again on the next hook of that session. A later turn invalidates the snapshot and the next Stop observes anew.

Source: packages/session-capture/README.md · packages/session-capture/cards/source.input.json · packages/server/cards/native-source-collection.input.json

What the door does with a session

A session ends when the client says so, or when it has been silent for PARALLAX_ABANDON_MIN minutes (30 by default). The door then queues one job for it, keyed by the session id, run off the HTTP loop within the configured worker and per-account limits, and kept across a restart. The job:

  1. proves the session’s own edits against the repository’s test command in a sandbox at the captured commit;
  2. reviews the whole transcript in one pass;
  3. forks the implementation turn to router-picked seats from other labs, blind and with tools, and tests their edits the same way;
  4. re-runs the proven tests of other sessions that touched the same files (regressions);
  5. runs the lenses on the diff (bugs and security as one question, perf, hygiene, drift), with a proof for each finding;
  6. scans for dead code the session left, each removal proven alone and together;
  7. delivers what it found and writes the session’s note.

A session is swept automatically at most once per capture mark: the turn count, the newest turn and the hash of the source snapshot. A step that did not finish is recorded as incomplete with its reason and is not bought again until the mark moves (the session continued, or a new snapshot arrived) or you ask with POST /v1/sessions/:id/sweep. Steps that did finish are reused.

The sweep has a wall clock (PARALLAX_SWEEP_JOB_MS, 30 minutes by default). It is operational: a step with under 30 s left does not start and reads skipped: clock; a proof the clock cut is could-not-run, never refuted. The clock removes work; it never lowers a bar.

When the sweep lands you get one line, once, at the next Stop of a session in the same repository, or on GET /v1/sweeps:

terminal
parallax: swept px_79466a8b - 2 proven - 1 PR - 1 regression caught - $0.14
parallax: partial cc_6d31258d - source unavailable: source snapshot has omitted files or repositories - $0.00

Source: packages/server/cards/server.input.json · packages/server/cards/sweep-once.input.json · packages/server/cards/sweep-line.json · packages/server/cards/manual-sweep.input.json

The nightly batch

The batch reviews a day of work on one repository together: every session recorded that day, from any host, plus the commits they produced, with the other sessions as context. Automatic batches run when the door is configured for nightly mode. Continuous per-session review remains the default.

  • One batch per (account, repository, day). The day is the window between two nightly times, 02:30 America/Los_Angeles unless the door is configured otherwise; the range reviewed runs from the earliest start commit to the last commit observed. Sessions belong to a window by their end time; sessions still open are listed but not reviewed.
  • The context block names what the sessions shared: files and symbols edited by more than one, a contradiction (one session introduced a line, a later one removed it), a repeated mistake, an unresolved TODO, a claim a session made that its diff does not show.
  • A batch can declare convergence only after at least two full passes, with the last adding nothing new. There is no cap on calls, shards, spend or rounds; a six-hour clock is the safety bound, and a run it stops still delivers what it proved and says stop_reason: clock.
  • Every proven mechanism is delivered; an unproven one is listed as a candidate with its label.
  • POST /v1/batch/run {day?, repository?} runs one by hand; GET /v1/batch/runs lists them.

Source: packages/batch/cards/batch.input.json · packages/batch/cards/hyperparameters.json · packages/server/cards/schedule.input.json

“skipped: source unavailable”

Every code step of a sweep needs the exact revision and the exact edits. When the door cannot get them for a reason that no retry can change, it records the sweep at once: the proof, the forks, the review, every lens and the dead-code scan read skipped: source unavailable (<reason>), cost zero, no model or sandbox call. The session is blocked until its capture mark moves. A retryable failure (GitHub authorization, a checkout timeout) is not this: the sweep proceeds and retries.

The reason is one of these. The right column is the fix.

reasonwhat to do
capture_workspace_missingStart the session inside the repository being edited; the cwd's git root is the capture's coordinate.
capture_commit_missingThe first hook saw no full commit. Commit once before the session, or make sure git works in that checkout.
capture_repository_missingThe repository has no github.com remote the door can resolve. Add the remote of the repository being edited.
capture_repository_ambiguousThe session edited more than one repository and none selects itself. Exactly one manifest with changed files and no omissions is chosen; among several, the one whose repository has a test command in its parallax.yml. Add test: to the repository you mean, or run one repository per session.
capture_repository_evidence_invalidThe repository or revision changed under the capture. Do not switch branches or rewrite history mid-session; start a new session after.
repository_access_deniedYour account has no current GitHub access to that repository through the App. Install the App on it, or select it in the organization grant, from the setup page.
github_app_unconfiguredA self-hosted door with no GitHub App. Configure the App, or run the door on the machine that holds the checkout.
test_configuration_missingNo test command at the captured revision. Add test: (and setup: if needed) to parallax.yml and capture a session at a revision that has it.
test_configuration_invalidparallax.yml could not be read safely. Correct it in the repository.
replay_no_editsThe session changed no file. Transcript review still runs; there is nothing to prove.
replay_mismatchThe recorded edits do not apply to the recorded revision. Keep the guard current (it updates itself once a day) so the snapshot, not tool replay, carries the edits.
replay_program_unsupportedA shell command or a patch program changed files the tool record cannot reconstruct. The source snapshot is the answer: see "source snapshot" reasons below.
source snapshot has omitted files or repositoriesA file was too large, binary, a secret path, or a nested repository was out of scope. Keep generated or vendored files out of the session's diff, or commit them before the session.
source snapshot predates the current captured sessionThe session kept going after its last snapshot. It is swept again at the next Stop.

A blind fork on the transcript alone still runs the first time a session has no source, and never a second time.

GET /v1/sweeps carries the reason on each sweep’s line. After the fix, the next session in that repository is captured with source; a stored session is not backfilled with today’s files.

Source: packages/server/cards/workspace-failure.output.json · packages/server/cards/source-workspace.input.json · packages/server/cards/sweep-once.input.json · packages/server/cards/sweep-line.json

The hook, the connect PR and the file itself are on Set up; the keys of parallax.yml are on Control.