Skip to content

refactor(runtime): lift tool dispatch table to typed ToolError (#3576 slice 5)#6124

Merged
houko merged 3 commits into
mainfrom
refactor/dispatch-toolerror
Jun 16, 2026
Merged

refactor(runtime): lift tool dispatch table to typed ToolError (#3576 slice 5)#6124
houko merged 3 commits into
mainfrom
refactor/dispatch-toolerror

Conversation

@houko

@houko houko commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Slice 5 of the #3576 error-contract migration: lift the tool_runner/dispatch.rs match table off the stringly boundary.

The dispatch table previously narrowed every arm back to Result<String, String> via .map_err(|e| e.to_string()), then stuffed the string into a hard-error ToolResult — discarding the ToolError kind that each submodule had already produced. The table now yields Result<String, ToolError> natively and converts once, at the end, through the existing tool_result_from_typed.

The payoff is the exact problem #3576 names: every tool's ToolError::execution_status() now reaches the ToolResult. A tool-level PermissionDenied (e.g. agent_send depth-exceeded) surfaces as the soft Denied status — reported to the model but not counted toward the consecutive-hard-failure abort — instead of the stringly path's hard default. This generalises the #5984 fix from the ACL-gated memory/wiki tools to every tool.

Changes

  • Remove 67 boundary .map_err(|e| e.to_string()) narrows; dispatch.rs drops from 10 Result<String, String> sites to 0.
  • Map the inline stringly arms to their semantic variants: missing url/query → MissingParameter, unknown tool → NotFound, browser-subsystem-missing → Unavailable.
  • Keep a .map_err(ToolError::upstream_msg) bridge only over the still-stringly downstreams (browser_tools, web_fetch_to_file, channel::tool_channel_send, MCP / skill providers), each pending its own slice.
  • Add converter-boundary unit tests pinning the soft/hard status mapping and the typed wire strings.
  • Update the now-stale tests/shell.rs "Unknown tool" assertion to the typed NotFound phrasing.
  • Refresh the error-contracts RFC (slice 5 marked done) and the error.rs module doc.

Compatibility

Public execute_tool / execute_tool_raw signatures are unchanged (still return the ToolResult struct), so there is no downstream API change. The one intentional behaviour change is the soft-Denied classification described above.

Verification

  • cargo check -p librefang-runtime with default features and --no-default-features
  • cargo clippy -p librefang-runtime — zero warnings
  • cargo test -p librefang-runtime --lib — 1991 passed, 0 failed (incl. 4 new dispatch tests)
  • librefang-api / librefang-cli check clean; tool-runner forwarding integration tests pass

Refs #3576.

…slice 5)

The `tool_runner/dispatch.rs` match table previously narrowed every arm back to `Result<String, String>` via `.map_err(|e| e.to_string())` at the boundary, then stuffed the string into a hard-error `ToolResult` — discarding the `ToolError` kind that every submodule had already produced.

The table now yields `Result<String, ToolError>` natively and converts once, at the end, through `tool_result_from_typed`. That routes each error's `execution_status()` onto the `ToolResult` for every tool, generalising the #5984 fix (previously only the ACL-gated memory/wiki tools): a tool-level `PermissionDenied` (e.g. agent_send depth-exceeded) now surfaces as the soft `Denied` status, reported to the model but not counted toward the consecutive-hard-failure abort, instead of the stringly path's hard default.

Inline stringly arms map to their semantic variants: missing url/query -> `MissingParameter`, unknown tool -> `NotFound`, browser-subsystem-missing -> `Unavailable`. The handful of still-stringly downstreams (`browser_tools`, `web_fetch_to_file`, `channel::tool_channel_send`, MCP / skill providers) keep a `.map_err(ToolError::upstream_msg)` bridge pending their own slices.

Public `execute_tool` / `execute_tool_raw` signatures are unchanged (still return the `ToolResult` struct), so there is no downstream API change.

- Removes 67 boundary `.map_err(|e| e.to_string())` narrows
- Drops dispatch.rs from 10 `Result<String, String>` sites to 0
- Adds converter-boundary tests pinning the soft/hard status mapping and the typed wire strings
- Updates the now-stale "Unknown tool" assertion in tests/shell.rs to the typed `NotFound` phrasing
- Refreshes the error-contracts RFC migration order (slice 5 done) and the error.rs module doc
@github-actions github-actions Bot added area/docs Documentation and guides area/runtime Agent loop, LLM drivers, WASM sandbox size/L 250-999 lines changed labels Jun 16, 2026
…mment

Comments referencing the current task slice number and superseded issue
IDs will rot as the codebase evolves. Trim them: drop the module-level
block comment in the test mod (test names are self-describing), shorten
the test helper comment and the `typed_not_found` inline note, and remove
the `shell.rs` change comment (assertions are already clear).

The invariant comment before `tool_result_from_typed` is reworded to
state the non-obvious WHY (soft vs hard abort counting) without the
issue numbers.
@houko
houko enabled auto-merge (squash) June 16, 2026 03:24
@houko
houko merged commit 52545d2 into main Jun 16, 2026
32 checks passed
@houko
houko deleted the refactor/dispatch-toolerror branch June 16, 2026 04:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/docs Documentation and guides area/runtime Agent loop, LLM drivers, WASM sandbox size/L 250-999 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants