[GH-ISSUE #8399] [Design] Let non-idempotent tools take a caller-supplied idempotency key so a dropped-connection retry cannot run the target twice #6584

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

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

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

Should the tools REA audits as idempotent: false accept an optional caller-supplied key, so that a second call with the same key returns the retained Evidence of the first instead of running again? The shipped skill tells the agent to "retry that call once only when it is safe to repeat or known not to have started" (.agents/skills/reverse-engineer-anything/SKILL.md:38-41, references/connection-and-recovery.md:23, 96-99), but no contract lets the agent establish "known not to have started" after the connection dropped.

Context and evidence

Source-derived, from main at 6e9ee97a69; no agent run is claimed.

  • src/contracts/toolEffects.ts audits 43 tools as idempotent: false; 36 of them change something outside the session (executesForeignCode, mutatesDevice, mayDiscardData, writesFilesystem or mutatesTarget): the capture tools (capture_process_scenario, capture_browser_scenario, capture_electron_scenario, capture_native_ui_scenario, observe_native_calls), the Frida, GDB, Rizin and Cutter session and command tools, the adb device tools (install_adb_package, uninstall_adb_package, push_adb_file, start_adb_app, start_adb_activity, stop_adb_app, the pulls, screen and bugreport), extract_artifact, extract_firmware, decompile_all, export_web_scripts, export_environment_bundle, recover_javascript_sources, close_binary, reset_investigation.
  • None of their input schemas has a key, request id or client token (checked over the extracted input keys of every contract; the only revision guards are expected_revision on update_unknown and update_analysis_settings, src/domain/residualUnknown.ts:267).
  • When the MCP connection drops after the request was sent, the result is lost with the connection, the MCP request id is per connection, and binary_session reports session state, not whether a particular launch or write happened. The skill's rule then falls back to "tell the user" (SKILL.md:39-41), which is right but leaves the common case (a capture whose result the host discarded) unrecoverable without a second run.
  • A static check of the shipped skill's flows under that retry rule finds 36 call sites where the retried call is one of the tools above, each of which would need a journal the agent cannot read today.
  • Unknown: whether and how the MCP hosts REA supports (Claude Code, Codex, Cursor, Gemini CLI) resend a request after a transport drop. NOT_ENOUGH_INFO.

Expected interaction with targets

Launch or attach to a process; Write or change files/configuration; Not sure yet. (The key changes nothing about what a first call does; it prevents a second identical call from doing it again.)

Boundary details (optional)

  • The key is caller-authored opaque data, retained with the Evidence like other caller-supplied values; it never selects a target or broadens an action.
  • A repeated key with a different request body should be refused (invalid_params), as expected_revision refuses stale writers, not silently answered.
  • Retention follows the Evidence ledger (reset_investigation clears it), so the dedup window is the session.

Options and tradeoffs

  1. Optional idempotency_key on every idempotent: false tool, answered from the retained Evidence when the key repeats within the session. Smallest agent-side change: the skill's retry rule becomes "retry with the same key".
  2. Only for the launch and device tools (the 36 with effects outside the session); the session-only ones (record_unknown, update_unknown, ...) already have revision guards or are cheap to repeat.
  3. Keep the contracts and instead expose, in binary_session, the last admitted request per tool with its state (started, finished, Evidence id), so the agent can decide "known not to have started". Weaker: it does not cover a request admitted after the connection dropped.

Verification available so far

I can suggest a sample or manual check: call capture_process_scenario for /usr/bin/true with a key, call it again with the same key, assert one process launch and two identical Evidence ids; a different body with the same key is refused.

Success criteria and open questions

  • A retry after a transport drop never runs a non-idempotent tool a second time within a session.
  • Open: whether the key should also survive reset_investigation with save_path (an exported bundle).

References

  • .agents/skills/reverse-engineer-anything/SKILL.md:38-41; references/connection-and-recovery.md:23, 96-110
  • src/contracts/toolEffects.ts (idempotent: false entries); src/domain/residualUnknown.ts:267
  • docs/mcp-contracts.md "Error recovery and deadline ownership"
Originally created by @trackoor on GitHub (Oct 31, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/8399 ### 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 Should the tools REA audits as `idempotent: false` accept an optional caller-supplied key, so that a second call with the same key returns the retained Evidence of the first instead of running again? The shipped skill tells the agent to "retry that call once only when it is safe to repeat or known not to have started" (`.agents/skills/reverse-engineer-anything/SKILL.md:38-41`, `references/connection-and-recovery.md:23, 96-99`), but no contract lets the agent establish "known not to have started" after the connection dropped. ### Context and evidence Source-derived, from main at 6e9ee97a69cb16c7150d8b550e705cba055ee4f0; no agent run is claimed. - `src/contracts/toolEffects.ts` audits 43 tools as `idempotent: false`; 36 of them change something outside the session (`executesForeignCode`, `mutatesDevice`, `mayDiscardData`, `writesFilesystem` or `mutatesTarget`): the capture tools (`capture_process_scenario`, `capture_browser_scenario`, `capture_electron_scenario`, `capture_native_ui_scenario`, `observe_native_calls`), the Frida, GDB, Rizin and Cutter session and command tools, the adb device tools (`install_adb_package`, `uninstall_adb_package`, `push_adb_file`, `start_adb_app`, `start_adb_activity`, `stop_adb_app`, the pulls, screen and bugreport), `extract_artifact`, `extract_firmware`, `decompile_all`, `export_web_scripts`, `export_environment_bundle`, `recover_javascript_sources`, `close_binary`, `reset_investigation`. - None of their input schemas has a key, request id or client token (checked over the extracted input keys of every contract; the only revision guards are `expected_revision` on `update_unknown` and `update_analysis_settings`, `src/domain/residualUnknown.ts:267`). - When the MCP connection drops after the request was sent, the result is lost with the connection, the MCP request id is per connection, and `binary_session` reports session state, not whether a particular launch or write happened. The skill's rule then falls back to "tell the user" (`SKILL.md:39-41`), which is right but leaves the common case (a capture whose result the host discarded) unrecoverable without a second run. - A static check of the shipped skill's flows under that retry rule finds 36 call sites where the retried call is one of the tools above, each of which would need a journal the agent cannot read today. - Unknown: whether and how the MCP hosts REA supports (Claude Code, Codex, Cursor, Gemini CLI) resend a request after a transport drop. `NOT_ENOUGH_INFO`. ### Expected interaction with targets Launch or attach to a process; Write or change files/configuration; Not sure yet. (The key changes nothing about what a first call does; it prevents a second identical call from doing it again.) ### Boundary details (optional) - The key is caller-authored opaque data, retained with the Evidence like other caller-supplied values; it never selects a target or broadens an action. - A repeated key with a different request body should be refused (`invalid_params`), as `expected_revision` refuses stale writers, not silently answered. - Retention follows the Evidence ledger (`reset_investigation` clears it), so the dedup window is the session. ### Options and tradeoffs 1. Optional `idempotency_key` on every `idempotent: false` tool, answered from the retained Evidence when the key repeats within the session. Smallest agent-side change: the skill's retry rule becomes "retry with the same key". 2. Only for the launch and device tools (the 36 with effects outside the session); the session-only ones (`record_unknown`, `update_unknown`, ...) already have revision guards or are cheap to repeat. 3. Keep the contracts and instead expose, in `binary_session`, the last admitted request per tool with its state (started, finished, Evidence id), so the agent can decide "known not to have started". Weaker: it does not cover a request admitted after the connection dropped. ### Verification available so far I can suggest a sample or manual check: call `capture_process_scenario` for `/usr/bin/true` with a key, call it again with the same key, assert one process launch and two identical Evidence ids; a different body with the same key is refused. ### Success criteria and open questions - A retry after a transport drop never runs a non-idempotent tool a second time within a session. - Open: whether the key should also survive `reset_investigation` with `save_path` (an exported bundle). ### References - `.agents/skills/reverse-engineer-anything/SKILL.md:38-41`; `references/connection-and-recovery.md:23, 96-110` - `src/contracts/toolEffects.ts` (`idempotent: false` entries); `src/domain/residualUnknown.ts:267` - `docs/mcp-contracts.md` "Error recovery and deadline ownership"
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#6584
No description provided.