5.4

export_evidence_bundle, evidence-export, and evidence-import

An Evidence bundle is seven sorted arrays in canonical JSON, so the same records give the same bytes from the MCP server and from the CLI.

Your agent's MCP connection closes tonight, and with it the eight records it collected on ledger-arm64-O1. Tomorrow a colleague wants to check them on another Mac, without Ghidra. When you finish this section, you can export a session to a bundle and read its summary without an engine. You can also import it into a new session or a CLI check.

export_evidence_bundle

Evidence bundle: one JSON object with seven arrays. They are artifacts, providers, environments, scenarios, captures, unknowns, and records. The first five are built from the records themselves. They hold the distinct subjects as digest, format, and architecture, the distinct providers, and the distinct environments. Then come the controlled-replay records and the process-capture records. Records are sorted by evidence_id, and the order carries no meaning.

The MCP tool takes a path and an overwrite flag. It answers with the path, the byte count, and the record and unknown counts.

export_evidence_bundle after eight calls on ledger-arm64-O1jsonFull log
"structuredContent": {
  "result": {
    "path": "$WORK/bundle-O1.json",
    "bytes": 178853,
    "records": 8,
    "unknowns": 0
  }
}
The request and the text copy of the result are cut.

The write is canonical and atomic. writeEvidenceBundle validates the bundle again and streams canonical JSON parts. writeTextParts creates a staging file with wx and mode 0600 and syncs it. It then renames the file over the destination when overwrite is true, or hard-links it when the destination must not exist. The contract says the same: a complete bundle export streams canonical JSON into a file that appears in one atomic step.

The capture copied the file the server wrote to 11-bundle-O1.json, with the capture directory masked and a newline added. Its seven arrays hold one artifact, one provider, no environments, no scenarios, no captures, no unknowns, and eight records.

the two bundle files of the capturetextFull log
94fca3db…  178398  11-bundle-O1.json (the bytes export_evidence_bundle wrote, with the capture path masked and a newline added)
6130b135…  286433  11-bundle-O2.json (the bytes export_evidence_bundle wrote, with the capture path masked and a newline added)
Nothing is cut.

inspect-evidence-bundle

You do not need rea's engine, or a session, to read a bundle. rea inspect-evidence-bundle takes a JSON argument with path, detail, and optional filters and prints one row per record without the payloads. The MCP tool get_evidence_bundle shares the projection for the records of a live session.

the summary of bundle O1, the first row and the first dossier rowjsonFull log
{
  "kind": "evidence-bundle-summary",
  "filters": {},
  "total_retained_records": 8,
  "matching_records": 8,
  "unknown_revisions": 0,
  "records": [
    {
      "evidence_id": "ev_2c883c7b…",
      …
      "operation": "procedure_assembly",
      …
      "limitation_count": 6,
      "native_dossier": {
        "available": false,
        "reason": "different-operation"
      }
    },
    …
    {
      "evidence_id": "ev_41e8ca1c…",
      …
      "operation": "analyze_function",
      "confidence": "observed",
      "authority": "shipped-artifact",
      "analysis_profile_digest": "3fc72c9b…",
      "retention": "complete-record",
      "limitation_count": 10,
      "native_dossier": {
        "available": true,
        "procedure_address": "0x100000518",
        "procedure_name": "_grade",
        …
The subject and provider of each row, the second row, and the last five rows are cut.

The rows are in evidence_id order, so the first row is the procedure_assembly record and the _grade dossier is the third. A row that is not a dossier reports native_dossier as unavailable, with the reason different-operation. The eight rows name four analyze_function dossiers, _percent_change, _grade, _total, and _grade_label, and one record each for list_procedures, procedure_assembly, xrefs, and list_strings. Every row carries the same profile digest, 3fc72c9b…, because one Ghidra session produced them all. The O2 bundle has two rows, the _total and _percent_change dossiers of the -O2 build (11-inspect-evidence-bundle-O2.json).

The contract names the filters a summary accepts. The optional filters match an exact evidence_id, operation, target_sha256, analysis_profile_digest, or procedure_address.

evidence-export and evidence-import on the CLI

rea evidence-export SOURCE OUTPUT reads a bundle, validates it, and writes it again in canonical form. The answer is the path, the byte count, and the record count. The capture exported 11-bundle-O1.json to a work file and compared the digests.

the MCP export and the CLI export of the same recordstextFull log
94fca3db…  178398  export_evidence_bundle over MCP, out/11-bundle-O1.json
47160c2d…  178397  evidence-export over the CLI, $WORK/canonical-O1.json
same bytes as the MCP export: no
same JSON content as the MCP export: yes
Nothing is cut.

The one byte of difference is the newline the kit appended to its copy. Strip it, and the copy has 178,397 bytes and the digest 47160c2d, the digest of the CLI file. The MCP server and the CLI write the same canonical bytes for the same records, which is what the thesis of this section promised.

rea evidence-import PATH validates a bundle and reports its counts. On the CLI there is no session, so imported and total are both the number of records in the file: {"imported": 8, "unknowns_added": 0, "total": 8} in 13-evidence-import.json. The CLI documentation draws the line: separate CLI calls read the full saved records.

import_evidence_bundle into a live session

In MCP the same verb merges into the connection's ledger. The handler reads and validates the file, calls importEvidenceBundle, and answers with the records added, the unknown revisions added, and the new total. The ledger adds each record it does not hold and keeps a record it already holds. It refuses a record whose id it holds with different content: "Conflicting evidence record".

bundle O1 imported into the session that analysed O2jsonFull log
"result": {
  "imported": 6,
  "unknowns_added": 0,
  "total": 15
}
The request and the text copy of the result are cut.

Six of eight, and a total of fifteen. The session had closed O1 before, and close_binary empties the ledger, even when no binary is open. Opening another target or restarting the current target keeps the records.

So the O1 records were gone when O2 opened. Then compare_functions ran twice, with the O1 dossiers of _percent_change and _total passed inline. The tool records both sides before its own result. Those two O1 records were back in the ledger, so the import added the other six.

The fifteen are the six new records and nine that were already there:

  • The two O2 dossiers, from calls 17 and 18.
  • The two O1 dossiers that the comparisons recorded as their sources.
  • The two comparison records, from calls 20 and 21.
  • The verify_reconstruction record, from call 22.
  • The two mutation records of the unknowns that the two comparisons opened (residual unknowns).

Figure 5.5 follows the bundle from the session to its four readers.

Fig. 5.5one bundle from the MCP session to four readersflow
One bundle from the MCP session to four readers Left: the MCP session on ledger-arm64-O1 holds eight records and no unknowns, and export_evidence_bundle writes them. Middle: bundle-O1.json, 178,853 bytes as written, 178,398 bytes in the masked kit copy, seven arrays of canonical JSON. Right: four readers. rea inspect-evidence-bundle prints eight rows and zero unknown revisions. rea evidence-export writes canonical-O1.json with 178,397 bytes and the same JSON. rea evidence-import validates it and reports imported 8 and total 8. import_evidence_bundle in the O2 MCP session reports imported 6 and total 15, because two of the eight records were already in that ledger. THE LEDGER THE FILE THE READERS MCP session, O1 8 records, 0 unknowns ledger of one connection bundle-O1.json 178,853 bytes written kit copy 178,398 bytes 7 arrays, canonical export inspect-evidence-bundle 8 rows, 0 unknowns evidence-export 178,397 bytes, same JSON sha256 47160c2d evidence-import imported 8, total 8 import_evidence_bundle into the O2 session imported 6, total 15 The server wrote 178,853 bytes. The kit masked the path and added a newline, so its copy has 178,398 bytes. Without the newline it has the digest of canonical-O1.json. 2 of 8 already in the ledger
One canonical file leaves the session, and the CLI summary, the CLI export, the CLI import, and a second MCP session all read it without Ghidra. Read left to right. Solid teal is a durable write, solid ink a call.

A bundle from an earlier release

The release that reads a bundle checks every id with its own hash rule. The kit kept the O1 bundle that rea 6.5.0 wrote for this manual, and gave it to rea evidence-import at 6.7.0. The import stopped with exit code 1.

rea 6.7.0 refuses the O1 bundle of rea 6.5.0jsonFull log
{
  "error": "Analysis failed",
  "code": "evidence_integrity_mismatch",
  "category": "integrity_mismatch",
  "message": "Evidence bundle does not match the bundle schema at records.3.evidence_id: Evidence semantic identifier does not match its record. Supply an unmodified bundle written by export_evidence_bundle or rea evidence-export.",
  "retryable": false,
  …
}
The remediation and the details are cut.

The bundle bytes are the bytes that 6.5.0 wrote. Release 6.5.0 left a null relationship_coverage out of the semantic projection, and 6.7.0 hashes it as null (the Evidence record). Records 0 to 2 of the old bundle are analyze_function dossiers with coverage, and their ids still verify. Record 3 is the list_procedures record, the first one with null coverage, so it is the first id that fails.

The error is not retryable, and rea does not migrate saved Evidence. Keep the rea version with every bundle you archive, so that you can read the bundle again with that version. To use old evidence with a new release, run the analysis again with that release and export a new bundle.

rea (rea-agents) with the Ghidra provider rea-agents 6.7.0, Ghidra 12.1.4 · 27dc42d · 2026-10-18 · Edition 2026.10