[GH-ISSUE #8364] [Design] Preserve contributing evidence for address-context handoffs #6566

Open
opened 2026-11-01 21:12:17 +00:00 by bsakaguchi · 6 comments
Owner

Originally created by @morluto on GitHub (Oct 31, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8364

Before submitting

  • I searched the open and closed issues and pull requests for a related discussion.
  • I understand the contribution scope must be reviewed before integration; open questions are welcome here.
  • This discussion does not include credentials, private samples, or sensitive target data.

Decision needed

How should an agent retain and identify the contributing Evidence and unavailable facets of an inspect_address_context result when handing an investigation to another session?

Context and evidence

On a real Ghidra MCP session for an owned raw x86-64 fixture, inspect_address_context({"address":"0x1007"}) returned a containing function and a null name, with comments and bookmarks explicitly unavailable. The response had no Evidence ID or links. Nevertheless, get_evidence_bundle immediately afterward contained separately recorded address_name and resolve_containing_procedure Evidence for that request. The unavailable facets and the aggregate answer had no corresponding record in that bundle.

I then set a session-only name audit_hit at the same address and repeated the context query. The second response showed the new name, but again did not link its contributors. The bundle now contained both address_name records, with identical document/address parameters and profile digest but different observed names and Evidence IDs. The caller can inspect retained records, including their values, but the two aggregate responses do not identify which records they used. Neither response retains its unavailable facets as an aggregate observation.

As a positive handoff control, search_bytes, inspect_native_instruction, and an explicit resolve_containing_procedure each returned Evidence IDs. After export_evidence_bundle, close, a fresh MCP server, and import_evidence_bundle, all three complete records matched the originals exactly. The target's SHA-256 stayed unchanged. Both observed context results and their successful component records existed before export; no claim is made that the underlying facts were all lost.

Expected interaction with targets

Read the selected local target and use a caller-selected static analysis provider; the test used an explicitly requested ephemeral Ghidra label edit, not executable mutation or target execution. Export writes only to a caller-selected owned bundle.

Boundary details (optional)

Preserve the exact address and document, each successful observation's provider/profile/identity and limitations, and unavailable facets as unavailable rather than absent. Results can reflect different session states even with the same target and analysis profile; avoid inventing an ordering, historical snapshot, or atomicity guarantee. Existing Evidence already contains successful component observations, so duplicating full payloads or adding an extra provider query may be unnecessary.

Options and tradeoffs

Compare returning references to the contributing retained records and explicit facet outcomes, recording a bounded composed observation with those references, or documenting a deliberate transient-only context workflow with a discoverable way to obtain individually citable facts. Consider current session-tool semantics and token cost; avoid adding a new provider-specific wrapper merely for parity.

Verification available so far

I have a prototype or observed results.

Success criteria and open questions

Test an unchanged address, a session annotation between observations, unavailable facets, mixed provider outcomes, and export/import after close. Show how an agent identifies the correct retained observations and which partial outcomes were actually observed without treating a missing record as an absent comment. Validate supported MCP output profiles and the intended offline route. The experiment used scripted calls, not an autonomous-agent evaluation; other providers and platforms remain untested.

References

  • inspect_address_context contract and composing implementation.
  • #8363 covers the separate Ghidra comment-facet availability and recovery issue; #6404 covers broader arbitrary-address comment operations.
  • REA d54a4661da20382ad8456564a08569fdd24131c5, Node 24.22.1, macOS 26.3 arm64, Ghidra 12.1.4, JDK 21, TypeScript MCP SDK 2.3.1. Fixture SHA-256 58658a4b21d7ffc610f7123d0f4e9435c3ba63b54facea49da1bb9bb51cd8cc5.

cc @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0

Originally created by @morluto on GitHub (Oct 31, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8364 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related discussion. - [x] I understand the contribution scope must be reviewed before integration; open questions are welcome here. - [x] This discussion does not include credentials, private samples, or sensitive target data. ### Decision needed How should an agent retain and identify the contributing Evidence and unavailable facets of an `inspect_address_context` result when handing an investigation to another session? ### Context and evidence On a real Ghidra MCP session for an owned raw x86-64 fixture, `inspect_address_context({"address":"0x1007"})` returned a containing function and a null name, with comments and bookmarks explicitly unavailable. The response had no Evidence ID or links. Nevertheless, `get_evidence_bundle` immediately afterward contained separately recorded `address_name` and `resolve_containing_procedure` Evidence for that request. The unavailable facets and the aggregate answer had no corresponding record in that bundle. I then set a session-only name `audit_hit` at the same address and repeated the context query. The second response showed the new name, but again did not link its contributors. The bundle now contained **both** `address_name` records, with identical document/address parameters and profile digest but different observed names and Evidence IDs. The caller can inspect retained records, including their values, but the two aggregate responses do not identify which records they used. Neither response retains its unavailable facets as an aggregate observation. As a positive handoff control, `search_bytes`, `inspect_native_instruction`, and an explicit `resolve_containing_procedure` each returned Evidence IDs. After `export_evidence_bundle`, close, a fresh MCP server, and `import_evidence_bundle`, all three complete records matched the originals exactly. The target's SHA-256 stayed unchanged. Both observed context results and their successful component records existed before export; no claim is made that the underlying facts were all lost. ### Expected interaction with targets Read the selected local target and use a caller-selected static analysis provider; the test used an explicitly requested ephemeral Ghidra label edit, not executable mutation or target execution. Export writes only to a caller-selected owned bundle. ### Boundary details (optional) Preserve the exact address and document, each successful observation's provider/profile/identity and limitations, and unavailable facets as unavailable rather than absent. Results can reflect different session states even with the same target and analysis profile; avoid inventing an ordering, historical snapshot, or atomicity guarantee. Existing Evidence already contains successful component observations, so duplicating full payloads or adding an extra provider query may be unnecessary. ### Options and tradeoffs Compare returning references to the contributing retained records and explicit facet outcomes, recording a bounded composed observation with those references, or documenting a deliberate transient-only context workflow with a discoverable way to obtain individually citable facts. Consider current session-tool semantics and token cost; avoid adding a new provider-specific wrapper merely for parity. ### Verification available so far I have a prototype or observed results. ### Success criteria and open questions Test an unchanged address, a session annotation between observations, unavailable facets, mixed provider outcomes, and export/import after close. Show how an agent identifies the correct retained observations and which partial outcomes were actually observed without treating a missing record as an absent comment. Validate supported MCP output profiles and the intended offline route. The experiment used scripted calls, not an autonomous-agent evaluation; other providers and platforms remain untested. ### References - [`inspect_address_context` contract](https://github.com/morluto/rea/blob/d54a4661da20382ad8456564a08569fdd24131c5/src/contracts/sessionToolContracts.ts#L182-L185) and [composing implementation](https://github.com/morluto/rea/blob/d54a4661da20382ad8456564a08569fdd24131c5/src/application/AnalysisContextQueries.ts#L80-L137). - #8363 covers the separate Ghidra comment-facet availability and recovery issue; #6404 covers broader arbitrary-address comment operations. - REA `d54a4661da20382ad8456564a08569fdd24131c5`, Node 24.22.1, macOS 26.3 arm64, Ghidra 12.1.4, JDK 21, TypeScript MCP SDK 2.3.1. Fixture SHA-256 `58658a4b21d7ffc610f7123d0f4e9435c3ba63b54facea49da1bb9bb51cd8cc5`. cc @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0
Author
Owner

@morluto commented on GitHub (Nov 1, 2026):

A real cross-provider control shows that adding references to already retained component records would not cover every provider. On REA main 6e9ee97a, I queried the same function address twice on an unexecuted source-owned arm64 Mach-O, exported the bundle, and imported it into a fresh target-free MCP server:

  • Ghidra 12.1.4 retained component records. After an explicit session-only function rename between calls, the two name values had distinct retained address_name Evidence IDs; neither context response identified its contributors. The exact records survived export/import.
  • Kuna 1.986 returned the function name in both context results, but its bundle contained zero records after those calls. A subsequent direct address_name with the same address and document returned the same name in this control, produced its own Evidence record, and that record survived export/import. It is a later observation, so repeating the query cannot generally prove it was the observation used by the earlier context result.

The direct Kuna Evidence also retained the provider's limitations, while the context result had no limitations field. Source-derived explanation: BinarySession retains internal source observations only when isSourceEvidenceRetainable admits the descriptor; Kuna declares cachePolicy: "live" and filesystem writes, which that gate excludes. The direct public tool independently constructs and retains its returned Evidence. This is not evidence that disabling Kuna's live policy or enabling snapshot replay would be safe.

A complete design therefore needs to preserve the actual contributing observation and its provenance when no internal Evidence ID exists, not synthesize a link, re-query later, or promote an unavailable facet to absence. The context aggregate's unavailable outcomes still need an explicit handoff policy. Both runs used the TypeScript MCP SDK 2.3.1 with fetched full output schemas on macOS arm64; the target SHA-256 ffad1b70c6a5a7ef3f7c3f59126be516520210777d949b35e97c9067f272aa2d was unchanged and the target was not executed. This is one fixture per provider, not a general provider or agent-behavior measurement.

<!-- gh-comment-id:6551988323 --> @morluto commented on GitHub (Nov 1, 2026): A real cross-provider control shows that adding references to *already retained* component records would not cover every provider. On REA main `6e9ee97a`, I queried the same function address twice on an unexecuted source-owned arm64 Mach-O, exported the bundle, and imported it into a fresh target-free MCP server: - Ghidra 12.1.4 retained component records. After an explicit session-only function rename between calls, the two name values had distinct retained `address_name` Evidence IDs; neither context response identified its contributors. The exact records survived export/import. - Kuna 1.986 returned the function name in both context results, but its bundle contained **zero** records after those calls. A subsequent direct `address_name` with the same address and document returned the same name in this control, produced its own Evidence record, and that record survived export/import. It is a later observation, so repeating the query cannot generally prove it was the observation used by the earlier context result. The direct Kuna Evidence also retained the provider's limitations, while the context result had no limitations field. Source-derived explanation: `BinarySession` retains internal source observations only when `isSourceEvidenceRetainable` admits the descriptor; Kuna declares `cachePolicy: "live"` and filesystem writes, which that gate excludes. The direct public tool independently constructs and retains its returned Evidence. This is not evidence that disabling Kuna's live policy or enabling snapshot replay would be safe. A complete design therefore needs to preserve the actual contributing observation and its provenance when no internal Evidence ID exists, not synthesize a link, re-query later, or promote an unavailable facet to absence. The context aggregate's unavailable outcomes still need an explicit handoff policy. Both runs used the TypeScript MCP SDK 2.3.1 with fetched full output schemas on macOS arm64; the target SHA-256 `ffad1b70c6a5a7ef3f7c3f59126be516520210777d949b35e97c9067f272aa2d` was unchanged and the target was not executed. This is one fixture per provider, not a general provider or agent-behavior measurement.
Author
Owner

@morluto commented on GitHub (Nov 1, 2026):

A controlled real-Ghidra MCP experiment adds a constraint to the Evidence handoff design: internal component retention can itself fail after a provider observation, while a sibling facet and its record succeed. With an injected 20,000-byte investigation representation allowance, inspect_address_context returned an available name and an unavailable procedure; one name record remained in the bundle. The subsequent direct procedure call returned a typed resource_constraint with the actual retention recovery, but the aggregate had advised that direct call without that recovery. See #8421 for the exact setup and limits.

For this design, recording an aggregate or additional source records must account for admission failure and preserve already committed records and partial outcomes. The injected allowance is a controlled boundary, not a claim that the production default was exhausted, and this does not justify caching live Kuna observations or silently dropping failed admissions.

<!-- gh-comment-id:6552303290 --> @morluto commented on GitHub (Nov 1, 2026): A controlled real-Ghidra MCP experiment adds a constraint to the Evidence handoff design: internal component retention can itself fail after a provider observation, while a sibling facet and its record succeed. With an injected 20,000-byte investigation representation allowance, `inspect_address_context` returned an available name and an unavailable procedure; one name record remained in the bundle. The subsequent direct procedure call returned a typed `resource_constraint` with the actual retention recovery, but the aggregate had advised that direct call without that recovery. See #8421 for the exact setup and limits. For this design, recording an aggregate or additional source records must account for admission failure and preserve already committed records and partial outcomes. The injected allowance is a controlled boundary, not a claim that the production default was exhausted, and this does not justify caching live Kuna observations or silently dropping failed admissions.
Author
Owner

@morluto commented on GitHub (Nov 1, 2026):

A real Kuna 1.986 control confirms the retention distinction also matters at the boundary: with an injected zero-byte Evidence allowance, inspect_address_context returned the function name, but the bundle remained empty; a subsequent direct address_name returned resource_constraint. The later refusal does not retroactively describe the earlier context observation. This used the real REA MCP server and SDK with in-memory transport and a controlled BinarySession; it did not exhaust the production default. The owned arm64 target was not executed and its digest was unchanged.

Draft #8424 improves typed recovery for resource refusals encountered by a facet. It cannot create Evidence for the Kuna observation in this control and does not resolve this issue.

<!-- gh-comment-id:6552357705 --> @morluto commented on GitHub (Nov 1, 2026): A real Kuna 1.986 control confirms the retention distinction also matters at the boundary: with an injected zero-byte Evidence allowance, `inspect_address_context` returned the function name, but the bundle remained empty; a subsequent direct `address_name` returned `resource_constraint`. The later refusal does not retroactively describe the earlier context observation. This used the real REA MCP server and SDK with in-memory transport and a controlled `BinarySession`; it did not exhaust the production default. The owned arm64 target was not executed and its digest was unchanged. Draft #8424 improves typed recovery for resource refusals encountered by a facet. It cannot create Evidence for the Kuna observation in this control and does not resolve this issue.
Author
Owner

@morluto commented on GitHub (Nov 1, 2026):

I exercised an application-boundary prototype, not a shipped MCP implementation. It wraps the actual inspectAddressContext provider calls, retains each successful returned observation when an exact session Evidence ID is not already present, and records a derived aggregate containing the facet outcomes and source IDs. The real REA SDK/MCP server opened the targets and exported the bundle; after close, it imported the bundle into a fresh target-free server and session in the same process and recovered the aggregate and referenced sources. An earlier control also used an investigation reset in the original server.

  • Real Ghidra 12.1.4: after a session-only function rename, the first and second aggregates linked different address_name records whose values matched the respective context answers. The executable was unchanged.
  • Real Kuna 1.986: a successful name observation absent from the normal internal ledger was retained with its own original provider, profile and limitations. The aggregate linked that record; allowsSnapshotReplay("address_name") remained false. Two unchanged results produced the same content-derived source and aggregate IDs. Those IDs do not establish two distinct observation times or an atomic snapshot.
  • In a separate real-Kuna zero-byte injected allowance, the naive wrapper called the provider successfully (_main) but converted the source-recording refusal into a facet failure, and then failed to record the aggregate. It lost the available answer. The production context currently returns that name in this control, so adopting this prototype as-is would regress it.

The practical design constraint is to distinguish the successful observation from the outcome of retaining it, preserve any already committed sources and exact limitations, and give an explicit recovery when retention fails. An absent ID must not imply absent data or a retained record. If a compact inline answer references a record, its closure must actually be retained and exportable; do not promise an ID before admission. Also handle cancellation after a source returns, mixed facet outcomes, bounded storage, and an aggregate-record refusal. The right UX for an unretained completed observation remains an open design question.

All runs used one owned static arm64 Mach-O, Ghidra 12.1.4/JDK 21 or Kuna 1.986 on macOS arm64, Node 24.22.1 and an in-memory TypeScript MCP SDK client/server. Target SHA-256 ffad1b70c6a5a7ef3f7c3f59126be516520210777d949b35e97c9067f272aa2d was unchanged and the target was not executed. This prototype did not validate a new public MCP schema, a fresh server process, concurrency/cancellation, a real default-heap exhaustion, Windows or autonomous-agent behavior; the existing issue's earlier control separately covered fresh-server import for existing records.

<!-- gh-comment-id:6552377546 --> @morluto commented on GitHub (Nov 1, 2026): I exercised an **application-boundary prototype**, not a shipped MCP implementation. It wraps the actual `inspectAddressContext` provider calls, retains each successful returned observation when an exact session Evidence ID is not already present, and records a derived aggregate containing the facet outcomes and source IDs. The real REA SDK/MCP server opened the targets and exported the bundle; after close, it imported the bundle into a fresh target-free server and session in the same process and recovered the aggregate and referenced sources. An earlier control also used an investigation reset in the original server. - Real Ghidra 12.1.4: after a session-only function rename, the first and second aggregates linked different `address_name` records whose values matched the respective context answers. The executable was unchanged. - Real Kuna 1.986: a successful name observation absent from the normal internal ledger was retained with its own original provider, profile and limitations. The aggregate linked that record; `allowsSnapshotReplay("address_name")` remained false. Two unchanged results produced the same content-derived source and aggregate IDs. Those IDs do not establish two distinct observation times or an atomic snapshot. - In a separate real-Kuna zero-byte injected allowance, the naive wrapper called the provider successfully (`_main`) but converted the source-recording refusal into a facet failure, and then failed to record the aggregate. It lost the available answer. The production context currently returns that name in this control, so adopting this prototype as-is would regress it. The practical design constraint is to distinguish the successful observation from the outcome of retaining it, preserve any already committed sources and exact limitations, and give an explicit recovery when retention fails. An absent ID must not imply absent data or a retained record. If a compact inline answer references a record, its closure must actually be retained and exportable; do not promise an ID before admission. Also handle cancellation after a source returns, mixed facet outcomes, bounded storage, and an aggregate-record refusal. The right UX for an unretained completed observation remains an open design question. All runs used one owned static arm64 Mach-O, Ghidra 12.1.4/JDK 21 or Kuna 1.986 on macOS arm64, Node 24.22.1 and an in-memory TypeScript MCP SDK client/server. Target SHA-256 `ffad1b70c6a5a7ef3f7c3f59126be516520210777d949b35e97c9067f272aa2d` was unchanged and the target was not executed. This prototype did not validate a new public MCP schema, a fresh server process, concurrency/cancellation, a real default-heap exhaustion, Windows or autonomous-agent behavior; the existing issue's earlier control separately covered fresh-server import for existing records.
Author
Owner

@morluto commented on GitHub (Nov 1, 2026):

Additional constraint for composed Evidence handoff: on #8426 (dd2b15bf4cbbc5409d6ab15b6776e13ce18b7f78), source admission can fail after the provider completed the primary operation. A real selected-project Ghidra test and a same-operation synthetic source test are documented in #8425. The inline unretained partial then represents the refused source, not necessarily the requested result; matching operation/parameters is insufficient to infer its role. Handoff needs to preserve primary vs. source identity and each record's retention state, and must not claim complete retained link closure when a source was refused.

<!-- gh-comment-id:6552680339 --> @morluto commented on GitHub (Nov 1, 2026): Additional constraint for composed Evidence handoff: on #8426 (`dd2b15bf4cbbc5409d6ab15b6776e13ce18b7f78`), source admission can fail after the provider completed the primary operation. A real selected-project Ghidra test and a same-operation synthetic source test are documented in #8425. The inline unretained partial then represents the refused source, not necessarily the requested result; matching operation/parameters is insufficient to infer its role. Handoff needs to preserve primary vs. source identity and each record's retention state, and must not claim complete retained link closure when a source was refused.
Author
Owner

@morluto commented on GitHub (Nov 1, 2026):

A related mixed-failure handoff case is now reproduced through public MCP: one facet's source retention fails after its name observation completed, while a separate facet reports incomplete cleanup. The context result correctly reports the cleanup owner but omits that source partial and completed name. See the controlled reproduction and limits.

This adds a failure-path constraint to the aggregate design: preserve cleanup ownership, cancellation ordering, and available partial facts together when delivery permits. The test uses a simulated provider and injected zero-byte ledger; it does not reproduce a real Ghidra or Kuna cleanup failure.

<!-- gh-comment-id:6553908574 --> @morluto commented on GitHub (Nov 1, 2026): A related mixed-failure handoff case is now reproduced through public MCP: one facet's source retention fails after its name observation completed, while a separate facet reports incomplete cleanup. The context result correctly reports the cleanup owner but omits that source partial and completed name. See the [controlled reproduction and limits](https://github.com/morluto/rea/issues/8425#issuecomment-6553905863). This adds a failure-path constraint to the aggregate design: preserve cleanup ownership, cancellation ordering, and available partial facts together when delivery permits. The test uses a simulated provider and injected zero-byte ledger; it does not reproduce a real Ghidra or Kuna cleanup failure.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6566
No description provided.