Feat/issue 42 agent first web proxy - #340
Open
rizzoMartin wants to merge 3 commits into
Open
Conversation
…ent page extraction (ARPAHLS#42) Raw web HTML is mostly scripts, styling, navigation and boilerplate. Loading it into a context window wastes tokens and dilutes model attention. This skill sits in front of a page and returns only its semantic core. Fetches a public http(s) URL behind an SSRF guard, or accepts pre-fetched HTML, and returns concentrated Markdown, plain text, or JSON via trafilatura together with an estimated token saving. Design notes: - Uses requests rather than httpx. requests is already a core dependency and is filtered out of the generated extras, and it matches the existing URL skills. - The SSRF guard follows security/deceptive_ui_guard, but follows redirects manually and re-validates every hop. Validating only the initial URL lets a 302 reach link-local metadata addresses. - Token counting defaults to a four-characters-per-token heuristic, matching optimization/prompt_rewriter. tiktoken downloads its vocabulary on first use, so cl100k_base is an opt-in extra guarded by find_spec and degrades to the heuristic with a warning rather than failing. - Savings are reported as a reduction percentage, plus a share of the caller's context window when context_window is supplied. No default window is assumed. - focus_element is replaced by include_comments, include_tables and include_links, which map directly onto trafilatura options. - No headless render lane. A page that looks client-rendered is reported with page_likely_requires_javascript rather than a silently empty payload. fetch_html is the only function that touches the network, so the whole test suite runs offline against fixture HTML. Fixes ARPAHLS#42
… bundle commit (ARPAHLS#42) The history row must reference a commit reachable from the branch. Refs ARPAHLS#42
…ttributes (ARPAHLS#42) CodeQL py/bad-tag-filter flagged the script-block regex in semantic_web_proxy: `</script\s*>` does not match `</script\t\n bar>`. HTML parsers ignore attributes on end tags, so `</script foo>` genuinely closes a script element. Because the regex missed those forms, findall returned no match and the script bulk of such a page was measured as zero, so a client-rendered shell was reported as a normal page instead of raising page_likely_requires_javascript. Both end-tag patterns now accept ignored attributes via `</tag\b[^>]*>`. The word boundary keeps `</scriptfoo>` from counting as a close. The regexes measure script volume for a warning heuristic and are not used to sanitize or filter untrusted HTML, so this is a correctness defect in the detector rather than an exploitable filter bypass. Fixed regardless, since the detector is wrong on pages that use these forms. Adds four regression tests covering attribute-bearing end tags, a trailing space before the bracket, the same form on the app-root container, and the negative case. Refs ARPAHLS#42
Contributor
|
Thanks @rizzoMartin, excellent work on #42. Fetch/extract split, SSRF guard with redirect re-validation, JS-shell honesty, fixture corpus, and firewall chaining section are top tier reasoning man. Tests and catalog quality are strong (59 bundle tests + integration, and registry/card/smoke pass on my side as well. One blocker before merge:
Otherwise good to merge from here. <3 |
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.
Description
Adds
data_engineering/semantic_web_proxy, a deterministic proxy that reduces a web page to its semantic core before it reaches a context window.Raw HTML is mostly not content. Scripts, styling, navigation, consent banners and footer link farms dominate a typical page, and every one of those bytes costs tokens and dilutes model attention. The skill fetches a public http(s) URL behind an SSRF guard (or accepts HTML the host already holds), strips everything non-semantic with trafilatura, and returns concentrated Markdown, plain text, or JSON plus an estimated token saving.
Measured on a live page:
https://en.wikipedia.org/wiki/Markdowngoes from roughly 77.8k to 5.0k estimated tokens, a 93.5 percent reduction. The bundled fixture corpus, which uses small synthetic pages with far less script weight, lands at 65 to 80 percent. Both figures are documented on the catalog page rather than only the flattering one.Acceptance criteria mapping
skills/data_engineering/semantic_web_proxy/proxy.py(extract_semantic);test_skill.py::TestExtractSemanticskill.py::_token_savings;test_skill.py::TestTokenSavingsproxy.py::fetch_html; no Playwright dependency addedproxy.py::looks_like_js_shell;test_skill.py::TestLooksLikeJsShell,TestEmptyJavascriptShell; fixturetests/fixtures/semantic_web_proxy/js_shell.htmlfocus_elementstring with flags mapped to trafilaturamanifest.yamlparametersinclude_comments,include_tables,include_links;test_skill.py::TestExtractSemantic::test_comments_*token_savings.reduction_pctalways,context_saved_pctwhen the caller passescontext_window;test_skill.py::TestTokenSavingsproxy.py::is_safe_public_url;test_skill.py::TestIsSafePublicUrl,TestFetchHtmlfetch_htmlis the only network-touching function; the whole suite is offline (tests/fixtures/semantic_web_proxy/*.html)Deviations from the issue and thread, for review
Four deliberate departures. Each is easy to reverse if you would rather I follow the original text.
requestsinstead ofhttpx.requestsis already a core dependency, is filtered out of the generated extras byextras.py::CORE_DEPENDENCIES, and matchessecurity/deceptive_ui_guardandcompliance/tos_evaluator. Using httpx would have added a new package to[all], which CI installs.Token counting defaults to a heuristic, not
cl100k_base. tiktoken downloads its vocabulary over the network on first use, which collides with the repository rule that bundle tests mock model downloads and CI does not download models, since[all]is installed in CI. The default is the same four-characters-per-token basisoptimization/prompt_rewriteruses.cl100k_baseis available through a hand-maintained opt-in extra,data_engineering_semantic_web_proxy_tokenizer, guarded withimportlib.util.find_specthe waydeceptive_ui_guardguards Playwright. When tiktoken is absent the skill falls back and emitstokenizer_unavailablerather than failing.Output shape differs from the issue example. The issue specifies a flat
{"status", "token_count_saved", "semantic_payload"}.statusandsemantic_payloadmatch, buttoken_count_savedbecametoken_savings.tokens_savedinside an object that also carriesoriginal_tokens,semantic_tokens,reduction_pct,context_saved_pct,tokenizerandestimate. Reporting the saving as a percentage, as requested in the thread, needed more than one scalar. Flagging it because the issue's Ideal Inputs and Outputs is part of the written spec.Redirects are followed manually and revalidated on every hop.
fetch_url_htmlinguard.pypairs a pre-flight host check withallow_redirects=True, so a 302 into169.254.169.254is not caught. This skill usesallow_redirects=Falsewith a bounded hop loop that reruns the guard each time. I did not touchguard.py, since that is outside this issue, butsecurity/deceptive_ui_guardprobably has the same gap and may deserve its own issue.Note on scope
Beyond the skill bundle and its catalog and index entries, this PR adds one row to the curated list in
tests/test_examples_smoke.pyso the new offline demo is covered by CI, in the spirit of #237. Happy to drop it if you would rather keep the diff to the bundle.Verification
Run locally against a clean tree:
python -m black --check .— 198 files unchangedpython -m flake8 .— 0, both the critical selection and the full runpytest skills/— 365 passed, 1 skippedpytest tests/— 384 passed, 4 skippedpython scripts/sync_extras.py --check— in syncpytest tests/test_skill_issuer.py tests/test_registry_identity.py tests/test_registry_docs.py tests/test_card_ui_schema.py tests/test_extras_sync.py— 41 passedskillware test data_engineering/semantic_web_proxy— 55 passedscripts/wheel_smoke_test.pyexits 0 and reports the correctpip install "skillware[data_engineering_semantic_web_proxy]"hintLocal interpreter is Python 3.14. I did not run the suite on the 3.10, 3.11 and 3.12 matrix, but all new modules parse against the 3.10 grammar and
trafilatura>=2.0.0declaresrequires-python >=3.10with wheels resolving cleanly for 3.10. CI is authoritative here.news.ycombinator.comis unreachable from my environment, so the Hacker News thread case from the issue is covered by thethread_with_comments.htmlfixture rather than verified live.Type of Change
skills/skills/skillware/core/loader, env, adaptersskillware/cli.py,docs/usage/cli.mdexamples/*.py, agent loops,examples/README.mdpyproject.toml,MANIFEST.inChecklist (all PRs)
Fixes #…orRefs #…)python -m black --check .andflake8pass locally (or CI-equivalent subset)pytest skills/andpytest tests/pass locally when relevantCHANGELOG.mdupdated under[Unreleased]when user-visible behavior changesexamples/README.mdupdated if this PR adds, renames, or removes a runnable scriptpytest tests/test_registry_docs.pywhen skills, examples index, or agent-loops matrix changedNew or updated skill
Bundle and metadata
skills/<category>/<skill_name>/(fromtemplates/python_skill/or equivalent)manifest.yaml:name(full ID),version,description,parameters,constitution, realissuershort_description,issuer.github,issuer.org,requirements,env_varsenv_varsis intentionally absent: the skill needs no API key and no configuration.Effect, Directive, Assurance
skill.py(Effect; no ad-hoc LLM-generated execution paths)instructions.md(Directive) explains when and how to use the skillcard.json(Presentation) issuer matches manifest when presenttest_skill.py(Assurance) covers execution and schema expectationsSkillLoader.load_skill("<category>/<skill_name>")succeeds (or deps documented)55 bundle tests plus 9 maintainer-layer tests in
tests/skills/data_engineering/test_semantic_web_proxy.py, driven over four fixture pages. Every test is offline:fetch_htmlis patched on the effect module, or the pure extraction path is exercised directly. An output fixture lives attests/fixtures/card_ui_schema/data_engineering__semantic_web_proxy.json, generated from realexecute()output so everyui_schema.fields[].keyresolves.Documentation and catalog
docs/skills/<skill_name>.mdand row indocs/skills/README.mdAlso added: a row in the
docs/usage/agent_loops.mdmatrix, a row inexamples/README.md, and a chaining example withsecurity/prompt_injection_firewallthat I ran end to end before documenting it.Constitution and safety (skills only)
Read-only by construction. The skill issues HTTP GET requests and parses the response; it never submits forms, authenticates, or follows non-content actions. No LLM is called inside the skill, so identical HTML and options always produce an identical payload.
Network safety: only public http and https URLs are accepted. Non-http schemes such as
file://are rejected, along with loopback,.local, private, link-local, reserved and multicast addresses, resolved throughsocket.getaddrinfobefore any request is issued. Redirects are followed manually, capped at five hops, and the guard reruns on every hop so a redirect cannot walk into a metadata endpoint. Responses are capped at 2 MB and must carry an HTML-like content type.Output safety:
semantic_payloadis attacker-influenced third-party text and may carry prompt injection aimed at the calling agent. The constitution andinstructions.mdboth direct hosts to treat it as data and to pass it throughsecurity/prompt_injection_firewallbefore it enters a context window, which is the text-channel half of the defense chain indocs/security/skill-trust-model.md.Honest limits:
token_savings.estimateis alwaystrue, and the catalog page states the counts are indicative for budgeting rather than billing figures. JavaScript is never executed, and a page that appears client-rendered is reported withpage_likely_requires_javascriptinstead of a silently empty payload.Permission is deliberately out of scope: this skill does not read robots.txt. The catalog page points at
compliance/tos_evaluatoras the pre-fetch partner when a site's terms are in question.Related Issues
Fixes #42