Repository navigation
feat(tools): add web_search, web_fetch, and run_command tools (ENG-382) - #5
Merged
folathecoder merged 2 commits intoOct 18, 2026
Conversation
added 2 commits
October 18, 2026 10:22
Add SearchProvider, TavilyProvider, and WebSearchTool, WebFetchTool with SSRF and redirect validation, 2 MB stream cap, and <untrusted> wrapping, and RunCommandTool with pre-approval denylist, secret env scrubbing, process-group timeout, and 4k/6k output truncation. Wire builtin_tools() into build_agent() and map ToolOutput card summaries into ToolResultEvent.
folathecoder
deleted the
folathecoder/eng-382-6-built-in-tools-web-search-web-fetch-and-terminal
branch
October 18, 2026 09:38
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.
Summary
Adds Aldwin's built-in web and terminal tools (
web_search,web_fetch, andrun_command), registers the fixed six-tool set for 0.0.1 throughbuiltin_tools(), and maps one-line tool card summaries intoToolResultEventwhile preserving full tool outputs in session memory. Web fetches block localhost and private IPs across redirects, cap downloads at 2 MB, and wrap extracted text in<untrusted>tags; terminal commands refusesudoand interactive programs before prompting, require explicity/napproval viaask(), scrub secret environment variables, and run in their own process group with a 60-second timeout.Ticket
Changes
src/aldwin/core/tools/__init__.py,src/aldwin/core/agent.py,src/aldwin/host/runner.py):ToolOutput(str)carrying.summaryand.okacrossstr(),+, andcopy.deepcopy.summarize_tool_result(),builtin_tools(), andlist_builtin_tools()(re-exported asget_builtin_tools()inaldwin.hostandaldwin.clientfor/tools).builtin_tools()as the sole default tool registry inbuild_agent()and updatedRememberToolandSetPersonaToolto returnToolOutput(..., ok=False)on validation errors or user decline.src/aldwin/core/tools/web_search.py):SearchResult,SearchProviderprotocol,TavilyProvider(httpx.AsyncClientagainstPOST https://api.tavily.com/searchwith API key redaction), andWebSearchTool(1..400char query,1..10limit, 15 s timeout, and missing-key setup message).src/aldwin/core/tools/web_fetch.py):validate_public_url()andis_public_ip()blocking non-http/httpsschemes,localhost/.local/.internalhosts, and loopback, RFC 1918, link-local (169.254.0.0/16,fe80::/10), CGNAT, and IPv4-mapped private IPv6 addresses.trafilatura+HTMLParserfallback extraction withContent-Typecharset decoding, 20,000-char truncation, and<untrusted source="...">wrapping with closing-tag escaping.src/aldwin/core/tools/run_command.py):check_command_allowed()rejecting empty or> 2,000-char commands,sudo/su/doas, interactive programs (vim,less,top,ssh, etc.), and bare REPLs/shells across pipelines, compound operators,$(...)/backtick subshells, andsh -cscripts before callingask().scrub_env()stripping*_KEY,*_TOKEN,*_SECRET, and*PASSWORD*variables and settingTERM=dumb,PAGER=cat,GIT_PAGER=cat.RunCommandToolwithstart_new_session=True, 60 s process-group timeout and< 1 sEsccancellation (SIGTERMthenSIGKILL+proc.wait()), and 4,000-head / 6,000-tail truncation at 10,000 chars.How it was tested
uv run pytest --cov=aldwin --cov-report=term-missing --cov-fail-under=90: 169 passed, 94.32% total coverage (tools/__init__.py98%,memory_tools.py100%,persona_tool.py100%,web_search.py99%,web_fetch.py92%,run_command.py92%).uv run ruff check src testsanduv run ruff format --check src tests: clean.uv run mypy src tests: 0 errors across 50 source files.web_fetch("https://docs.python.org/3/")(M-02): returned1k charswrapped in<untrusted source="https://docs.python.org/3/">.run_command("df -h | head -n 5 && env")(M-03): returnedexit 0with disk usage and verifiedOPENAI_API_KEYwas stripped from the child environment.Notes for reviewers
httpcorenetwork backend is deferred past 0.0.1 because rewriting the request host to a literal IP breaks TLS SNI and certificate hostname verification without custom stream overrides; per-hopgetaddrinfovalidation is enforced on the initial request and every redirect hop.