[GH-ISSUE #8402] [Design] Connect unbound tool recovery to provider selection reasons #6583

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

Originally created by @morluto on GitHub (Nov 1, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/8402

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 paths, or sensitive target data.

Decision needed

When a target opens without a bound deep provider, should tool and mode recovery point to the already recorded candidate-selection reasons, rather than only advising provider installation or configuration?

Context and evidence

On current main 6e9ee97a69cb16c7150d8b550e705cba055ee4f0, macOS arm64, Node 24.22.1, Ghidra 12.1.4/JDK 21, Kuna 1.986, and SDK 2.3.1 stdio MCP, I opened a self-built, unexecuted x86-64/arm64 universal Mach-O with Ghidra and Kuna installed and a deliberately unavailable Hopper launcher. The target opened with analysis_provider_binding: null.

  • binary_session({detail:"complete"}) marked procedure_address unavailable with reason: provider_missing and advised installing or configuring a provider. get_call_graph and the native-seed mode gave similar guidance.
  • In that same session, Ghidra was present but rejected this target with architecture_unsupported, and Kuna was present but rejected it with target_format_unsupported; both suggested a thin slice. Hopper separately reported its launcher missing, so configuring a compatible provider is also a possible route. Direct procedure_address returned provider_unavailable with the candidate rejections. The default compact session summary also retained candidate status/reason and mode-level remediation. A separate draft #8400 control exercised full and compact SDK schemas and checked that summary and complete availability facts matched for this unbound session.
  • inspect_native_dispatch_metadata remained available and succeeded through the Apple reader; an unbound deep provider must not become a blanket session failure. The source target SHA-256 705614f3df4c590fee5e0bbe7639e7108886f4d47138d20ff63ec59829019538 stayed unchanged. Successful MCP calls used fetched compact output schemas.

An agent reading only the grouped tool entry may miss a useful target-specific route that the session already reports. This is one host, one universal binary and provider configuration; I did not measure agent decisions or frequency. Draft #8400 was also tested and leaves the direct deep-operation hint generic, while refining the trace-level response.

Expected interaction with targets

Read existing files and launch the selected local static provider if a subsequent operation requires it. The proposed discovery handoff itself does not execute the target, mutate it, select another provider, or thin it automatically.

Boundary details

Preserve each candidate's installed, unavailable, unsuitable, or unknown state; an unavailable operation does not mean every candidate has the same failure. Keep the selected target and provider decision explicit, and avoid copying large diagnostics into every catalog group.

Options and tradeoffs

Compare a concise pointer to candidate-selection facts with a bounded target-specific hint derived from the owning selection result. The direct operation already retains candidate rejections; discovery should not invent another interpretation. Avoid silently changing the selected target or claiming an unsupported slice is absent.

Source-derived: SessionProviderRouter retains only auxiliary route capabilities when no deep binding exists, while CapabilityInventory evaluates that projected set without the separately retained candidate statuses. The direct operation uses the router's unbound selection error, which retains candidate rejections. This explains the different granularity observed here.

Verification available so far

I have a prototype or observed results.

Success criteria and open questions

Exercise unbound but installed/unsupported, genuinely unconfigured, unhealthy, and unknown-readiness candidates; verify compact and complete MCP results, target-free and resource-only sessions, provider switching, and bounded recovery. A controlled agent study would be needed to measure the UX effect. Last known good: NOT_ENOUGH_INFO.

References

#8193 and #7381 discuss explicit universal-slice support; #8322 concerns an incorrectly advertised route with a bound Kuna/ELF provider. Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0.

Failed explicit switch with an already bound target

A separate compact/full stdio MCP control on #8608 head 331f2dd82 began with a Ghidra session bound to a thin arm64 Mach-O, then explicitly attempted to open its universal container with Ghidra. The new open returned capability_unavailable / target_unsupported; the existing session remained bound to the thin file and a subsequent read_bytes returned Evidence for the thin file's SHA-256. The refusal's selection diagnostic explained the unsupported universal target but did not report that the previous target remained active. Explicitly opening the container without a provider then succeeded and list_architectures exposed both slice offsets. All successful replies validated against fetched MCP schemas, and files were unchanged. See #8193 for hashes and exact observations.

This is a distinct recovery situation from the unbound session above: a follow-up analysis can succeed against the old target unless the caller checks its subject. Consider reporting the actual post-failure session selection when the owner has established it, rather than assuming every failure preserves or closes a target; cleanup and rollback outcomes can differ. No autonomous-agent behavior or broader failure-stage coverage was tested.

Lifecycle ownership constraint

Source inspection at 331f2dd82: BinarySession.#open serializes lifecycle transitions. Resolution failure returns before retiring #active, while later provider startup failure can trigger retirement and attempted restoration. A separate status read after the transition is not necessarily an atomic receipt of that failed transition if another queued switch intervenes. Any per-error state observation should be captured by the session owner at the transition boundary, identify its observation time/scope, preserve the primary error and any pending cleanup, and avoid implying the state remains current after later operations. A separate binary_session call remains the way to request current state.

The existing BinarySessionRestoration.test.ts passed four controlled cases on this revision: failed restoration left no active target, with cleanup pending only in the injected cleanup-failure cases; cancellation and the primary error remained distinguishable. These are simulated providers, not real Ghidra cleanup failures. Together with the real early-refusal control above, they rule out a blanket assertion that every failed switch leaves the prior target open.

Receipt ownership experiment

A local prototype attached the settled selection directly to the returned AnalysisError. Source review showed this is not a safe ownership model: BinarySession can return the retained #externalMutationCleanupFailure on multiple open attempts, and error objects also carry live cleanup retry dependencies. Mutating a shared error for a new attempt could change the projection of an earlier attempt. The prototype was discarded; no implementation or runtime test of that approach is claimed.

A candidate is an immutable per-attempt failure envelope returned alongside the original error by the serialized open operation, containing only the selected target identity actually known at settlement (or an explicit no-selection state). The existing error remains the owner of live cleanup and primary failure; the MCP boundary projects the receipt as a past observation, while one-shot CLI delivery must not imply a continuing session after shutdown. Evaluate existing result and delivery types, target/resource-only states, queued switches, cancellation and late cleanup before choosing the contract. This is a design option, not an implemented or verified fix.

Originally created by @morluto on GitHub (Nov 1, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8402 ### Before submitting - [x] I searched the open and closed issues and pull requests for a related discussion. - [x] I understand the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope) must be reviewed before integration; open questions are welcome here. - [x] This discussion does not include credentials, private paths, or sensitive target data. ### Decision needed When a target opens without a bound deep provider, should tool and mode recovery point to the already recorded candidate-selection reasons, rather than only advising provider installation or configuration? ### Context and evidence On current main `6e9ee97a69cb16c7150d8b550e705cba055ee4f0`, macOS arm64, Node 24.22.1, Ghidra 12.1.4/JDK 21, Kuna 1.986, and SDK 2.3.1 stdio MCP, I opened a self-built, unexecuted x86-64/arm64 universal Mach-O with Ghidra and Kuna installed and a deliberately unavailable Hopper launcher. The target opened with `analysis_provider_binding: null`. - `binary_session({detail:"complete"})` marked `procedure_address` unavailable with `reason: provider_missing` and advised installing or configuring a provider. `get_call_graph` and the native-seed mode gave similar guidance. - In that same session, Ghidra was present but rejected this target with `architecture_unsupported`, and Kuna was present but rejected it with `target_format_unsupported`; both suggested a thin slice. Hopper separately reported its launcher missing, so configuring a compatible provider is also a possible route. Direct `procedure_address` returned `provider_unavailable` with the candidate rejections. The default compact session summary also retained candidate status/reason and mode-level remediation. A separate draft #8400 control exercised full and compact SDK schemas and checked that summary and complete availability facts matched for this unbound session. - `inspect_native_dispatch_metadata` remained available and succeeded through the Apple reader; an unbound deep provider must not become a blanket session failure. The source target SHA-256 `705614f3df4c590fee5e0bbe7639e7108886f4d47138d20ff63ec59829019538` stayed unchanged. Successful MCP calls used fetched compact output schemas. An agent reading only the grouped tool entry may miss a useful target-specific route that the session already reports. This is one host, one universal binary and provider configuration; I did not measure agent decisions or frequency. Draft #8400 was also tested and leaves the direct deep-operation hint generic, while refining the trace-level response. ### Expected interaction with targets Read existing files and launch the selected local static provider if a subsequent operation requires it. The proposed discovery handoff itself does not execute the target, mutate it, select another provider, or thin it automatically. ### Boundary details Preserve each candidate's installed, unavailable, unsuitable, or unknown state; an unavailable operation does not mean every candidate has the same failure. Keep the selected target and provider decision explicit, and avoid copying large diagnostics into every catalog group. ### Options and tradeoffs Compare a concise pointer to candidate-selection facts with a bounded target-specific hint derived from the owning selection result. The direct operation already retains candidate rejections; discovery should not invent another interpretation. Avoid silently changing the selected target or claiming an unsupported slice is absent. Source-derived: `SessionProviderRouter` retains only auxiliary route capabilities when no deep binding exists, while `CapabilityInventory` evaluates that projected set without the separately retained candidate statuses. The direct operation uses the router's unbound selection error, which retains candidate rejections. This explains the different granularity observed here. ### Verification available so far I have a prototype or observed results. ### Success criteria and open questions Exercise unbound but installed/unsupported, genuinely unconfigured, unhealthy, and unknown-readiness candidates; verify compact and complete MCP results, target-free and resource-only sessions, provider switching, and bounded recovery. A controlled agent study would be needed to measure the UX effect. Last known good: NOT_ENOUGH_INFO. ### References #8193 and #7381 discuss explicit universal-slice support; #8322 concerns an incorrectly advertised route with a bound Kuna/ELF provider. Looping in @gensokyo-dev @kaoru0822-kitauji @N0zoM1z0. ### Failed explicit switch with an already bound target A separate compact/full stdio MCP control on #8608 head `331f2dd82` began with a Ghidra session bound to a thin arm64 Mach-O, then explicitly attempted to open its universal container with Ghidra. The new open returned `capability_unavailable` / `target_unsupported`; the existing session remained bound to the thin file and a subsequent `read_bytes` returned Evidence for the thin file's SHA-256. The refusal's selection diagnostic explained the unsupported universal target but did not report that the previous target remained active. Explicitly opening the container without a provider then succeeded and `list_architectures` exposed both slice offsets. All successful replies validated against fetched MCP schemas, and files were unchanged. See #8193 for hashes and exact observations. This is a distinct recovery situation from the unbound session above: a follow-up analysis can succeed against the old target unless the caller checks its subject. Consider reporting the actual post-failure session selection when the owner has established it, rather than assuming every failure preserves or closes a target; cleanup and rollback outcomes can differ. No autonomous-agent behavior or broader failure-stage coverage was tested. ### Lifecycle ownership constraint Source inspection at `331f2dd82`: `BinarySession.#open` serializes lifecycle transitions. Resolution failure returns before retiring `#active`, while later provider startup failure can trigger retirement and attempted restoration. A separate status read after the transition is not necessarily an atomic receipt of that failed transition if another queued switch intervenes. Any per-error state observation should be captured by the session owner at the transition boundary, identify its observation time/scope, preserve the primary error and any pending cleanup, and avoid implying the state remains current after later operations. A separate `binary_session` call remains the way to request current state. The existing `BinarySessionRestoration.test.ts` passed four controlled cases on this revision: failed restoration left no active target, with cleanup pending only in the injected cleanup-failure cases; cancellation and the primary error remained distinguishable. These are simulated providers, not real Ghidra cleanup failures. Together with the real early-refusal control above, they rule out a blanket assertion that every failed switch leaves the prior target open. ### Receipt ownership experiment A local prototype attached the settled selection directly to the returned `AnalysisError`. Source review showed this is not a safe ownership model: `BinarySession` can return the retained `#externalMutationCleanupFailure` on multiple open attempts, and error objects also carry live cleanup retry dependencies. Mutating a shared error for a new attempt could change the projection of an earlier attempt. The prototype was discarded; no implementation or runtime test of that approach is claimed. A candidate is an immutable per-attempt failure envelope returned alongside the original error by the serialized open operation, containing only the selected target identity actually known at settlement (or an explicit no-selection state). The existing error remains the owner of live cleanup and primary failure; the MCP boundary projects the receipt as a past observation, while one-shot CLI delivery must not imply a continuing session after shutdown. Evaluate existing result and delivery types, target/resource-only states, queued switches, cancellation and late cleanup before choosing the contract. This is a design option, not an implemented or verified fix.
Author
Owner

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

Additional readiness and resource-session controls

On PR #8400 head ea357ddf04590dbf1b3268aee43a8cffe641e071 with Node 24.22.1 and SDK 2.3.1 stdio MCP on macOS arm64, I deliberately pointed Ghidra, Hopper and Kuna at nonexistent locations and left IDA unconfigured. This is a controlled configuration-failure case, not a claim about a naturally unhealthy installation.

  • Before opening a target, summary binary_session marked procedure_address unavailable with target_required and told the caller to open a target; candidate target support was unknown.
  • After opening a self-built thin arm64 Mach-O, the session was unbound. All four candidates reported unavailable with their own missing-executable or unconfigured reasons; their separate target-support observations were supported. The direct tool entry still said provider_missing and gave generic install/configure guidance. Direct procedure_address returned provider_unavailable, its candidate rejections, and suggested local diagnostics or rea doctor. Apple dispatch metadata remained available and succeeded. This control shows why configuration advice can be appropriate while a pointer to candidate facts would make it more actionable; it does not prove a provider can execute this target after its installation is fixed.
  • In a separate real stdio MCP run with the same intentionally unavailable configuration, open_bundle_resources opened an owned keyless .app and inspect_plist succeeded. Summary and complete binary_session both reported no provider candidates and marked procedure_address as target_unsupported, directing the caller to open an executable target. Switching to the thin Mach-O produced the unbound provider status described above. Both selected files retained their SHA-256 digests; no target was executed.

Successful calls were validated by the SDK against fetched compact-profile schemas. These controls support keeping target-required and resource-only recovery ahead of any candidate handoff, and keeping availability separate from target support. The earlier universal binary control covers installed but unsupported providers. This still does not cover every readiness state, a failed provider switch, other hosts, or agent decision-making.

<!-- gh-comment-id:6551027552 --> @morluto commented on GitHub (Nov 1, 2026): ### Additional readiness and resource-session controls On PR #8400 head `ea357ddf04590dbf1b3268aee43a8cffe641e071` with Node 24.22.1 and SDK 2.3.1 stdio MCP on macOS arm64, I deliberately pointed Ghidra, Hopper and Kuna at nonexistent locations and left IDA unconfigured. This is a controlled configuration-failure case, not a claim about a naturally unhealthy installation. - Before opening a target, summary `binary_session` marked `procedure_address` unavailable with `target_required` and told the caller to open a target; candidate target support was unknown. - After opening a self-built thin arm64 Mach-O, the session was unbound. All four candidates reported unavailable with their own missing-executable or unconfigured reasons; their separate target-support observations were supported. The direct tool entry still said `provider_missing` and gave generic install/configure guidance. Direct `procedure_address` returned `provider_unavailable`, its candidate rejections, and suggested local diagnostics or `rea doctor`. Apple dispatch metadata remained available and succeeded. This control shows why configuration advice can be appropriate while a pointer to candidate facts would make it more actionable; it does not prove a provider can execute this target after its installation is fixed. - In a separate real stdio MCP run with the same intentionally unavailable configuration, `open_bundle_resources` opened an owned keyless `.app` and `inspect_plist` succeeded. Summary and complete `binary_session` both reported no provider candidates and marked `procedure_address` as `target_unsupported`, directing the caller to open an executable target. Switching to the thin Mach-O produced the unbound provider status described above. Both selected files retained their SHA-256 digests; no target was executed. Successful calls were validated by the SDK against fetched compact-profile schemas. These controls support keeping target-required and resource-only recovery ahead of any candidate handoff, and keeping availability separate from target support. The earlier universal binary control covers installed but unsupported providers. This still does not cover every readiness state, a failed provider switch, other hosts, or agent decision-making.
Author
Owner

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

Tested thin-slice recovery

Using the same owned universal Mach-O and PR #8400 ea357ddf0, I created a separate arm64 slice with xcrun lipo -thin arm64 and tested it in the same real stdio MCP session with Ghidra 12.1.4 and Kuna 1.986 installed:

  1. The universal target opened unbound, with the candidate target rejections already described above.
  2. Opening the extracted slice with automatic provider selection failed with an explicit ambiguity: both Ghidra and Kuna were eligible, and REA asked for a provider selection. The original universal target remained open and unbound after that failed attempt.
  3. Opening the slice with provider_id: ghidra succeeded; procedure_address(_evaluate) returned 0x100000430. Switching the same slice to provider_id: kuna also succeeded and returned the same address. Both session summaries advertised the operation available.

The original universal file and the caller-created slice had unchanged SHA-256 hashes throughout the MCP calls; neither target was executed. Successful responses were checked against the SDK's fetched compact output schemas. This illustrates an actual recovery path for this fixture: selecting a slice may lead to an additional explicit provider choice, so guidance should not promise that extraction alone binds a provider. The slice is a separate caller-created file, and this does not establish automatic slicing or a general cross-provider equivalence claim.

<!-- gh-comment-id:6551037044 --> @morluto commented on GitHub (Nov 1, 2026): ### Tested thin-slice recovery Using the same owned universal Mach-O and PR #8400 `ea357ddf0`, I created a separate arm64 slice with `xcrun lipo -thin arm64` and tested it in the same real stdio MCP session with Ghidra 12.1.4 and Kuna 1.986 installed: 1. The universal target opened unbound, with the candidate target rejections already described above. 2. Opening the extracted slice with automatic provider selection failed with an explicit ambiguity: both Ghidra and Kuna were eligible, and REA asked for a provider selection. The original universal target remained open and unbound after that failed attempt. 3. Opening the slice with `provider_id: ghidra` succeeded; `procedure_address(_evaluate)` returned `0x100000430`. Switching the same slice to `provider_id: kuna` also succeeded and returned the same address. Both session summaries advertised the operation available. The original universal file and the caller-created slice had unchanged SHA-256 hashes throughout the MCP calls; neither target was executed. Successful responses were checked against the SDK's fetched compact output schemas. This illustrates an actual recovery path for this fixture: selecting a slice may lead to an additional explicit provider choice, so guidance should not promise that extraction alone binds a provider. The slice is a separate caller-created file, and this does not establish automatic slicing or a general cross-provider equivalence claim.
Author
Owner

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

A related freshness control is now tracked in #8403. After repairing a deliberately missing Kuna path, a fresh rea doctor --provider kuna --json reported Kuna available, but the still-open unbound session continued to expose the selection-time missing-executable candidate and direct operation failure. Explicitly reopening the same target with provider_id: kuna succeeded. The proposed candidate handoff here should therefore identify selection-time facts and make an explicit retry discoverable; a pointer alone does not make those facts fresh. The issue includes the runtime and limits.

<!-- gh-comment-id:6551063203 --> @morluto commented on GitHub (Nov 1, 2026): A related freshness control is now tracked in #8403. After repairing a deliberately missing Kuna path, a fresh `rea doctor --provider kuna --json` reported Kuna available, but the still-open unbound session continued to expose the selection-time missing-executable candidate and direct operation failure. Explicitly reopening the same target with `provider_id: kuna` succeeded. The proposed candidate handoff here should therefore identify selection-time facts and make an explicit retry discoverable; a pointer alone does not make those facts fresh. The issue includes the runtime and limits.
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#6583
No description provided.