Skip to main content

Agent workspace ABI

This ABI covers the practice and conversation runners' sandbox workspace at /workspace/. See Conversation runtime for turn context.

For a practice review, the server renders one disposable folder for an owned execution attempt. SandboxLayout owns the runtime and practice-script contract versions.

Folder layout​

/workspace/
├── .sessions/ # Pi session files, outside the context folder.
├── context/ # Authorized frozen source records.
│ ├── scm/ # SCM domain evidence.
│ ├── chat/ # Permitted thread evidence.
│ ├── docs/ # Permitted document evidence.
│ └── people/ # Permitted account context.
├── repos/ # Read-only repository working copies.
├── inputs/
│ ├── history/results.json # Authorized prior results.
│ └── practices/ # Pinned practices and criteria.
├── work/
│ ├── practice-scripts/
│ │ ├── lib/ # Installed core and host toolkit.
│ │ ├── kits/ # Installed domain kits.
│ │ └── practices/ # <slug>.inputs.json and custom <slug>.ts.
│ └── review-input/ # Prepared input for fresh review sessions.
├── out/ # Collected bounded output and diagnostics.
└── task.json # Owned runtime envelope.

The conversation runner executes its compiled program from the agent image in its conversation sandbox. It reads the stored path, situation and system prompt through the run's gateway (GET context). It stages documents and repositories in its local context folder. The practice and conversation runners execute on Node. See ADR 0036.

Source identity and capture facts are frozen before evaluation. A folder path alone is not authorization or durable proof. The source contract owns capture-state meaning. The runtime guide owns execution and retry.

Practice inputs​

Each practice gets one <slug>.inputs.json record. It contains contractVersion 3, kind, unknown domain item, About, practice identity, scope, review metadata and previous result metadata. The host supplies decimal-string bigint IDs, UUIDs and the pinned version. Custom code cannot select another workspace, developer or assignment.

The core toolkit defines the result and host capabilities. A domain kit validates the item before it offers domain evidence helpers. The authored SCM record remains in the SCM domain. Core does not receive a diff type.

For ITEM, scope names the reviewed item. For SCAN, scope names an assigned file at the frozen default-branch commit. The product review's item is the repository, while About remains null. No authored-person history or blame is inferred from repository file content.

Evidence and read receipts​

Read receipts identify source, path, version, read extent and completion state. The host supplies trusted receipts separately from the returned candidate. A search, listing or isolated quotation cannot establish a full assigned-file read. A failed read is unavailable, not proved absence.

For SCAN, repos/reviewed/ contains the pinned files without Git metadata. context/item.json identifies the assigned path, blob and repository commit. The runner checks this record against the frozen assignment before it reads the tree. The record is the scm.repository.tree source witness for scan read receipts and citations.

The server fills verified citation coordinates from captured text. The capture manifest maps source, path, side and version to staged bytes and their digest. Core reads those bytes again and checks their digest before accepting the quote. A domain verifier can refuse unsupported semantics but cannot supply replacement text.

Core checks quotes, ambiguity and criteria warrants. Domain citation verifiers own side, version and change-basis checks. The source contract defines which sources support those coordinates. Archived or redacted proof keeps null text and coordinates instead of fabricated line 1.

Claims that the repository lacks something​

A search receipt (SEARCHED, path .) records a grep for its pattern over the whole checkout. The grep had no file filter and ran to the end. The receipt has no line range, and it proves no file read.

The loop searches the same files again in its own worker thread. The receipt carries the checkout digest of the bytes that this search read. The checkout digest is the SHA-256 of one line <path>\0<SHA-256 of the file>\n for each regular file outside .git, in the byte order of the paths. checkoutDigest in pi-search-worker.ts defines it.

A command in the sandbox can change the sandbox's copy of the checkout. Thus, before the sandbox starts, the server records the digest of its own copy in the job's capture (JobEvidenceFiles.checkoutDigests). Admission counts a search only when its digest equals the recorded one (ADR 0079 § Evidence authority). When a result needs a search and no receipt of it has the recorded digest, admission withholds the result as COVERAGE_INCONSISTENT.

A grep that the runner sees produces a search receipt, also inside a codemode script. A cut, failed, filtered or narrower grep produces none. A file listing, a search through the shell and a pattern over 500 characters produce none. A checkout that the capture holds only in part produces none. A session keeps at most 20 search receipts.

In an item review, a result says that the repository lacks something in one of two ways:

  • Its search warrant or inapplicability warrant names the tree as consulted.
  • The review reads a checkout, captured or not, and a text that the result shows matches an absence marker. The texts are the summary, the explanation, the example note and every warrant field. They also include each problem's summary, explanation, basis and next step, and each open question. AbsenceClaims.java and pi-session-reads.ts hold the same short list of markers. The list reads words, not the meaning of the text, so a claim in other words can pass. A clause bounded to the change or the record, such as "anywhere in the diff", is no claim. A clause that says what the review did not establish is no claim.

Such a result needs a search receipt of the tree. The capture must hold the tree COMPLETE at the version of the receipt, and the receipt's checkout digest must equal the capture's. A could-not-determine result says nothing about the work, so it needs no receipt.

A result keeps only the search receipts of the decision that it keeps. A review session's unchanged result keeps the searches of that session. A result that code decided keeps the searches of the script's reader.

The orchestrator prompt tells the model to grep the checkout and name the tree for such a claim. Before the runner submits a result without the receipt, it returns it to the model once for each practice. The runner submits the next result of that practice as it is. Thus, a session that cannot repair the claim does not use up the refusals of the practice. When the capture does not hold the whole checkout, the runner asks the model to remove the claim.

Then admission withholds a result without the receipt. The reason is source_unavailable when the capture does not hold the whole checkout. Otherwise the reason is consulted_source_unread.

A search with the recorded checkout digest backs a claim only when it also names what the claim says is absent. Admission does not run a model's pattern. It reads the words of a plain pattern: words joined by |, with \b or a leading (?i) if needed. Any other pattern names nothing.

Each claim must hold one of those words. A search that ignored case must be able to find the claim's word: auth counts for "authentication", and retries does not count for "retry". A search that did not ignore case must match the word as written. A clause with only generic words takes lookedFor as its claim. Otherwise the reason is absence_not_searched. The runner asks the session before each of these reasons, once for each.

A match does not refute "no other file references X", so a search with matches can back a claim. When a search that names a claim finds matches, the result must account for them. A Met or Not met result uses evidence.search.hits. A Does not apply result uses evidence.inapplicability.hits.

The account says what the matches are and why they do not show the thing. Otherwise the reason is absence_hits_unexplained. Admission reads that this text exists, not that it is true: a reader sees it on the result.

Admission and composition​

Candidates use the strict shape in the script runner. The server allocates result and problem IDs during atomic admission. Its response includes those identities, verified citations and public eligibility. A composer cannot invent problem references or substitute failed candidate fragments.

The workspace stops at admission. The composer reads no file of this workspace, only answers of the gateway. Composition reads the admitted result record, the inputs that admission retained and authorized history. Confirmed earlier feedback words enter only after custody and source-access checks. Unavailable history is a refusal, not empty history. Prepared feedback commits its exact basis and physical copies before egress.

Output and custody​

Collected output is bounded runtime data, not the sole owner of a product review. Reviews, assignments and feedback receipts remain durable independently of raw logs. A stopped or cancelled attempt cannot publish through its earlier fence. Unknown external visibility authorizes readback, never an automatic repeat write.

Erasure tracks every person's evidence dependency, including work copied into another developer's review. PostgreSQL records evidence erasure. Each server removes its local erased copies. The data map owns export, retention and physical erasure.

Practice script validation and limits​

New scripts import the core author entry point and the appropriate domain kit. They return one candidate, without result-level severity or positional context. The runtime validates declared model access and brokers each call through credential-free IPC. Proof beyond a bound is withheld rather than silently truncated. The operator script guide owns author tests and model limits.