Files
rea/experiments/analysis-efficiency/command-observation-views.md
T
gensokyo-dev 17f581ff75 perf(reverse): add retained GDB and objdump summaries (#5614)
Add opt-in retained GDB and objdump summaries using the shared analysis-view layer. Preserve default complete Evidence, ordered records, provenance and completion facts.

Validate real Bash disassembly, whole-client context costs, CLI publication and cleanup; retain the original measured cohorts and explicit coverage limitations.

Closes #5584.
2026-10-21 01:20:52 +08:00

140 lines
8.9 KiB
Markdown

# Retained command observations
Addresses [#5584](https://github.com/morluto/rea/issues/5584). GDB command Evidence
repeats ordered MI records in stream groups and a raw mirror; objdump repeats
stdout/stderr in its raw mirror. Explicit `detail: summary` retains the complete
parent and projects existing owner facts once. Default complete Evidence is
unchanged. This adds no tool, MI decoder, live-state cache or command authority.
GDB summaries preserve **every ordered record**, including asynchronous and
unrecognized records, correlated MI result, command, completion/truncation,
invalid-UTF-8 and process-exit facts. They omit the convenience console/target/log
groupings and report their original UTF-16 sizes. Objdump summaries preserve
opaque stdout/stderr, exit/signal, completion and pre/post digest-change facts.
Both carry source parameters/provider/profile, parent identity, limitations and
unknowns. Subjectless observations stay subjectless. Historical observations do
not establish current target state or global coverage. The existing
`inspect_analysis_view` summary operation works after debugger close.
The domain codec now owns the command fact shapes used by contracts and GDB's
capture type; protocol interpretation stays with its provider. MCP requires
retention before command execution. CLI uses the existing shared projection and
nonoverwriting bundle publisher after provider cleanup:
```sh
rea inspect-with-objdump /usr/bin/bash disassemble --symbol execute_command \
--detail summary --evidence_output /tmp/bash-command-evidence.json --json
```
## Real input and correctness
Linux x86-64, Node 24.18.0/npm 11.16.0; GNU GDB 13.1 (Debian 13.1-3),
GNU objdump 2.40, installed Bash ELF 1,265,648 bytes, SHA-256
`55b89ab22bee4792a210f493a53fb066accd5d30b69837c28d98be5ff863efcf`.
The [receipt](command-observation-views.json) pins native binaries, baseline main
`c2858d2aa0916f0dd2d073d848b8c2195d9bf6b3`, source patch and packaged runtime digests.
No target execution, attach, GUI or database mutation occurred.
An [independent ELF reader](command-observation-oracle.py) maps dynamic symbols
to exact file ranges: `execute_command`, 239 bytes at `0x4ba50`, and
`execute_command_internal`, 15,428 bytes at `0x47e00`. The
[verifier](command-observation-verify.py) checks every disassembled byte and its
contiguous address against those ranges. GDB reports 66/3,365 instructions;
objdump byte lines also include continuations, so their count is not an
instruction count. Ordered summary facts match each exported complete parent.
GDB Evidence has a null subject; the external oracle does not manufacture a
target binding. Actual CLI default objdump Evidence matches the baseline exactly,
including identity; symbol/pathless summaries preserve it in bundles. CLI checks
also cover rejected overwrite, missing destination before dispatch, and a real
GDB command with a caller-owned PTY that remains available after debugger exit.
## Whole tasks and resource costs
The [SDK driver](command-observation-queries.mjs) discovers all 210 tools, reads
architecture and symbol-scoped disassembly, closes GDB where applicable, exports
complete Evidence and finalizes owned resources. GDB uses 8 calls; objdump uses 3.
Warm cases repeat disassembly in the same process, adding one call. Fresh-process
cases have **unflushed OS caches**, not disk-cold inputs. Native commands are
rerun; results are not reused as current live state.
The [actual Codex 0.163 app-server driver](command-observation-wire.py) uses a
loopback deterministic SSE fixture, no AI inference or paid service. All 210
function schemas are present in every normal model-facing request. Matched tasks
use full input/output profiles; only disassembly selects summary. The
[counter](command-observation-costs.py) counts compact JSON serialization with
`tiktoken 0.12.0 / cl100k_base` across **every** request, including schemas,
replayed results and the actual client's local compaction request. The fixture
answers compaction with observed identifiers and the retained-body location.
These are serialization-token proxies, not model rendering, acceptance, answer
quality, actual usage or billing. Compaction behavior is qualified only for this
client/configuration; no inference quality is established.
| Completed GDB task | JSON token proxy before → after | Requests | Wall seconds | CPU seconds | Family peak MiB |
| ------------------------------ | ------------------------------: | -------: | ------------: | ------------: | --------------: |
| Small, 30,000 per-tool budget | 605,133 → 580,070 | 9 → 9 | 7.906 → 8.618 | 7.195 → 7.832 | 788.9 → 785.9 |
| Large, 350,000 per-tool budget | 1,269,405 → 1,091,290 | 10 → 9 | 9.551 → 9.127 | 8.832 → 8.370 | 805.2 → 770.8 |
Whole-task context proxies decrease **4.14% / 14.03%**, rather than the much
larger reply-byte reduction. The large baseline makes one compaction request;
the candidate makes none. The small task is slower. These single paired samples
do not establish a general speedup. With the client's default output budget,
the small baseline delivers truncated invalid inner JSON and does **not**
complete the question; summary completes it. That incomplete baseline is
excluded from the percentage comparison.
| SDK task | Reply bytes before → after | Whole wall seconds | CPU seconds | Family peak MiB |
| ------------- | -------------------------: | -----------------: | ------------: | --------------: |
| GDB small | 54,123 → 18,009 | 6.631 → 5.447 | 6.102 → 5.240 | 536.3 → 535.8 |
| GDB large | 2,763,983 → 700,453 | 6.965 → 6.660 | 6.729 → 6.451 | 595.0 → 545.8 |
| Objdump small | 22,467 → 15,990 | 7.599 → 7.699 | 7.110 → 7.264 | 527.8 → 532.5 |
| Objdump large | 964,889 → 487,219 | 7.737 → 7.499 | 7.334 → 6.935 | 537.0 → 536.6 |
Same-process repeat disassembly takes 1,100 → 1,016 ms for GDB and
1,070 → 939 ms for objdump in these observations. Whole warm-task CPU improves
7.613 → 7.162 s for GDB but increases 7.819 → 7.859 s for objdump. Timings include
existing ownership checks; no check was removed or cached.
Retaining a summary also retains an additional derived record: large GDB bundles
grow 1,310,073 → 1,640,022 bytes; objdump 474,288 → 712,656. This trades retained
storage for smaller delivery; long-session heap growth is unmeasured. Retained-data lifetime follows the existing investigation ledger and cleanup
owners. Output still scales
with the requested complete disassembly; summary is not a fixed-size preview.
Each experiment runs in one owned systemd scope, one CPU, no swap, 4 GiB hard /
3 GiB early memory thresholds and host-memory admission/stop thresholds. Recorded
payload exit and cleanup are verified, with zero OOM/resource events. Final
whole-scope counters disappear after teardown and remain explicitly unverified.
The receipt includes CPU time, wall time, peak family memory, warm cases and
bundle growth. Build/check scopes use separate bounded budgets. Host-memory
check stops and initial fixture failures are preserved locally and are not passes.
## Coverage and reproduction
320 focused tests cover the new SDK schemas/views plus existing GDB queue,
malformed output, cancellation, process exit, target identity and cleanup cases,
objdump symbol selection and command admission. Real provider evidence is from
the CLI/SDK/client runs above, not simulated-provider tests.
Run the oracle for the selected Bash image, then use the SDK driver with
`--runtime`, `--input`, `--oracle`, `--output`, `--provider gdb|objdump`,
`--symbol`, `--detail complete|summary` and optional `--warm`. The wire driver
also takes the fixed Codex/Node paths, `--mode`, `--symbol` and `--budget`; it always performs the
export (eight tool calls). Run each through `resource-run.py --observe-payload-exit`, preserving
its sibling `.log`; the verifier and counter accept `--root` for those results.
Default Rizin execution returned ENOENT with no configured alternative. Cutter
reported no live bridge; no configured selector or default bridge directory was
present. Real Rizin/Cutter performance and GUI workflows remain unverified.
Their command semantics were not changed. Existing objdump native symbol
selection was reused. No claim covers arbitrary GDB target execution or another
host client's output budget.
Review follow-up adds nine CLI publication regressions (16 CLI tests including the
existing cleanup cases), three deterministic delivery-oracle tests, and a focused
strict typecheck of the new CLI boundary and its transitive source. Real client
small/default and large/generous summary runs pass the strengthened oracle and
export complete parents; only the explicit complete/default baseline may finish
with an incomplete answer. The unused export switch was removed: export is
mandatory in this question. Product source and the original paired measurements
are unchanged; the receipt records these additional validation runs separately.