Skip to content

feat(navigator): let N2ComputerAgent serve and dispatch caller tools - #364

Merged
lawrencechen98 merged 3 commits into
mainfrom
lawrence/n2-agent-custom-tools
Sep 4, 2026
Merged

feat(navigator): let N2ComputerAgent serve and dispatch caller tools#364
lawrencechen98 merged 3 commits into
mainfrom
lawrence/n2-agent-custom-tools

Conversation

@lawrencechen98

@lawrencechen98 lawrencechen98 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Problem

chat.completions.create already accepts caller-supplied tools, but N2ComputerAgent implements only the tools in its set, so it has nowhere to dispatch one. api.md said as much:

N2ComputerAgent implements only the tools in the set … To actually run custom tools, drive chat.completions.create in your own loop.

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's run_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, write and edit stay 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_calls takes custom_tool_names so the routing is testable on its own.

Behaviour preserved at both edges:

  • an adapter with no run_custom_tool gets the same recoverable "not supported" result the other families produce, instead of an AttributeError mid-run
  • a name the caller did not declare still fails with [ERROR] Invalid <name> call: … does not expose <name>, so a typo in a definition surfaces

Name 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 different tool_set.

Test

Four tests in tests/test_navigator_n2_harness.py cover 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
N2ComputerAgent now accepts tools=, forwards those OpenAI-shaped definitions on every completion request, and routes matching model calls through a single run_custom_tool(name, arguments) hook on the computer adapter instead of treating them as unsupported set tools.

parse_n2_tool_calls takes custom_tool_names so declared names become run_custom_tool actions; undeclared names still get recoverable does not expose errors, and adapters without the hook get a recoverable [ERROR] rather than crashing the run.

Observation policy: caller custom tools and built-in goto_url no longer return text-only results like bash—their tool output is input_image with adapter text plus a post-action screenshot, so the model is not left blind after navigation-style actions.

api.md documents the new agent path; tests/test_navigator_n2_harness.py adds 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.

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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread yutori/navigator/n2.py
lawrencechen98 and others added 2 commits September 3, 2026 18:22
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
lawrencechen98 merged commit dc62738 into main Sep 4, 2026
16 checks passed
@lawrencechen98
lawrencechen98 deleted the lawrence/n2-agent-custom-tools branch September 4, 2026 16:01
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant