AnalysisSnapshot and the exact-match answer cache
A snapshot caches exact answers under a query id bound to the target digest and the profile digest, and the dispatch of one mutating call turns the cache off.
The capture ran rea function fixtures/ledger-arm64-O1 NAME --provider ghidra four times, once per function, and each run took between 7.9 and 9.0 seconds (the capture). Every run imported the same 33,872 bytes into a fresh Ghidra project and threw the project away. You want the second question about the same file to be free. When you finish this section, you can say which calls a snapshot can answer and build the query id by hand. You can also predict when rea refuses to replay or to save.
Three things called a snapshot
The word covers three things in rea, and only one of them is a cache you can keep.
Target snapshot: the private copy of the input that the Ghidra launcher imports with -readOnly and -deleteProject. It is deleted when the session closes, and nothing of the analysis survives it. Every new provider session imports the target again into a new temporary project.
Analysis snapshot cache: the in-memory table of one session, AnalysisSnapshotCache, which holds up to 10,000 entries for one target identity. This is the cache this section is about.
Snapshot file: the JSON file that close_binary writes and open_binary reads. It holds the target identity, the provider binding, the cache entries, and the session's Evidence bundle. The CLI documentation says what it is and what it is not. It holds cached observations, not a saved provider database. Only an exact query that qualifies can reuse a result, and a new or live query can still start the provider.
The query id
Query id: query_ followed by the SHA-256 of canonical JSON holding four things: the target, the binding, the operation, and the parameters. The target is the path-free identity, sha256, kind, format, and architecture. A raw-binary target also carries its entry_point. The binding is the provider and its analysis profile. For the capture that is the digest that starts 01b5d08b, executable, mach-o, arm64, Ghidra 12.1.4, and the profile below.
"upstream_analysis_profile": {
"provider": {
"id": "ghidra",
"name": "Ghidra",
"version": "12.1.4"
},
"parameters": {
"target_kind": "executable",
"target_format": "mach-o",
"architecture": "arm64",
…
"function_references": "complete-body-and-entry-reference-manager-v2",
…
"analyzer_preset": "ghidra-default"
},
"digest": "3fc72c9b…"
},The parameters are version strings, one per observation contract that the Ghidra adapter makes. The digest is the SHA-256 of the canonical JSON of provider and parameters. A cache entry matches only when the binding's provider and digest equal the selected profile's. A release that moves function_references from -v2 to -v3 changes the digest. Every entry made under the old digest then stops matching. That is what exact means in the thesis of this section.
Version 6.7.0 is such a release. For this Mach-O target the parameter list kept its 25 names, and two values moved. instruction_references went from exact-address-incoming-v1 to exact-address-incoming-without-synthetic-entry-v2. function_boundary_observations went from ghidra-function-boundary-limitations-v3 to -v4. The digest went from 966e6016… to 3fc72c9b…, so no 6.5.0 snapshot entry matches a 6.7.0 session. One Ghidra session made all eight rows of the bundle summary, so all of them carry the digest 3fc72c9b… (export and import).
Which calls the cache answers
isSnapshotCacheable decides, before any provider runs, whether a call can be answered from the cache. A call qualifies when all of these hold.
- The operation is not one of the stateful ones,
health,current_address,current_procedure,current_document,goto_address, orlist_documents. - An operation that is scoped to a document names its
document. - An operation that defaults to the cursor, such as
xrefs, names itsaddress. - The capability declares
mutatesArtifact: false, acachePolicyother thanlive, and no permission change. - Its
cachePolicyissnapshot, or it does not write to the file system.
The Ghidra adapter marks every operation except the mutating ones and list_documents with cachePolicy: "snapshot". prepareSessionExecution computes the id and looks it up. BinarySession returns a hit without touching the provider. The replayed answer carries one extra limitation: "Loaded from a local REA analysis snapshot; this call did not re-run the provider.".
The replayed record names the path of the target that is open now. The function rebindEvidenceToTargetPath writes that path into the stored record. The evidence_id does not change, because the path is outside the hash. On Windows, Ghidra must admit the source file before a replay, so a cache hit can still start the provider.
On a miss the provider runs, and the result is bound to the profile and the target. An Evidence record is then created and recorded, and a cache entry is stored under the id.
The capture shows no replay, because no two of its 25 MCP calls had the same operation and parameters. The first Ghidra-backed call after open_binary was list_procedures, and it took between 6 and 7 seconds while the headless import ran. Every later analysis call on that target took under 100 ms (the capture). Those later calls were fast because the bridge was already running, not because of the cache. A repeat of analyze_function with "procedure": "_percent_change" would have been the first replay.
Save and load
close_binary takes snapshot_path and overwrite. closeWithSnapshot drains the calls in progress, exports the snapshot, writes the file, and closes the provider. Its receipt adds primitive_entries, workflow_entries, and evidence_records. open_binary takes snapshot_path too, reads the file before it opens the target, and imports the entries into the new session. The capture closed both targets without a path, so its two close_binary results are null (10-mcp-15-close-binary-O1.json, 10-mcp-25-close-binary-O2.json). On the CLI the flag is --snapshot.
An exact cache hit is normally read before rea starts a provider process. On Windows the Ghidra provider can start first, because it must admit the source file.
Loading a file is a full check. The entries must be in canonical id order, and every query id is recomputed and must match. No id appears twice. Every entry must have an Evidence record in the bundle with the same profile and the same result. A bundle record for another target fails the whole file.
Writing is atomic: the file is staged with mode 0600, synced, and renamed or hard-linked into place. Importing into a session whose target or profile differs fails with "Analysis snapshot profile_mismatch: target, provider, or analysis profile does not match the active binary".
Invalidation before a mutating call
Some Ghidra operations write to the session database, such as annotate_native_function, which renames a function or adds a comment. Each of them lists mutates_artifact in its effects, and each carries a limitation that names the consequence.
{
"operations": [
"annotate_native_function"
],
"effects": [
"mutates_artifact",
"launches_process",
"may_write_filesystem"
],
"limitations": [
…
"Metadata edits invalidate immutable analysis snapshots and are discarded on close. CLI returns the updated dossier before session cleanup; this is not a saved Ghidra project."
],
"available": true,
"reason": null,
"availability_code": null
}Before rea sends such a call to the provider, BinarySession calls invalidateSnapshot. The cache is emptied and a flag is set. From then on every lookup returns nothing, every new result is not stored, and an export fails with "Analysis snapshots are unavailable because analysis metadata may have changed". The error adds the way out: export the Evidence with export_evidence_bundle, close the target without a snapshot, and reopen it. An import after a mutation is refused the same way.
At 6.7.0 the order is: invalidate, then call the provider. A failed reply cannot prove that the write did not reach Ghidra. So a mutating call that fails also turns the cache off. The 6.4.0 release used the other order and invalidated only after a successful call.
The CLI documentation states the rule. A metadata write clears the snapshot cache of the active session, even when its reply fails. The provider can already have applied the write.
rea (rea-agents) with the Ghidra provider rea-agents 6.7.0, Ghidra 12.1.4 · 27dc42d · 2026-10-18 · Edition 2026.10