Artifacts
What the agent captures as evidence, and what it does not.
An artifact is a single piece of captured evidence from a run. Each one is hashed with SHA-256 the moment it is produced and stored under that hash, with the phase it came from, its position in the run, and the finding it supports if it supports one.
The kinds written during a run¶
| Stored kind | Written when |
|---|---|
command_log |
The agent runs a shell command. The artifact is JSON holding the command, its stdout and stderr, the exit code, the start and end times and the duration. |
req_resp |
The agent makes an HTTP request with its request tool, and again for the navigation behind a browser screenshot. The artifact is JSON holding the request and the response. |
screenshot |
The agent takes a browser screenshot. The artifact is the PNG, with filename set to screenshot.png. |
other |
The model attaches evidence to a finding and declares it as a payload or a file. |
The first three come from tool calls the agent makes directly. other comes
only from the fourth path below.
Evidence the model attaches to a finding¶
When the model records a finding, or adds to one, it supplies evidence items
alongside the description. Each item declares a kind, and the tool schema offers
six: command_output, screenshot, payload, request, response and
file. That is the model's vocabulary, not the stored one. Each declared kind
is translated before the capture is written:
command_outputbecomescommand_logscreenshotbecomesscreenshotrequestandresponseboth becomereq_resppayloadandfileboth becomeother
The item's content is stored as UTF-8 bytes, hashed like any other artifact, and
linked to the finding. A payload or a file is not dropped and does not
raise; it lands in the locker under the kind other, which carries no
information about what the model meant by it beyond the content itself.
A kind outside those six never reaches the translation. The evidence item fails validation as the finding is being built, the tool call raises, and the run loop counts a failed step: no artifact is written, and on a new finding the finding itself is discarded. On an update, the status, severity and CVSS changes made earlier in the same call survive; the new evidence does not. Five consecutive failed steps abort the run.
What the store permits¶
The artifact schema permits five kinds — the four above plus json_manifest.
Nothing in the product writes json_manifest.
The manifest and the signature block are written into the same content-addressed store at seal time, under their own hashes, so an export can copy the exact signed bytes. They are not artifacts: they have no artifact row, no kind and no position in the chain.
Content addressing¶
An artifact's name is the hex SHA-256 of its bytes. The agent computes it at capture time; the server recomputes it on receipt and refuses bytes that do not hash to the value claimed for them. Accepted bytes are written to a temporary file, made read-only, and renamed into place, so a stored blob is written once and never modified.
A database row pairs that hash with the engagement, under a unique constraint on the pair. One distinct byte string is one row per engagement. Capturing identical bytes a second time within a run is a no-op: the existing row is returned and linked to the new finding if there is one. Two different engagements that produce identical bytes get separate rows and separate files, because the storage path includes the engagement id.
Sequence numbers come from a counter in the agent that starts at zero and advances on every capture, including ones that turn out to be duplicates. A run's stored sequence numbers can therefore have gaps. Order is what the chain depends on, not contiguity.
What each artifact records¶
Beside the kind, the hash and the size, an artifact records:
phase— which of the seven phases the agent was in when it captured this.seq— its position in the run, and the order the hash chain is built in.filename— set for screenshots, absent for the JSON kinds.- Findings it supports — a many-to-many link, so one artifact can back several findings. The link is written when the artifact arrives and is idempotent.
control_ids— empty on arrival, filled at seal time with the union of the framework control identifiers of every finding the artifact is linked to. That union is built from the framework's ATT&CK and WSTG tables only, not from the keyword fallback the report uses elsewhere.
The finding link is what lets the report put a hash beside a claim. For a sealed run, the report reads the manifest rather than the database, groups the manifest's entries by the finding ids recorded in them, and prints each supporting artifact's hash, kind, position and size under the finding. Every hash printed is by construction one the signature covers. An unsealed run has no manifest, so the report renders with no per-finding hashes and says so.
The 10 MiB ceiling¶
One artifact cannot exceed 10 MiB — 10,485,760 bytes exactly, checked as
len(data) > 10 * 1024 * 1024 at both ends, in the agent and again in the
store. The check runs before anything is emitted, so an oversized capture
produces no bytes, no hash and no row; it is dropped, not truncated.
An oversized capture fails the step that produced it
Nothing on the agent's path catches the error. It propagates out of the tool handler and out of the step, where the run loop records an error event and increments the consecutive-error counter. What else is lost depends on which handler was running:
- Shell command. The capture is attempted before the command's output is given back to the model, so the model never sees the output either. The step is lost whole.
- Browser screenshot. The request/response pair is captured first, so an oversized PNG leaves that pair recorded and the image absent.
- Finding evidence. The finding has already been collected and still appears in the results, and any evidence items before the oversized one were captured. The oversized one is not, and the model is not told the call succeeded.
A single failed step does not end a run. Five consecutive ones do.
Last updated 2026-09-22