Native Anthropic web_search — built-in (always-on, no toggle) #2

Merged
will.anderson merged 2 commits from feat/native-web-search into main 2026-06-10 22:37:51 +00:00
Member

handle_chat_agentic always attaches Anthropic's native web_search_20250305 tool. Web search is built-in; the model invokes it only when a query needs fresh info (max_uses:5). No user-facing toggle — supersedes the gated approach. The body's web_search field is ignored (back-compat). Pairs with neuron-ui removing the chat-input toggle. .el-only change; reviewer builds/verifies (no elc on authoring machine). Base branch: maintainer's call (neuron has no dev/stage yet).

handle_chat_agentic always attaches Anthropic's native web_search_20250305 tool. Web search is built-in; the model invokes it only when a query needs fresh info (max_uses:5). No user-facing toggle — supersedes the gated approach. The body's web_search field is ignored (back-compat). Pairs with neuron-ui removing the chat-input toggle. .el-only change; reviewer builds/verifies (no elc on authoring machine). Base branch: maintainer's call (neuron has no dev/stage yet).
tim.lingo added 1 commit 2026-06-09 04:32:27 +00:00
Add native Anthropic web_search tool to the agentic chat path
Neuron Soul CI / build (pull_request) Successful in 5m8s
c594cec8f7
When a chat request carries web_search=true, handle_chat_agentic now attaches Anthropic's
NATIVE server-side web_search tool (web_search_20250305) to the request. The native tool is
executed by Anthropic (not by the soul), so it returns real results with citations and needs
no local runtime — it sidesteps the soul's lack of executable tools entirely.

- new agentic_tools_with_web(web_search) helper (appends the native tool to the standard set)
- handle_chat_agentic reads json_get_bool(body,"web_search") and uses it

Pairs with neuron-ui: ChatRequest.web_search + the chat-input Web search toggle.
Note: built/verified by reviewer — no elc on the authoring machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner

Code Review: Native Anthropic web_search in agentic chat

Verdict: REQUEST_CHANGES — the approach is sound and the implementation is clean, but there are several issues that need to be addressed before merging, one of which is a hard blocker.


Summary

This PR adds web_search_20250305 — Anthropic's native server-side search tool — to the agentic chat path by introducing agentic_tools_with_web() and wiring it to ChatRequest.web_search. The architectural choice (server-executed tool, no local runtime needed) is correct and elegant. The change is minimal (14 lines net), well-commented, and integrates cleanly with the existing tool-dispatch loop.


What Was Done Well

  • Correct architectural approach (chat.el line 266–271): Using web_search_20250305 as a server-side tool is the right call. It cleanly sidesteps the soul's inability to execute tools locally, and native search returns citations natively.
  • Clean function decomposition: The new agentic_tools_with_web(web_search: Bool) function follows the existing style and avoids duplicating agentic_tools_literal(). The early-return on !web_search is idiomatic.
  • Correct JSON splicing (chat.el line 269–270): Slicing off the surrounding [/] from the base JSON array and appending the new tool object is structurally correct — the resulting JSON will be valid as long as agentic_tools_literal() always returns a well-formed array (which it does).
  • max_uses: 5 is a reasonable default — it prevents runaway search costs and matches the Anthropic docs recommendation for general-purpose search.
  • The PR description is honest about what wasn't tested: "not built locally — please verify the tool type string." That's good hygiene.

Issues / Needs Work

BLOCKING

1. Missing anthropic-beta: web-search-2025-03-05 header (chat.el lines 323–326)

The web_search_20250305 tool requires the anthropic-beta: web-search-2025-03-05 header to be present on the API request. Without it, the Anthropic API will return a 400 invalid_request_error with a message like "unknown tool type: web_search_20250305". This is the very failure mode Tim flagged in the PR description ("please verify the tool type string").

The header map h is built unconditionally before the loop and reused across all iterations. The correct fix is to conditionally add the beta header when web_search is true. Since the map is built once outside the loop, this is a straightforward addition:

// After map_set(h, "content-type", "application/json"):
let discard: Bool = if web_search {
    map_set(h, "anthropic-beta", "web-search-2025-03-05")
    true
} else { false }

Without this fix, the feature will never work — every request with web_search=true will hit a 400.


NON-BLOCKING (should fix before merge)

2. dispatch_tool has no web_search branch (chat.el lines 273–301)

web_search_20250305 is a server-executed tool — Anthropic calls it, not the soul. But if the API ever returns a tool_use block with name: "web_search" (which can happen in certain edge cases or when the model decides to explicitly invoke it), dispatch_tool will fall through to the "unknown tool: " + tool_name path. That string gets fed back to the API as a tool result, producing a confused model turn.

A defensive guard would help:

// In dispatch_tool, before the final return:
if str_eq(tool_name, "web_search") {
    return "web_search is server-executed; no local dispatch needed"
}

3. handle_dharma_room_turn_agentic is not updated (chat.el line 547)

handle_dharma_room_turn_agentic still calls agentic_tools_literal() directly. If rooms should also support web search (likely, given the system prompt already mentions "browse the web"), it should call agentic_tools_with_web(...) too. If rooms intentionally do not get web search, add a comment explaining why.

4. The web_search flag is silently false when absent (chat.el line 317)

json_get_bool(body, "web_search") returns false if the key is missing. This is correct for the default-off case, but the behavior is undocumented. Add a comment:

// Defaults to false if omitted — web search is opt-in per request
let web_search: Bool = json_get_bool(body, "web_search")

5. dist/chat.c is stale (entire dist/ directory)

The compiled dist/chat.c reflects the old main state — it still calls agentic_tools_literal() directly and has no agentic_tools_with_web function. If the CI pipeline or Dockerfile builds from the dist/ C sources (which the Dockerfile suggests it may, based on the "download pre-built binary" strategy), this PR will deploy the old behavior regardless of the .el changes. Either:

  • Regenerate dist/chat.c as part of this PR, or
  • Document that the El compiler must be run as part of CI before the dist artifacts are updated.

This is the most operationally significant gap if the dist artifacts are the actual deployment artifact.


NITS

  • chat.el line 270: The inline JSON string {\"type\":\"web_search_20250305\",\"name\":\"web_search\",\"max_uses\":5} is the only tool definition in the file that omits an input_schema. The Anthropic API does not require it for native tools, but for consistency with the rest of the tool definitions a brief comment noting "native tools do not need input_schema" would prevent future confusion.
  • The PR description mentions "ChatRequest.web_search" implying the neuron-ui side sends this flag. There is no validation here that the flag is a boolean (vs. a string "true"). json_get_bool presumably handles both, but worth confirming.

Build Result

This is a custom language ("El") that compiles to C — it is not Emacs Lisp despite the .el extension. Emacs is not installed in this environment. The language toolchain (El compiler + SDK) is pulled from Artifact Registry at Docker build time per the Dockerfile.

Static analysis of the source: the El syntax is valid and consistent with the rest of the file. The JSON string surgery in agentic_tools_with_web is structurally correct. The blocking issue is not a syntax or logic error in El — it is a missing HTTP header in the API call, which only manifests at runtime.

The dist/chat.c file does not include the changes from this PR, confirming it was not regenerated. Merging without rebuilding the dist would deploy dead code.


Summary of Required Actions Before Merge

# Severity Action
1 BLOCKING Add anthropic-beta: web-search-2025-03-05 header to h when web_search is true
2 Non-blocking Add web_search guard in dispatch_tool
3 Non-blocking Decide whether handle_dharma_room_turn_agentic gets web search and act accordingly
4 Non-blocking Comment the json_get_bool default-false behavior
5 Non-blocking Regenerate dist/chat.c or document the CI step that does it
## Code Review: Native Anthropic `web_search` in agentic chat **Verdict: REQUEST_CHANGES** — the approach is sound and the implementation is clean, but there are several issues that need to be addressed before merging, one of which is a hard blocker. --- ### Summary This PR adds `web_search_20250305` — Anthropic's native server-side search tool — to the agentic chat path by introducing `agentic_tools_with_web()` and wiring it to `ChatRequest.web_search`. The architectural choice (server-executed tool, no local runtime needed) is correct and elegant. The change is minimal (14 lines net), well-commented, and integrates cleanly with the existing tool-dispatch loop. --- ### What Was Done Well - **Correct architectural approach** (`chat.el` line 266–271): Using `web_search_20250305` as a server-side tool is the right call. It cleanly sidesteps the soul's inability to execute tools locally, and native search returns citations natively. - **Clean function decomposition**: The new `agentic_tools_with_web(web_search: Bool)` function follows the existing style and avoids duplicating `agentic_tools_literal()`. The early-return on `!web_search` is idiomatic. - **Correct JSON splicing** (`chat.el` line 269–270): Slicing off the surrounding `[`/`]` from the base JSON array and appending the new tool object is structurally correct — the resulting JSON will be valid as long as `agentic_tools_literal()` always returns a well-formed array (which it does). - **`max_uses: 5` is a reasonable default** — it prevents runaway search costs and matches the Anthropic docs recommendation for general-purpose search. - **The PR description is honest** about what wasn't tested: "not built locally — please verify the tool type string." That's good hygiene. --- ### Issues / Needs Work #### BLOCKING **1. Missing `anthropic-beta: web-search-2025-03-05` header** (`chat.el` lines 323–326) The `web_search_20250305` tool requires the `anthropic-beta: web-search-2025-03-05` header to be present on the API request. Without it, the Anthropic API will return a `400 invalid_request_error` with a message like `"unknown tool type: web_search_20250305"`. This is the very failure mode Tim flagged in the PR description ("please verify the tool type string"). The header map `h` is built unconditionally before the loop and reused across all iterations. The correct fix is to conditionally add the beta header when `web_search` is true. Since the map is built once outside the loop, this is a straightforward addition: ``` // After map_set(h, "content-type", "application/json"): let discard: Bool = if web_search { map_set(h, "anthropic-beta", "web-search-2025-03-05") true } else { false } ``` Without this fix, the feature will never work — every request with `web_search=true` will hit a 400. --- #### NON-BLOCKING (should fix before merge) **2. `dispatch_tool` has no `web_search` branch** (`chat.el` lines 273–301) `web_search_20250305` is a *server-executed* tool — Anthropic calls it, not the soul. But if the API ever returns a `tool_use` block with `name: "web_search"` (which can happen in certain edge cases or when the model decides to explicitly invoke it), `dispatch_tool` will fall through to the `"unknown tool: " + tool_name` path. That string gets fed back to the API as a tool result, producing a confused model turn. A defensive guard would help: ``` // In dispatch_tool, before the final return: if str_eq(tool_name, "web_search") { return "web_search is server-executed; no local dispatch needed" } ``` **3. `handle_dharma_room_turn_agentic` is not updated** (`chat.el` line 547) `handle_dharma_room_turn_agentic` still calls `agentic_tools_literal()` directly. If rooms should also support web search (likely, given the system prompt already mentions "browse the web"), it should call `agentic_tools_with_web(...)` too. If rooms intentionally do not get web search, add a comment explaining why. **4. The `web_search` flag is silently `false` when absent** (`chat.el` line 317) `json_get_bool(body, "web_search")` returns `false` if the key is missing. This is correct for the default-off case, but the behavior is undocumented. Add a comment: ``` // Defaults to false if omitted — web search is opt-in per request let web_search: Bool = json_get_bool(body, "web_search") ``` **5. `dist/chat.c` is stale** (entire `dist/` directory) The compiled `dist/chat.c` reflects the old `main` state — it still calls `agentic_tools_literal()` directly and has no `agentic_tools_with_web` function. If the CI pipeline or Dockerfile builds from the `dist/` C sources (which the Dockerfile suggests it may, based on the "download pre-built binary" strategy), this PR will deploy the old behavior regardless of the `.el` changes. Either: - Regenerate `dist/chat.c` as part of this PR, or - Document that the El compiler must be run as part of CI before the dist artifacts are updated. This is the most operationally significant gap if the dist artifacts are the actual deployment artifact. --- #### NITS - `chat.el` line 270: The inline JSON string `{\"type\":\"web_search_20250305\",\"name\":\"web_search\",\"max_uses\":5}` is the only tool definition in the file that omits an `input_schema`. The Anthropic API does not require it for native tools, but for consistency with the rest of the tool definitions a brief comment noting "native tools do not need input_schema" would prevent future confusion. - The PR description mentions "ChatRequest.web_search" implying the neuron-ui side sends this flag. There is no validation here that the flag is a boolean (vs. a string `"true"`). `json_get_bool` presumably handles both, but worth confirming. --- ### Build Result This is a custom language ("El") that compiles to C — it is not Emacs Lisp despite the `.el` extension. Emacs is not installed in this environment. The language toolchain (El compiler + SDK) is pulled from Artifact Registry at Docker build time per the Dockerfile. Static analysis of the source: the El syntax is valid and consistent with the rest of the file. The JSON string surgery in `agentic_tools_with_web` is structurally correct. The blocking issue is not a syntax or logic error in El — it is a missing HTTP header in the API call, which only manifests at runtime. The `dist/chat.c` file does not include the changes from this PR, confirming it was not regenerated. Merging without rebuilding the dist would deploy dead code. --- ### Summary of Required Actions Before Merge | # | Severity | Action | |---|----------|--------| | 1 | **BLOCKING** | Add `anthropic-beta: web-search-2025-03-05` header to `h` when `web_search` is true | | 2 | Non-blocking | Add `web_search` guard in `dispatch_tool` | | 3 | Non-blocking | Decide whether `handle_dharma_room_turn_agentic` gets web search and act accordingly | | 4 | Non-blocking | Comment the `json_get_bool` default-false behavior | | 5 | Non-blocking | Regenerate `dist/chat.c` or document the CI step that does it |
will.anderson approved these changes 2026-06-09 19:19:56 +00:00
Dismissed
will.anderson left a comment
Owner

Reviewed — code looks good, CI verified locally. Approved to merge.

Reviewed — code looks good, CI verified locally. Approved to merge.
tim.lingo added 1 commit 2026-06-10 04:39:29 +00:00
feat(chat): make web search built-in (always attach native web_search)
Neuron Soul CI / build (pull_request) Successful in 5m27s
8eea1d94ff
handle_chat_agentic now always attaches Anthropic's native web_search_20250305
tool instead of gating it behind a per-request web_search flag. Web search is a
built-in capability: the model invokes it only when a query needs fresh info
(max_uses:5 caps it), so there is no user-facing toggle. The body's web_search
field is now ignored (back-compat — old UI clients sending it cause no harm).

Pairs with neuron-ui removing the chat-input web search toggle.
Note: .el change only — no elc on the authoring machine; reviewer builds/verifies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tim.lingo changed title from Native Anthropic web_search in agentic chat to Native Anthropic web_search — built-in (always-on, no toggle) 2026-06-10 04:39:37 +00:00
will.anderson approved these changes 2026-06-10 22:01:30 +00:00
will.anderson left a comment
Owner

Re-approved. web_search_20260209 is GA — no beta header needed. Confirmed against current API ref.

Re-approved. web_search_20260209 is GA — no beta header needed. Confirmed against current API ref.
will.anderson merged commit abaa61fd7f into main 2026-06-10 22:37:51 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: neuron-technologies/neuron#2