feat(navigator): let N2ComputerAgent serve and dispatch caller tools - #364
Merged
Conversation
The agent implemented only the tools in its set, so a caller-supplied `tools` definition had nowhere to go: the model would call it and get back `does not expose <name>`. Running custom tools meant abandoning the agent and driving chat.completions.create in your own loop, re-implementing the batch mechanics, image window, compaction and budgets to add one function. N2ComputerAgent now takes `tools` and forwards them with each request, and a call to one is dispatched to the computer's `run_custom_tool(name, arguments)`. Its returned text is the tool result, with no frame, exactly as for `bash`. This follows the shell/file/browser families already in the loop: one action type, one handler-map entry, one adapter hook. An adapter without the hook gets the same recoverable not-supported result those families produce, and an undeclared name still fails as before, so a typo surfaces rather than silently doing nothing. Motivated by evaluating n2 on the OSWorld driver's browser tools (goto_url, go_back, execute_js, ...) alongside the canonical desktop set of computer_batch, bash, read, write and edit. No existing tool set serves that combination: the browser-navigation sets predate the shell and file tools. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 65c7457. Configure here.
A custom tool acts on the machine, so its result now carries the post-action screenshot alongside the tool's own text, exactly as computer_batch does. The first cut rendered it like bash -- text, no frame -- which was wrong for the case these tools exist to serve: a navigation replaces the page, and the model cannot act on what it cannot see. Measured on 132 OSWorld-driver tasks before this change: 42% of goto_url calls (54 of 128) were immediately followed by a screenshot-only computer_batch whose only purpose was to see the page just navigated to. That is a whole turn per navigation, against a step cap that counts turns. goto_url loses its text-only special case for the same reason. bash and the file tools keep theirs -- they change nothing on screen, so a frame would be pure token cost. Custom output moves to its own slot rather than reusing shell_output_text, which is what routed it to the text-only exit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PHkcDvvjaw6TNukT99af2o
A failed post-action screenshot returned early with only "Post-action screenshot failed: ...", discarding the stopped_reason the action itself had set. The model was then told the frame broke when the tool call was what actually failed, and the real cause never reached it. This path has always been reachable for GUI batches; routing custom tools through it for their frame widened the exposure to a second family, so report both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PHkcDvvjaw6TNukT99af2o
lawrencechen98
added a commit
that referenced
this pull request
Sep 4, 2026
…ls (#379) #364 lets a caller serve its own tools, but the documented example was abstract. These are the eight browser tools the feature was evaluated with -- four navigation, four DOM -- in the shape the API expects, so a user can paste them into `tools=` and implement `run_custom_tool` against a known-good schema. Data and docs only; nothing imports this file. Claude-Session: https://claude.ai/code/session_01PHkcDvvjaw6TNukT99af2o Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
chat.completions.createalready accepts caller-suppliedtools, butN2ComputerAgentimplements only the tools in its set, so it has nowhere to dispatch one. api.md said as much:That is a steep price for adding one function: you give up the batch mechanics, coordinate mapping, image window, compaction, budgets and callbacks, and re-implement them.
Change
N2ComputerAgent(tools=[...])forwards the definitions with every request and dispatches a call to one of them to the computer'srun_custom_tool(name, arguments). The returned text becomes the tool result, and the call carries the post-action frame the way a GUI batch does:{"type": "input_image", "image_url": ..., "result": "<tool text>"}. A custom tool acts on the machine — a navigation replaces the page — so leaving it text-only sent the model back for a screenshot it should already have had.bash,read,writeandeditstay text-only: they change nothing on screen.It follows the families the loop already has (
SHELL_ACTION_HANDLERS,FILE_ACTION_HANDLERS,BROWSER_ACTION_HANDLERS): one action type, one handler-map entry, one adapter hook.parse_n2_tool_callstakescustom_tool_namesso the routing is testable on its own.Behaviour preserved at both edges:
run_custom_toolgets the same recoverable "not supported" result the other families produce, instead of anAttributeErrormid-run[ERROR] Invalid <name> call: … does not expose <name>, so a typo in a definition surfacesName collisions are left to the server, which already refuses a definition shadowing a served name; duplicating that check here would only drift.
Why
Evaluating n2 on the OSWorld driver's browser tools (
goto_url,go_back,go_forward,refresh,extract_elements,find,set_element_value,execute_js) alongside the canonical desktop set —computer_batch,bash,read,write,edit. No published tool set serves that combination: the browser-navigation sets predate the shell and file tools, so they cannot be combined by picking a differenttool_set.Test
Four tests in
tests/test_navigator_n2_harness.pycover routing, the served request, the adapter hook receiving name and arguments, the text-result rendering, and both failure edges. Full n2 suites pass: 139 passed.🤖 Generated with Claude Code
Note
Medium Risk
Changes the n2 agent loop’s tool dispatch and when screenshots attach to tool results, which affects model turns and token usage; failures are recoverable and covered by new harness tests.
Overview
N2ComputerAgentnow acceptstools=, forwards those OpenAI-shaped definitions on every completion request, and routes matching model calls through a singlerun_custom_tool(name, arguments)hook on the computer adapter instead of treating them as unsupported set tools.parse_n2_tool_callstakescustom_tool_namesso declared names becomerun_custom_toolactions; undeclared names still get recoverabledoes not exposeerrors, and adapters without the hook get a recoverable[ERROR]rather than crashing the run.Observation policy: caller custom tools and built-in
goto_urlno longer return text-only results likebash—their tool output isinput_imagewith adapter text plus a post-action screenshot, so the model is not left blind after navigation-style actions.api.mddocuments the new agent path;tests/test_navigator_n2_harness.pyadds four tests for routing, serving, execution, and failure edges.Reviewed by Cursor Bugbot for commit 06ad7fc. Bugbot is set up for automated code reviews on this repo. Configure here.