feat(engine): port Anthropic server-side web_search into the agentic loop #108
Closed
tim.lingo
wants to merge 0 commits from
feat/soul-native-web-search-20260804 into main
pull from: feat/soul-native-web-search-20260804
merge into: :main
:main
:fix-mcp-wrapper-tool-schemas
:docs-cognitive-architecture
:docs/session-2026-08-13-language-faculty
:docs/geo-operators-el-cutover-reversal-2026-08-13
:docs/engram-cognitive-architecture-2026-08-12
:engram-store-wiring
:el-route-decorators
:fix/bound-session-payload
:feat/cgi-identity-accessors
:fix/restore-soul-identity-producer
:rebase/openai-tools-onto-main
:rebase/gate-state-key-reads-onto-main
:rebase/structural-audit-onto-main
:feat/self-seeded-context
:integrate/semantic-plus-writethrough
:feat/semantic-leg-dropout
:feat/semantic-substrate
:feat/gold-set-heldout
:feat/evidence-weighted-merge
:feat/structural-audit
:feat/claim24-unfloored-semantic
:feat/executive-filter-recall
:feat/bm25-lexical-leg
:feat/semantic-seeded-assoc
:feat/associative-recall-leg
:feat/hybrid-semantic-recall
:chore/retrieval-measurement-contract
:feat/retrieval-eval-harness
:feat/soul-write-through
:feat/recall-through-activation
:chore/regen-soul-amalgam-20260807
:feat/gate-state-key-reads
:fix/129-on-openai-tools
:fix/129-history-amplification
:fix/liveness-engine-91
:feat/soul-openai-tools-v2
:fix/resume-server-tool-replay
:fix/soul-history-provenance-20260805
:feat/soul-plain-chat-generation-20260805
:feat/soul-native-web-search-20260804
:ci/pin-vendored-runtime
:reconcile/soul-union-main
:fix/bound-session-payload-main
:fix/receipts-wrapper-forget
:fix/receipts-agent-tools
:reconcile/neuron-wip-to-main
:hotfix/trackb-threat-to-others
:reconcile/hotfix-to-main-launch
:hotfix/elc-source-typos
:fix/safety-contact-truncation
:fix/genesis-boot-crash
:fix/immutable-on-hotfix
:feat/bounded-persona-floor
:ci/rdynamic-http-handler
:ci/harden-gate-boot
:feat/agent-phase1-soul
:salvage/elh-state-20260704
:feat/soul-model-self-report
:feat/openai-format-providers
:el-runtime-repoint
:feat/plan-mode-endpoint
:fix/operator-identity-home-resolution
:fix/wrapper-backlog-endpoints
:fix/list-typed-slice-offset
:feat/connectors-call-route
:fix/chat-vision-attachments
:fix/prevent-engram-corruption
:fix/emergency-regressions
:fix/session-continuity-hook
:fix/context-dedup-shared-ids
:fix/engram-float-parser
:improve/recall-context-format
:improve/recall-context-dedup
:improve/recall-cross-session-continuity
:improve/recall-emotional-recall
:improve/recall-activation-seed
:improve/recall-recall-completeness
:improve/recall-temporal-precision
:improve/recall-engram-scoring
:improve/recall-recall-reliability
:improve/recall-session-start-recall
:improve/reliability-engram-write
:improve/reliability-state-management
:improve/soul-memory-formation
:improve/safety-crisis-detection
:improve/reliability-route-error-recovery
:improve/reliability-llm-retry
:improve/reliability-session-boundary
:improve/reliability-safety-resilience
:improve/reliability-engram-connection
:improve/soul-routes-api
:improve/reliability-cross-session-affective
:propose/agent-workspace-root-read
:improve/reliability-conv-history
:improve/soul-strip
:improve/soul-chat-pipeline
:docs/conversation-retrieval-design
:propose/no-fake-tools-in-chat-mode
:fix/ci-soul-build-single-file
:fix/canonical-self-bridge
:feat/agent-tool-workspace-scope
:fix/agentic-tools-duplicate-web-search
:green/agentic-fixes
:feat/connectors-soul
:feat/layer-safety
:feat/layer-imprint
:feat/layer-stewardship
:test/layer-composition
:test/layer-safety
:test/layer-stewardship
:test/layer-imprint
:feat/memory-delete-update
:feat/native-web-search
No Reviewers
Labels
Clear labels
BETA-CRITICAL
blocks-public-beta
HELD
ORTHOGONAL
P0
POST-BETA
security
On the beta critical path — must resolve before ship
Must be fixed before any public beta
Blocked pending an external decision (counsel / dual sign-off)
Parallel workstream (testing/hardening) — safe to assign off critical path
Drop-everything severity
Important but not blocking the beta ship
Security vulnerability
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: neuron-technologies/neuron#108
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feat/soul-native-web-search-20260804"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Root cause
The product promises web search; the engine never attached the tool.
agentic_tools_with_web()(chat.el:1418) was defined but reachable only through a legacytools_variantfield nothing writes. The intended design existed and was lost: commit8eea1d9(2026-06-09, Tim-approved) made native web_search built-in with no user-facing toggle, andtests/test_agentic_tools.el§2 still assertsagentic_tools_all()contains it — an assertionmaincurrently fails. The call site disappeared whenagentic_tools_all()(connector tools, PR #19) replacedagentic_tools_with_web()inhandle_chat_agentic.Separately,
pause_turnwas absent everywhere — zero occurrences in the shipped binary. Server-side search pauses turns; without handling, the loop treats the partial text as final and the user gets a silently truncated answer.The change
Re-authors
soul-webfix-20260711.patchin El. That patch is a diff against generated C at month-old offsets, so nothing was applied as a patch — every hunk was re-authored. Its last two hunks are an unrelated/api/safety-contactimplementation, deliberately not ported (the route already exists, is safety-critical, and passes the contract gate as-is).One file,
chat.el, +256/−22:web_search_tool_json()(new) — one source of truth for the tool block; version from state keyweb_search_tool_version.strip_client_web_search()(new) — removes any client tool namedweb_searchbefore attaching the server one, so they can never collide on the name. No-op on our literal set (it shipsweb_get); exists for third-party MCP connector tools.agentic_tools_all()— attaches the server tool unconditionally. One edit covershandle_chat_agentic,handle_dharma_room_turn_agentic, andagentic_resume(via the stored bridge blob).agentic_loop()—pause_turnhandling;final_textaccumulates across resume cycles instead of overwriting;server_tool_useaccounting intotools_used; iteration cap 8→12;max_tokens4096→16384; container-id carry-forward; version-drift fallback; API error head logged instead of swallowed;tools_usedgated onis_tool_turnso a truncated tool block isn't reported as work done.The stopgap conflict — surfaced, not papered over
web_search_20260209(dynamic filtering) is hard-incompatible with ADR 0005. Dynamic filtering uses server-side programmatic tool calling; the API refuses the pair. Verified live on bothclaude-opus-4-8andclaude-sonnet-4-5:Neither feature is dropped. Dropping the stopgap resurrects neuron#78 bug b (killed runs 3/3 in ADR 0005's own A/B);
web_search_20250305is compatible with the stopgap and returns real results. So the default is the basic variant. What we give up is dynamic filtering — a token-efficiency/quality nicety, not the capability.Removal trigger: flip to
_20260209when ADR 0005 retires. One config write, no recompile. The fallback also fires onprogrammatic tool calling, so a premature flip self-heals loudly instead of dying.Bug this port exposed (fixed here)
json_getis a first-match scanner, and a cited text block serialisescitationsfirst:so
json_get(block,"type")returnedweb_search_result_location,"text"never matched, and every citation-bearing block was dropped — exactly the blocks carrying the searched facts. Users gotThe current temperature is , with .. Only text blocks carrycitations, so its presence identifies the type. Reply length on the same question: 79 → 360 chars.How to test
Sandbox: throwaway HOME/engram, dead axon + ISE,
NEURON_PORTset explicitly (~/neuron_soul_smokebuild.shexportsSOUL_PORT, which the soul ignores — it readsNEURON_PORT, default 7770, the live port; that latent bug is still open).POST /api/chat {"agentic":true}asking for current weather →tools_used:["web_search"], live figures (86°F Bentonville, RealFeel 98°, isolated thunderstorms), 6s.scripts/verify-soul-contract.sh <bin> <port>under bash ≥ 4.3 → GATE: PASS, 27/27 routes, immutability PASS. (macOS/bin/bash3.2 fails onlocal -n; use/opt/homebrew/bin/bash.)["read_file","read_file","read_file"], 4 iterations, correct answer, no 400.Honest gap:
pause_turnis compiled in but not exercised by a live pause —max_uses:5caps the server loop below the pause threshold. Code-complete, unexercised.Needs Will
dist/soul.c— deliberately not regenerated here. The localelc(2026-06-05) predates your last regen (e610a41); regenerating with an older compiler risks unrelated codegen drift. Source only..elhheaders are stale vs the.elsources (regen addsbounded_persona_floor,affective_context_prefix,run_command_guard,classify_tool_risk; removes some fromsafety.elh). Left untouched so this PR is one change — worth a separate resync.Note this branch is based on
hotfix/parallel-tool-stopgap-20260803, so it carries that commit too (the stopgap is kept intact, per ADR 0005).Artifacts, full proof transcript, and the build recipe:
_engine-websearch-20260804/README.md.⚠️ A separate safety finding is in
_engine-websearch-20260804/SAFETY-STOP.md— the dead plain-chat route was not wired; doing so would remove the only enforcing output gate in the codebase. Not part of this PR.🤖 Generated with Claude Code
The agentic loop keeps only the FIRST tool_use block per round (chat.el:2281, "Capture first tool_use block only"). Anthropic lets a model emit several tool_use blocks in one message and requires a tool_result for every one, so a parallel-tool turn is answered once, the rest are dropped, and the next request dies with: tool_use ids were found without tool_result blocks immediately after (neuron#78 quotes this as "tool_use ids found without tool_result"; the above is the API's actual wording - recorded so the next person's grep matches.) This constrains the wire to match what the loop can assemble: "tool_choice":{"type":"auto","disable_parallel_tool_use":true} STOPGAP - AND THE DURABLE FIX ALREADY EXISTS. A correct multi-tool loop is already in Will's EL runtime, in C, and the soul does not call it. Verified on el:origin/main lang/el-compiler/runtime/el_runtime.c: llm_register_tool:9616, llm_build_tool_results:9743 - which walks EVERY content block, emits one tool_result per tool_use, and sets is_error for an unregistered tool - llm_call_agentic:9817 calling it at :9918, iteration cap 10 at :9847. Will's commit 12d5e77 (2026-04-30). grep for llm_call_agentic/llm_register_tool across every neuron/*.el returns nothing; dist/soul.c has zero references. chat.el hand-rolls its own single-tool loop instead, and that is the one that breaks. The durable fix is therefore to register the soul's tools via llm_register_tool and call llm_call_agentic - deleting a loop, not writing one. See ADR 0005. Our own approved spec called this seven weeks ago: docs/research/agentic-tool-approval-design.md (2026-06-12, "Approved for build"), line 20 on the defect, line 30 on the goal ("Execute all tool_use blocks in a turn (one result per block)"). Two edits, because dist/soul.c cannot be regenerated here (Will's gated elc/elb toolchain is not on this machine): (a) chat.el:2255 - source of truth, so a later regen carries the fix. One edit covers all three routes: agentic_loop is called from chat.el:2152 (/api/chat agentic), :2676 (dharma room) and :2496 (agentic_resume). (b) dist/soul.c:28173 - generated form, hand-spliced. Line 27624 is the non-agentic/OpenAI-compat req_body (no tools) and was left untouched. Prior art reused rather than reinvented: soul-narrated-runs-20260713.patch (27,824 bytes) line 78 spliced this same string into the same concat chain on 2026-07-13. Deliberately NOT ported from that patch, having read it: max_tokens is not changed by it (16384 sits on both sides of the hunk; our main's 4096 is a separate output-truncation concern), and its pause_turn pairing fix - same defect class - is unreachable today because no server-side web_search is wired (agentic_tools_with_web at chat.el:1418 is never called), so it is untestable and logged instead. Proven E2E on a scratch profile and port 7791, never the live chain. A/B against a pristine origin/main control built from the same vendored runtime: fixed completed the mission (tools_used read_file x3, 4 iterations, correct answer); control failed 3/3. Direct API probe confirmed the mechanism - without the field the model emits 3 parallel tool_use blocks and replaying the unfixed loop's next turn returns HTTP 400; with it, exactly 1 block. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>Superseded by #109 — verified by ancestry: #109's head
635f6fecontains this branch's head62af564, so merging #109 lands the web_search port and plain-chat generation together. Please review #109.Pull request closed