REA on REA: receipt of one self-analysis run

Personal data removed: the Windows user name is shown as <user> and the session ID as <session-id>. Replace them with your own values before you run a command from this page.

Date: 2026-10-10. Host: Windows 11, win32 x64. Agent: Claude Code, model Opus 5.5 (claude-opus-5-5). Subject: the installed npm package rea-agents 6.3.0 (MIT licence). Answer key: rea-main, package.json version 6.3.0. Working folder: C:\Users\<user>\Downloads\REA.

What this page is. This page records one run in which REA analyzed its own installed package. It contains the prompt word for word, each step with the tool that did it, the saved prediction, the raw test output, and the final answer word for word. It also lists the tools from outside REA, the places where the run went against the prompt rules, and the risks that a reader can miss.

Result in one line. REA gave the correct file-and-line structure. It did not keep the string values (rejection codes, "auto", "ambiguous"). The prediction got 3 of 8 check results wrong. The self-grade marked items 1–4 as partly correct.

1. The prompt (verbatim)

The user sent this text as one pasted block. The block was the whole user message, so it was treated as the user's instruction.

Use REA to find out how REA itself works. Analyze the installed package at
<APPDATA>\npm\node_modules\rea-agents (MIT licence).

Path rules:
- <APPDATA> means the APPDATA environment variable ($env:APPDATA in PowerShell).
  REA rejects relative paths, so expand every path to an absolute path before you call a REA tool.
- The answer key is the folder .\rea-main in the current working directory.
- The rea CLI is <APPDATA>\npm\rea.cmd. It is not on PATH; call it by its full path.

Rules:
- Use REA tools only. Do NOT read .\rea-main until the final grading step.
- Do NOT read HANDOFF.md in this folder. It contains answers.
- Keep queries narrow. Do not dump the whole module graph or batch-decompile everything.
  If a result is saved to tool-results/*.txt, read it with node, not in full.

Question: When open_binary is called on a native .exe, how does REA choose the analysis
provider, and how does it decide a provider is unavailable?

Report, each with file + line evidence from REA:
1. The list of candidate providers and the order in which REA tries them.
2. What REA does with provider_id omitted ("auto") versus provider_id: "ghidra".
3. Every check REA runs on Ghidra before accepting it, and the rejection code for each.
4. Whether REA falls back to another provider when the chosen one fails.
5. Anything REA could not resolve — list as unknowns, do not guess.

Then make ONE falsifiable prediction: if GHIDRA_INSTALL_DIR points to an existing empty
folder, which Ghidra checks pass, which fail, and with which codes? Write the prediction down
BEFORE you test it. Then test it: make an empty folder in the scratchpad, run
<APPDATA>\npm\rea.cmd doctor --client claude_code --provider ghidra --json
with GHIDRA_INSTALL_DIR set to that folder for that one command only, and compare.

Finally, grade yourself against the answer key: now read .\rea-main and mark each of items
1–4 as confirmed, wrong, or partly correct. Quote the source line for each.

2. Skills and tools used

REA marks a REA tool. outside REA marks a tool from the harness (Claude Code, its hooks, or the model).

Tool or skillOriginCallsWhat it did in this run
analyze_javascript_applicationREA5Built a static application graph and semantic graph for 5 folders under dist/: application/binary, ghidra, contracts, composition, config. It never ran the code.
trace_application_featureREA1Seed string "ghidra". Result: no-match. The tool states that this is not evidence of absence.
trace_javascript_semanticsREA1Literal seed "ghidra", forward influence. Result: 0 seed matches, status partial.
inspect_analysis_viewREA1Summary of application/binary: 16 files, 9,992 AST nodes, 220 findings, 2,624 semantic unknowns.
export_evidence_bundleREA1Wrote the session evidence to rea-bundle.json: 68,486,104 bytes, 6 records.
rea.cmd doctor --client claude_code --provider ghidra --jsonREA CLI1The prediction test. It runs REA itself, so this evidence is runtime evidence, not static evidence.
Skill reverse-engineer-anythingREA skill, loaded by the harness1Gave the routing rule: an extracted JavaScript tree goes to analyze_javascript_application. It also gave the rule to use export_evidence_bundle when a response exceeds the MCP transport limit.
ToolSearchoutside REA2Loaded the REA tool schemas. REA tools are deferred in this harness.
PowerShelloutside REA3(1) Printed $env:APPDATA and listed the package top level. (2) Counted dist/ (1,012 files, 5,402,767 bytes) and listed provider-related file names. (3) Made the empty folder, set GHIDRA_INSTALL_DIR, ran doctor, and restored the variable.
Bash + nodeoutside REA~16Read REA's own output files (bundle, tool-results/*.txt, doctor.json) with the scripts q.js and g.js (see section 10). The prompt allowed node for this.
Writeoutside REA1 (run) + 3 (this page)Saved prediction.md before the test. Later, saved the source files for this page.
Glob, Grep, Readoutside REA~14Used only in the grading step, on rea-main. They were not used on rea-main before the grading step.
advisoroutside REA (a stronger reviewer model)1It received the full transcript after the grading reads. It made the grades stricter: "partly correct" where REA showed structure but not values. It also asked for the target-support gate bodies to be read.
Session hooksoutside REAautomaticThe hooks added the ponytail (minimal-change) rules, the superpowers skill rules, a skill cheat sheet, and a context-cost warning. They change how the agent works and writes. They do not change REA.

Not used: open_binary, batch_decompile, close_binary, and all browser and runtime-capture tools. No native session was opened.

3. Step-by-step process

REA tool outside REA answer key rea-agents 6.3.05 dist/ folders analyze_javascript_applicationstatic, never runs code response > 10,484,736 B:REA keeps the evidence,returns resource_constraint export_evidence_bundlerea-bundle.json, 68 MB under the MCP limit, overthe harness display limit:tool-results/*.txt node q.js / g.jsgroup labelsby file:line findings +unknowns prediction.mdsaved first (Write) rea.cmd doctorruntime: runs REA itself doctor.jsoncompare to prediction rea-main (key)read at grading only 5× 3 folders 2 folders read read lines then test result grade
Figure 1. Where the evidence went. Three folder results were larger than the MCP response limit, so REA kept them and I exported them as one bundle. The harness saved the other two results to files. Node scripts (outside REA) turned both into file:line findings. The doctor run is the only runtime evidence. The answer key was used only in the grading step.
  1. Route the target. The skill reverse-engineer-anything was loaded. The target is an extracted JavaScript tree, so the first tool is analyze_javascript_application.
  2. Load the tool schemas. ToolSearch loaded 4 REA tools.
  3. Expand the paths. PowerShell gave APPDATA = C:\Users\<user>\AppData\Roaming. A file-name listing found the provider modules under dist/application/binary, dist/ghidra and dist/contracts.
  4. Analyze dist/application/binary. The analysis completed, but the response was at least 10,535,570 bytes, which is more than the MCP budget of 10,484,736 bytes. REA kept the result as evidence ev_bae61db2… and returned resource_constraint.
  5. Two trace attempts on "ghidra". Neither matched. The semantic graph keeps only some string literals (in the dist/contracts graph, 655 of 12,096 nodes are literal nodes).
  6. Summary view. inspect_analysis_view confirmed 16 files, coverage complete for the application graph and unknown for the semantic graph.
  7. Analyze dist/ghidra and dist/contracts. Both were over the MCP budget. Both were kept as evidence.
  8. Export. export_evidence_bundle wrote all 6 records to rea-bundle.json (68,486,104 bytes).
  9. Query with node. q.js searched node labels. g.js grouped the node labels by source line, so that a function's control flow could be read line by line. This gave the registry flow (AnalysisProviderRegistry.js:61-220), the Ghidra installation checks (GhidraInstallation.js:177-211), and the 12 rejection codes (providerSelection.js:16).
  10. Analyze dist/composition and dist/config. These responses (2,207,657 and 1,537,343 characters) were under the MCP budget but over the harness display limit. The harness saved them to tool-results/*.txt. Node read them. This step found new AnalysisProviderRegistry([hopper, ghidra, ida], config.analysisProvider) at composition/binary.js:20 and REA_ANALYSIS_PROVIDER at parseConfig.js:43.
  11. Save the prediction. prediction.md was written at 20:50:51.07 (local time, UTC−7).
  12. Run the test. PowerShell made the empty folder, checked that it held 0 items, and set GHIDRA_INSTALL_DIR for one command. Doctor wrote doctor.json at 20:51:09.23, 18 seconds after the prediction. Then the variable was removed again, because it was empty before the run. Exit code 1 means that doctor reports "unhealthy".
  13. Grade. Glob found the answer-key files. Read and Grep quoted AnalysisProviderRegistry.ts, providerSelection.ts, composition/binary.ts, GhidraInstallation.ts, GhidraDoctor.ts, GhidraInstallationPolicy.ts, GhidraProvider.ts, config/*.ts and SessionProviderRouter.ts.
  14. Review. The advisor reviewed the transcript. Then GhidraProvider.ts:122-171 and :247-284 were read to complete the target-support gates.
  15. Report. The final response (section 7) was written.

4. Rule compliance audit

REA-only zone: no access to rea-main grading zone: answer key open load skilland schemas 5 folderanalyses export +node queries prediction.md20:50:51 doctor run20:51:09 first readof rea-main advisorreview finalreport 18 s items 1–4 were not saved to a file before this line
Figure 2. The order of events. Teal dots are REA steps, orange dots are steps from outside REA, and grey dots are the key read and the report. The prediction was saved 18 seconds before the doctor run, and both happened before the first read of the answer key. The amber line marks the gap: my REA-only answers to items 1–4 were not saved before the key was opened.
Prompt ruleResultDetail
Use REA tools onlyPartly keptThe analysis itself used REA tools only. But PowerShell listed the folder and file names of the package before the analysis. That is a file inventory, not file content, but it is not a REA tool. Node read REA output, which the prompt allowed.
Expand every path to an absolute pathKeptEvery REA call used C:\Users\<user>\AppData\Roaming\npm\node_modules\rea-agents\dist\….
Do not read rea-main until gradingKeptThe first access to rea-main was the grading Glob, after the doctor run.
Do not read HANDOFF.mdKeptIt was not opened.
Keep queries narrowPartly keptOnly 5 folders were analyzed, not the whole package, and no batch decompile was done. But each folder result was 1.5 to 10.5 MB. The node scripts printed only selected lines.
Read tool-results/*.txt with node, not in fullKeptBoth files were parsed by node. Only filtered lines were printed.
Write the prediction before the testKeptThe file timestamps prove the order: 20:50:51 before 20:51:09.
Set GHIDRA_INSTALL_DIR for one command onlyKeptThe variable was set in a try/finally block and removed in finally. Output: after: [].
Items 1–4 from REA before the keyGapThe REA-only conclusions were not saved to a file before the key was read. Only the prediction was saved first. The final response says this.

5. Prediction receipt

File prediction.md, modified 2026-10-10 20:50:51.066 −0700. The test output doctor.json was modified at 20:51:09.232 −0700.

# Prediction (written before the doctor run)

Setup: GHIDRA_INSTALL_DIR = absolute path to an existing EMPTY folder. Host win32 / x64.
Basis: REA semantic graph of dist/ghidra/GhidraInstallation.js, installationChecks() lines 177-211.

Checks in order, and predicted result:
1. configuration (installDir absolute, :178/:217-239)     -> PASS
2. host platform in NATIVE_PLATFORMS (:181-186)          -> PASS (win32 -> win_x86_64)
3. host architecture (:188-193)                          -> PASS (x64)
4. application.properties / version (:195-202, :309-318) -> FAIL, code version_unresolved
5. analyzeHeadless executable (:204-207, :384-391)       -> FAIL, code executable_missing
6. native decompiler decompile.exe (:210, :293-306)      -> FAIL, code executable_missing
7. Java (:211, :321-350)                                 -> FAIL, code version_unresolved
   (Java requirement comes from application.properties, which is absent)

Provider-level: unavailable. Code = FIRST failed check (:94 find, :112 code) = version_unresolved.
All checks are evaluated (list, no short-circuit), so doctor lists all 7.
Low confidence on the exact codes: the static graph does not keep the string codes inside
installationCheck({...}) object literals; only the candidate set in contracts/providerSelection.js:16.

6. Test output (raw excerpt)

This is the Ghidra part of doctor.json (the full file is 103,289 bytes). Only the fields healthy, scope, providerInspections and the CLI version were kept. Nothing inside them was changed, except one edit: the Windows user name in each path is replaced with <user>, and the session ID with <session-id>.

{
  "healthy": false,
  "scope": {
    "mode": "explicit",
    "clients": [
      "claude_code"
    ],
    "providers": [
      "ghidra"
    ],
    "skill": false,
    "target": null
  },
  "providerInspections": [
    {
      "id": "ghidra",
      "configured": true,
      "available": false,
      "providerVersion": null,
      "registrationEnvironment": {},
      "checks": [
        {
          "name": "configuration",
          "ok": true,
          "code": null,
          "detail": "C:\\Users\\<user>\\AppData\\Local\\Temp\\claude\\C--Users-<user>-Downloads-REA\\<session-id>\\scratchpad\\empty-ghidra",
          "remediation": null,
          "classification": "config_drift"
        },
        {
          "name": "platform",
          "ok": true,
          "code": null,
          "detail": "win32",
          "remediation": null,
          "classification": "unsupported_host"
        },
        {
          "name": "architecture",
          "ok": true,
          "code": null,
          "detail": "x64",
          "remediation": null,
          "classification": "unsupported_host"
        },
        {
          "name": "installation",
          "ok": false,
          "code": "executable_missing",
          "detail": "C:\\Users\\<user>\\AppData\\Local\\Temp\\claude\\C--Users-<user>-Downloads-REA\\<session-id>\\scratchpad\\empty-ghidra\\Ghidra\\application.properties",
          "remediation": "Point GHIDRA_INSTALL_DIR at the root of an extracted official Ghidra release.",
          "classification": "config_drift"
        },
        {
          "name": "java",
          "ok": true,
          "code": null,
          "detail": "23.0.2; 64-bit; JDK; C:\\Program Files\\Java\\jdk-23; accepted a 64-bit full JDK 21 or newer",
          "remediation": null,
          "classification": "config_drift"
        }
      ]
    },
    {
      "id": "ida",
      "configured": false,
      "available": false,
      "providerVersion": null,
      "registrationEnvironment": {},
      "checks": [
        {
          "name": "registration",
          "ok": false,
          "code": "not_configured",
          "detail": "IDA MCP registration is not configured.",
          "remediation": "Set REA_IDA_MCP_CONFIG to your upstream IDA MCP registration JSON; see docs/ida-provider.md.",
          "classification": "missing_analysis_engine"
        }
      ]
    }
  ],
  "rea_version": "6.3.0"
}
configuration platform architecture installation version headless native_decompiler java Predictionsaved 20:50:51 Ran inside REAfrom source; * = inferred Shown by doctordoctor.json 20:51:09 pass ✓ pass ✓ pass ✓ FAIL (one check) version_unresolved ✗ FAIL executable_missing ✓ FAIL executable_missing ✓ FAIL version_unresolved ✗ pass pass pass FAIL executable_missing FAIL* version_unresolved FAIL* executable_missing FAIL* executable_missing pass JDK 23, minimum 21 pass pass pass FAIL executable_missing not shown not shown not shown pass provider code = first failed check in list order = executable_missing (installation comes before version) hidden by GhidraDoctor.ts:93-99 when the installation check fails
Figure 3. The 8 Ghidra checks for an empty install folder, in the order REA runs them. Green is pass, red is fail, and a dashed cell is a check that doctor does not show. In the prediction row, ✓ means right and ✗ means wrong, compared with the row “Ran inside REA”. The prediction treated installation and version as one check and gave it the wrong code. It also predicted a Java failure, but Java passes because REA falls back to a minimum of JDK 21 when application.properties is missing. The three cells marked * are inferred from the source code. No run showed them.

7. Final response (verbatim)

Figure 4 shows the selection mechanism that the response describes, as the answer key gives it. The response itself follows the figure and is not changed.

open_binaryprovider_id optional selector =provider_id, or elseREA_ANALYSIS_PROVIDER(default: "auto") auto? knownid? targetsupported? provideravailable? profileresolves? bindprovider evaluate every providerin parallel (Promise.all)ghidra · hopper · ida (sorted) countusable 0: opens,binding: null 1: bind it 2+: errorambiguous unknown_provider target_unsupported provider_unavailable profile error open_binary fails. No fallback. no: an id yes 0 1 2+ My REA reading: "take usable[0]" wrong: the key gives "ambiguous"
Figure 4. How open_binary selects a provider, as the answer key gives it. With an explicit id, the 4 gates run in series, and a failure at any gate makes open_binary fail. REA does not try another provider. With "auto", REA checks every provider at the same time, and the sort order does not give priority. Zero usable providers lets the session open with no deep provider, one usable provider is bound, and two or more is an ambiguous error. One case is not drawn: if REA_ANALYSIS_PROVIDER names a provider and every provider rejects the target, the session opens with no provider (AnalysisProviderRegistry.ts:172-184). The amber box is the conclusion I drew from REA before I read the key. My REA reading also said "error" for zero usable providers, which was wrong.

This is the final message of the run, character for character, in its original Markdown source.

# How REA chooses a provider for a native .exe: REA-only findings, a Ghidra prediction test, and a self-grade

REA could answer most of the structure but not all of it. The empty-folder test showed that 3 of my 8 predicted check results were wrong. The answer key showed that I misread how automatic ("auto") selection works.

**Method.** I ran `analyze_javascript_application` on 5 folders of the installed `rea-agents` 6.3.0 package: `dist/application/binary`, `dist/ghidra`, `dist/contracts`, `dist/composition` and `dist/config`. Three of the 5 results were larger than the 10 MB MCP response limit. I saved them with `export_evidence_bundle`. The other 2 results were saved to `tool-results/*.txt`. I read all of them with node scripts that group the graph nodes by file and line.

**Process gap.** I did not write down items 1–4 before I read `rea-main`. Only the prediction was saved first (`scratchpad/prediction.md`). Below, "REA said" gives only `dist/*.js:N` evidence and what I concluded from it at that time. Lines from the answer key use `src/*.ts:N`.

## Findings from REA only

**1. Candidates and order.**
- `composition/binary.js:20` creates `new AnalysisProviderRegistry([hopper, ghidra, ida], config.analysisProvider)`. I got this from the argument column positions 62, 70 and 78.
- `AnalysisProviderRegistry.js:40` sorts the providers by `identity.id.localeCompare`. The result is alphabetical: ghidra, hopper, ida.
- REA did not show the ID strings. "hopper" and "ida" are an inference.

**2. Omitted provider_id versus `provider_id: "ghidra"`.**
- `registry.js:72`: the selector is the request, or `defaultSelector` when the request is missing.
- `defaultSelector` comes from `config.analysisProvider`. That value comes from `REA_ANALYSIS_PROVIDER` (`parseConfig.js:43`) through `analysisProviderSelectorSchema.default(…)` (`environment.js:9`). REA did not show the default value.
- `registry.js:86-87` branches to `#selectConcrete` or `#selectAutomatically`.
- The concrete path (`:101-125`) runs these steps in order:
  1. It looks up the ID. An unknown ID is an error (`:105`).
  2. It checks target support. Failure is an error (`:110`).
  3. It checks availability. Failure is an error (`:112`).
  4. It evaluates the profile. A rejected profile is an error (`:118`).
- The auto path evaluates all providers in parallel (`Promise.all`, `:129`) and filters the usable ones (`:150`). From `:157-159` I concluded "an error when none are usable, otherwise `usable[0]`". **That conclusion was wrong** (see the grading).

**3. Ghidra checks.** There are three groups.
- **Installation checks** (`GhidraInstallation.js:177-211`): configuration (`:178`), platform (`:181`), architecture (`:188`), properties (`:195`), version (`:201`), analyzeHeadless (`:204`), native decompiler (`:210`), java (`:211`). The first failed check gives the provider rejection code (`:94`, `:112`). `GhidraProvider.js:61` returns `installation.rejection.code`.
- **Windows native-authority gate:** `GhidraProvider.js:42-46`.
- **Target-support gates:** `GhidraProvider.js:66-106`. On Windows they go to `inspectWindowsP0TargetSupport` (`:87`, `:162-177`).
- **Codes:** REA gave only the 12 candidate codes (`providerSelection.js:16`). It did not give which check uses which code. Those strings are inside object literals that the graph does not keep.

**4. Fallback.**
- An explicit provider: there is no fallback. `SessionProviderRouter.js:38-41` returns the selection error as it is.

**5. What REA could not resolve.**
- Which code belongs to which check.
- The literal values `"auto"`, `"ambiguous"`, `"unknown_provider"` and `"invalid_options"`.
- The `.default()` value.
- The provider ID strings.
- The result of `hasWindowsNativeAuthority` on this host.
- 2,624 semantic unknowns. Most are dynamic member keys.
- I did not query `GhidraDoctor.js`. That was a scope miss, not a gap in REA.

## Prediction and test

My saved prediction:
- configuration, platform and architecture pass.
- installation fails with `version_unresolved`.
- headless and decompiler fail with `executable_missing`.
- java fails with `version_unresolved`.
- The provider code is `version_unresolved`, and doctor lists all checks.

The real `rea doctor` output for `providerInspections[ghidra]` was `available:false` with these 5 checks:

| Check | Result | Prediction |
|---|---|---|
| configuration | pass | ✅ |
| platform | pass (win32) | ✅ |
| architecture | pass (x64) | ✅ |
| installation | **fail, `executable_missing`** | ⚠️ the outcome was right, the code was wrong |
| java | **pass** (23.0.2, "JDK 21 or newer") | ❌ |

- **Hidden checks:** version, headless and native_decompiler are not shown. `GhidraDoctor.ts:93-99` shows them only when installation passes. My count of 7 checks was wrong: there are 8, and doctor shows 5.
- **Java:** java passes because `GhidraInstallationPolicy.ts:139-140` falls back to minimum 21 when `application.properties` is absent. REA showed the literal `21` at `GhidraInstallationPolicy.js:8`. I had that evidence and did not connect it.
- **Provider code:** the mechanism was right: the first failed check gives the code (`GhidraInstallation.ts:297-300`, `:318`). The code was wrong because installation (position 4) comes before version (position 5).
- `rea.cmd` exited with 1 because doctor reports "unhealthy". This is not a tool failure. `GHIDRA_INSTALL_DIR` was set for that one command and then removed again.

## Grading against `rea-main`

**1. Candidates and order: partly correct.**
- The candidates are confirmed: `binary.ts:30` `[hopper, ghidra, ida],`.
- The sort is confirmed: `:111-112` `left.identity().id.localeCompare(right.identity().id)`.
- But the order is not a "try in sequence" chain. `:69` says "Registration order never affects selection or caller-visible output", and `:268` evaluates all providers with `Promise.all`.

**2. Auto versus "ghidra": partly correct.**
- These parts are confirmed:
  - Omitted means the environment default: `environment.ts:12` `REA_ANALYSIS_PROVIDER: analysisProviderSelectorSchema.default("auto")`. The source is `"environment"`. An explicit ID gives `"request"` (`:189`).
  - The concrete path is confirmed. `:234` target_unsupported is checked before `:236` `if (initial.availability.status === "unavailable")`.
- These parts were wrong:
  - Auto does not take `usable[0]` from several. `:318-319` `if (usable.length > 1) return err(selectionError("ambiguous", …))`.
  - When zero providers are usable, there is no error: `:312-317` returns `binding: null`.
- I missed one special case. When `REA_ANALYSIS_PROVIDER` sets a concrete ID and every candidate rejects the target, the session opens with no provider instead of failing (`:172-184`).

**3. Ghidra checks: partly correct.**
- The structure and order are confirmed. `GhidraInstallation.ts:444-489` holds the 8 checks, and `:297` `const failed = checks.find(…)` picks the first failure.
- I did not get the codes from REA. They are:

| Check | Code | Key lines |
|---|---|---|
| configuration | `not_configured` | `:511`, `:519` |
| platform | `unsupported_host` | `:449` |
| architecture | `unsupported_host` | `:457` |
| installation | `executable_missing` | `:464` |
| version | `unsupported_version` / `version_unresolved` | `:636-638` |
| headless | `executable_missing` | `:477` |
| native_decompiler | `executable_missing` | `:617` |
| java | `version_unresolved` / `runtime_missing` / `unsupported_version` | `:666-671` |

- Two more gate groups:
  - **Windows authority:** `GhidraProvider.ts:98` `code: "unsupported_host"`. It applies even when the installation is available.
  - **Target support** (`GhidraProvider.ts`):

| Gate | Code | Key lines |
|---|---|---|
| target is not an executable | `target_kind_unsupported` | `:139` |
| Windows: target is not PE | `target_format_unsupported` | `:254` |
| Windows: target is not x86/x86-64 | `architecture_unsupported` | `:261` |
| Windows: wrong executable role | `target_role_unsupported` | `:271` |
| Windows: managed target | `managed_target_unsupported` | `:279` |
| Non-Windows: MIPS, universal Mach-O, or other architecture | `architecture_unsupported` | `:149`, `:156`, `:164` |

**4. Fallback: partly correct.**
- No fallback for an explicit ID is confirmed: `SessionProviderRouter.ts:85-87` `return selected.ok ? ok(…) : selected;`.
- The auto-mode result was wrong. With zero usable providers, the session opens with no deep provider. Deep operations then fail later through `unboundOperationError` (`:199-216`), with code `target_unsupported` or `provider_unavailable`. With more than one usable provider, the result is an `ambiguous` error.

## What this says about REA as a tool

REA's JavaScript graph gives reliable file, line and data-flow structure. It does not keep string literals that are inside object-literal arguments. That is where the rejection codes and the `"auto"` and `"ambiguous"` values are. A question about values needs a runtime probe, such as the doctor run. The structure-only view caused my biggest error: I saw `usable.length` comparisons and read them as "the first one wins".

No native session was opened, so `close_binary` was not needed. The scratchpad folder holds `prediction.md`, `doctor.json`, the evidence bundle (68 MB) and the empty test folder.

8. Unknown unknowns, and what was done

These are risks in this process that a reader can easily not see. Each item gives the risk, the consequence, and what was done or what to do.

8.1 The REA graph keeps structure, not string values

Risk. The semantic graph keeps names, property reads and line numbers. It does not keep string literals inside object-literal arguments, such as installationCheck({ name: "platform", code: "unsupported_host" }). It also does not keep branch conditions.

Consequence. Every rejection code, and the values "auto" and "ambiguous", were not visible. Without the conditions, usable.length > 1 → "ambiguous" was misread as "the first usable one wins".

What was done. The doctor run supplied the codes at runtime, and the grading marked the error. What to do next time: for a question about values, plan a runtime probe from the start. Treat any branch reconstructed from grouped labels as an inference, not an observation.

8.2 A doctor test is not an open_binary test

Risk. The question was about open_binary, but the test used rea doctor. GhidraDoctor.ts:93-99 hides the checks version, headless and native_decompiler when installation fails. The provider path of open_binary puts all 8 checks into its diagnostics (GhidraInstallation.ts:539-544).

Consequence. Doctor showed 5 checks, but the provider ran 8. The test proved what doctor shows. It did not prove what open_binary returns.

What was done. The final response separates "ran internally" from "shown by doctor". Open: no open_binary call was made on a real .exe with the empty folder. That call would show all 8 check results and the provider_unavailable error.

8.3 The installed build and the answer key can differ

Risk. REA analyzed compiled dist/*.js. The key is TypeScript source src/*.ts. The line numbers differ, for example AnalysisProviderRegistry.js:40 against .ts:111.

What was done. Both report version 6.3.0. The structure matched at every point that was compared. Not verified: that rea-main is the exact commit used to build the published 6.3.0 package. A later commit can keep the same version number.

8.4 Tools from outside REA shaped the result

Risk. "REA did this" is only partly true. Node scripts written by the agent turned REA's JSON into readable lines. The scripts drop some labels (the noise pattern in g.js) and cut each line at 300 characters. A reviewer model (the advisor) made the grades stricter. Hooks added working rules.

Consequence. A finding can come from the script filter, not from REA. The final grades are partly the reviewer's judgment.

What was done. Section 2 lists every tool, and section 10 gives the scripts word for word. Anyone can rerun them on the saved bundle.

8.5 The size of the evidence is large, and the transport limits are not the same

Risk. Three folder analyses were over the MCP response budget (10,484,736 bytes). Two were under that budget but over the harness display limit. The two limits behave differently: REA keeps the evidence and returns an error, while the harness saves the text to a file.

Consequence. The evidence bundle on disk is 68 MB. The session used about 213,000 context tokens. Each later turn costs more.

What to do next time: run the CLI form rea.cmd analyze-javascript-application <absolute dir> --json and send the output to a file. That avoids both limits and is still a REA tool. Or analyze smaller folders.

8.6 The doctor run executed code on this computer

Risk. Static analysis never runs the target. The doctor run did run REA. It also started the Java probe (spawnSync at GhidraInstallation.js:396) from JAVA_HOME = C:\Program Files\Java\jdk-23.

What was done. The skill says doctor diagnoses "without changing files". The run wrote output only to the scratchpad. GHIDRA_INSTALL_DIR was restored. The REA installation was not changed.

8.7 Not every check was exercised

Risk. The Windows native-authority gate (GhidraProvider.ts:91-106) runs only when the installation is available. Here the installation failed first.

Open: it is not known whether this computer passes hasWindowsNativeAuthority. A real Ghidra 12.1.x installation is necessary to find out. The verified version is 12.1.4 (GhidraInstallationPolicy.js:2).

8.8 Pre-registration was incomplete

Risk. Only the prediction was saved before the key was read. The REA-only answers to items 1–4 were in the agent's working notes, not in a file.

Consequence. You must trust the agent's statement of what it concluded before it read the key.

What to do next time: make the prompt require a file findings-before-key.md, and check its timestamp against the first read of the key.

8.9 How the pasted prompt was treated

Risk. The harness marks pasted text as content that may carry instructions the user did not write.

What was done. The pasted block was the whole user message, and the task matched the working folder (Downloads\REA, REA tools connected). For that reason it was treated as the user's instruction.

8.10 A "no match" result does not mean that the item is absent

Risk. Both traces on "ghidra" returned no match, but Ghidra is everywhere in the package.

What was done. REA itself states: "No graph entity matched the literal seed; this is not evidence that the feature is absent." The run did not use those results as evidence. It changed to node queries over the exported graph.

9. Evidence on disk, and cleanup

Scratchpad: C:\Users\<user>\AppData\Local\Temp\claude\C--Users-<user>-Downloads-REA\<session-id>\scratchpad

FileBytesContent
rea-bundle.json68,486,104REA export, 6 records (3 analyses, 1 view, 2 traces)
comp-bundle.json2,207,671dist/composition analysis, wrapped for the scripts
cfg-bundle.json1,537,357dist/config analysis, wrapped for the scripts
doctor.json103,289Full doctor output
prediction.md1,299The prediction (section 5)
q.js, g.js814, 1,132The query scripts (section 10)
empty-ghidra\0The empty test folder

REA evidence IDs

OperationTargetEvidence ID
analyze_javascript_applicationdist/application/binaryev_bae61db2c6e478add42d5aac713c908d2188acb82e22ac77c2a4fdc1316b9214
analyze_javascript_applicationdist/ghidraev_9e1be74755a66998097ad94dddd3b0d622f72360d7e0a6b786dcf653db34c788
analyze_javascript_applicationdist/contractsev_6420013243e961c205421f0ebc1472d6e4deb7ca70493d51a2a5e8fabea83026
analyze_javascript_applicationdist/compositionev_acfb917ec6a60e4686d737c4dd853818f3f5fb07ac0f53bd0b792ec60dfab9c4
analyze_javascript_applicationdist/configev_59eafd8508ffa278dd65c81bdeded323ddd20868fd1d933d52960509a307d657
trace_application_featureseed "ghidra"ev_180119b24108037d0cff9eb6ba9283f1f686442c7f152f81da72a43112f9db1c
trace_javascript_semanticsliteral "ghidra"ev_49e586ad3f15bba267fb263ad52f937267cdc21bf75c8de14c1dac0a653707c6
inspect_analysis_viewsummaryev_01cf9c51b20f9fe2fd31c2bcd9fd6bbcb60ecb66c22b7c7c33afd32082a1bbb8

The temporary folder is not removed automatically. To keep the evidence, copy the folder first. To delete it, run this command in PowerShell:
Remove-Item -Recurse -Force "C:\Users\<user>\AppData\Local\Temp\claude\C--Users-<user>-Downloads-REA\<session-id>\scratchpad"

10. Query scripts (verbatim)

To rerun a query, go to the scratchpad and run, for example, node g.js '^AnalysisProviderRegistry' 60 220. Set B=comp-bundle.json or B=cfg-bundle.json to query the other two files.

q.js: search node labels

// usage: node q.js <regex> [kind] [moduleRegex]
const fs=require("fs");
const b=JSON.parse(fs.readFileSync(__dirname+"/"+(process.env.B||"rea-bundle.json"),"utf8"));
const re=new RegExp(process.argv[2],"i"), kind=process.argv[3]||"literal", mre=process.argv[4]?new RegExp(process.argv[4]):null;
for(const r of b.records){const e=r.evidence||r; if(e.operation!=="analyze_javascript_application")continue;
 const root=e.subject.local_path.split("\dist\\")[1];
 for(const n of e.normalized_result.semantic_graph.nodes){ if(kind!=="*"&&n.kind!==kind)continue;
  const m=n.identity.module_path; if(mre&&!mre.test(m))continue;
  const L=String(n.label??n.identity.role_key); if(!re.test(L))continue; const s=n.identity.source_range;
  console.log(`${root}/${m}:${s?s.start.line:"?"}  [${n.kind}] ${L.slice(0,200)}`);}}

g.js: group node labels by source line

// usage: node g.js <moduleRegex> <fromLine> <toLine> : group node labels per line, dedup, drop noise
const {execFileSync}=require("child_process");
const fs=require("fs");
const b=JSON.parse(fs.readFileSync(__dirname+"/"+(process.env.B||"rea-bundle.json"),"utf8"));
const mre=new RegExp(process.argv[2]), from=+process.argv[3], to=+process.argv[4];
const noise=/^(parameter|argument \d+|variable|write|call|construct|import|return|class|await|member|spread|default)$/;
const lines={};
for(const r of b.records){const e=r.evidence||r; if(e.operation!=="analyze_javascript_application")continue;
 for(const n of e.normalized_result.semantic_graph.nodes){const m=n.identity.module_path; if(!mre.test(m))continue;
  const s=n.identity.source_range; if(!s||s.start.line<from||s.start.line>to)continue;
  const L=String(n.label??""); if(noise.test(L)&&n.kind!=="literal")continue;
  const key=m+":"+s.start.line; (lines[key]=lines[key]||new Set()).add((n.kind==="literal"?"LIT ":"")+L);}}
for(const k of Object.keys(lines).sort((a,b)=>+a.split(":").pop()-+b.split(":").pop())) console.log(k, "|", [...lines[k]].join(", ").slice(0,300));

Two known defects in the scripts: g.js imports execFileSync and does not use it. In q.js, the path split string is "\dist\\". JavaScript reads \d as d, so the split gives the correct folder prefix, but only by chance. The file name and line number in its output do not depend on it.