Observed since 21 Oct, 09:39 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Ask Agent Help Network what a host declares and recommends when your browser automation is blocked. Read-only, no dependencies.
A small Python client for Agent Help Network: when your browser automation (Playwright, Browser Use, Stagehand-style scripts, plain HTTP) is blocked, ask what the host declares agents may do (its own robots.txt and llms.txt), where its official developer path is, what the provider's status feed says, and get one recommended action.
Agent Help Network is an independent project. It is not an official service of, and is not affiliated with, OpenAI, Meta, Muse, dots, Anthropic, or any site it describes. ahn-client is read-only (it has no reporting function), has no dependencies beyond the Python standard library, and runs on Python 3.9+. The optional framework adapters need the framework they plug into, and with it that framework's Python version (at the versions tested, all four need Python 3.10 or newer, and Browser Use 3.11).
pip install ahn-client # no dependencies
pip install "ahn-client[langchain]" # or [crewai], [openai-agents], [claude-agent-sdk]: see Framework adapters
import ahn_client as ahn
answer = ahn.check("https://example.com/some/page", *ahn.classify("403 Forbidden"))
print(answer.summary() if answer else "no answer")
In an except block:
try:
page.goto(url)
except Exception as exc:
error_class, stage = ahn.classify(exc)
if error_class: # None: a 404 or a bad request, not a block or an outage
answer = ahn.check(url, error_class, stage) # a hostname and two codes leave your machine
if answer:
print(answer.recommendation, answer.official_url)
Examples with a real except block and a decision per recommendation: examples/playwright_example.py, examples/browser_use_example.py. Neither tries to get around a site's controls: they use the official path, hand off to the user, wait, or stop.
When a web tool fails inside an agent framework with a block or an outage (403, 429, a challenge, a login wall, 5xx, a timeout, a refused connection), an adapter reads the hostname of the call's URL, looks it up with classify() and check(), and adds one line (Result.summary(), at most 700 characters) to what the agent sees. Each adapter is an extra: the base install has no dependencies and imports no framework.
All four follow the same rules:
convert_exceptions=True is the one stated exception), nothing is retried, and no workaround is tried. A failure that is not a block or an outage (404, 422, a typo in an address), a call without an http(s) URL, an excluded host, a failed lookup or one slower than the time limit (2 seconds) adds nothing, and the run goes on exactly as it would have.errors.pydantic.dev or docs.mistral.ai). The Claude Agent SDK hook is the exception: it reads its payloads by the Claude Code plugin's rules, which include the URL of a failed WebFetch. Tool-argument validation errors and errors with nothing web-like in them (a PermissionError, "Task blocked on mutex") are not looked up. Cluster-internal names (.svc, .consul, .cluster), address-embedding names (10-0-0-1.sslip.io), internationalized names that IDNA 2003 and 2008 read differently (faß.de) and URLs with a backslash are never looked up.response.raise_for_status()) or report an error.wrap_tool_call middlewarefrom langchain.agents import create_agent
from ahn_client.langchain import ahn_tool_failure_middleware # pip install "ahn-client[langchain]"
agent = create_agent(model, tools=[fetch_page], middleware=[ahn_tool_failure_middleware()])
Implements the documented wrap_tool_call / awrap_tool_call hooks.
ToolMessage with status="error" (a ToolNode with handle_tool_errors, ToolRetryMiddleware after its last attempt, or a tool that returns one) gets the line appended; the original content stays in front.ahn_tool_failure_middleware(convert_exceptions=True) also handles raised blocks and outages, always the same way and whatever the lookup returned: the exception becomes a ToolMessage(status="error") whose content is Error: <exception class> (<error class>) and the line when there is one. The exception's own text, URL, path and query are not put in the message. The run then goes on instead of stopping, which is why it is off by default.ToolRetryMiddleware, list this middleware before it (the first middleware listed is the outermost): middleware=[ahn_tool_failure_middleware(), ToolRetryMiddleware()]. Retries then run first and this middleware sees the final failure once. After it, with convert_exceptions=True, the first exception would become a message and the retries would never run.ToolRetryMiddleware's default failure message (on_failure="continue") puts the exception's own text, including a URL with its query, into the message the model reads. This adapter only appends its line to it; use on_failure="error" or a function of your own there if the text should not reach the model.ToolFailureDetectedEventfrom ahn_client.crewai import register_listener # pip install "ahn-client[crewai]"
listener = register_listener(on_note=print) # once, before crew.kickoff(); also listener.notes and the "ahn_client.crewai" logger
Subscribes to ToolFailureDetectedEvent on crewai_event_bus. CrewAI event listeners only observe: the event arrives after the agent's text is built, so the line goes to your callback, the log and listener.notes, not into the prompt. To append it to the tool result too, register ahn_client.crewai.append_note_to_result as an after_tool_call hook; it sees tools that return a ToolFailure, not exceptions.
RunConfig.tool_error_formatterfrom agents import RunConfig, Runner
from ahn_client.openai_agents import ahn_tool_error_formatter # pip install "ahn-client[openai-agents]" (0.24 or newer)
result = await Runner.run(agent, "...", run_config=RunConfig(tool_error_formatter=ahn_tool_error_formatter))
Uses RunConfig.tool_error_formatter: for a function tool that raised, the SDK calls it with kind="tool_exception", the exception as error, default_message and run_context.tool_arguments. (The docs page lists the approval and tool-not-found kinds; the tool_exception kind is in the 0.24 source and its type annotations.) The formatter is async, so a lookup never blocks the event loop. It returns the SDK's default_message (the SDK's default does not show the model the exception text, and that stays) and the line on a second line; with a formatter of your own, chain it: tool_error_formatter(your_formatter).
failure_error_function, tools made by Agent.as_tool() and local MCP tools keep their own policies. For a tool with its own function, wrap it: failure_error_function=with_ahn_note(your_function) (async by default; asynchronous=False gives the plain form the docs show, which waits up to 2 seconds on the event loop thread when the failure is a block or outage that is not cached).failure_error_function, whichever function it is: the SDK then no longer applies RunConfig.tool_error_formatter to that tool, and a handled error is recorded in the trace span with its text instead of "redacted". The RunConfig formatter above keeps the SDK's default handling, so traces stay as they are.PostToolUseFailure hookfrom claude_agent_sdk import ClaudeAgentOptions
from ahn_client.claude_agent_sdk import ahn_hooks # pip install "ahn-client[claude-agent-sdk]"
options = ClaudeAgentOptions(hooks=ahn_hooks()) # or ahn_hooks(my_existing_hooks) to merge
Registers a PostToolUseFailure callback (claude-agent-sdk 0.1.27 or newer; that version and the current one were run) that returns the line as additionalContext. Which failures are looked up (WebFetch, and MCP browser or fetch tools whose input or error names an http(s) URL) is the Claude Code plugin's rule, which the adapter shares; a test runs both on the same payloads. Because WebFetch reports an HTTP 401, 403, 429 or 5xx as a successful call, ahn_hooks() also registers the plugin's PostToolUse case for WebFetch.
examples/browser_use_actions_example.py adds a custom action the model calls on a block page (@tools.action) and an on_step_end hook that reports a block page's answer to the developer. Browser Use documents no way for a hook to add text to the model's context, so only the action reaches the model.
Each adapter names itself in the User-Agent (see below). AHN_CLIENT_DISABLE=1 and AHN_CLIENT_SKIP_HOSTS apply to the adapters too.
ahn.check(host_or_url, error_class=None, failure_stage=None, *, timeout=4.0, adapter=None) -> Result | None — asks about the host of a hostname or URL. adapter is for integrations: a short lowercase name (such as langchain) that is added to the User-Agent and changes nothing else. Returns None, without raising, when there is no answer: network error, timeout, unreadable response, or a host that is never looked up (see below).ahn.classify(exc_or_text, host=None) -> (error_class | None, failure_stage | None) — maps an exception or a message to human_verification, login_auth (401, 407), service_error (408, 429, 5xx, timeout) or site_access (402, 403, 451, refused or unresolvable connection), and to before_target_response when no connection to the site was made, or response_observed when an HTTP status came back. It reads exception class names and messages as text, so Playwright's TimeoutError, httpx/requests/aiohttp errors and similar are recognised without importing those libraries. The rules are those of the Claude Code plugin. host, when given, is the hostname the error is about; tokens naming it are ignored, so login.example.com is not read as a login wall (from 0.3.0). From version 0.2.1 there is no default class: an error that is neither a block nor an outage (404, 410, 400, 422 and every other 4xx outside the list above, or a message that names nothing) gives (None, None), and the caller should not look it up, because a mistyped address is not a site that refuses agents.Result — recommendation (proceed, use_official_path, hand_off_to_human, wait_provider_incident, stop_declared_block, unknown), confidence, reasons, access_profile, official_paths, provider_status, evidence_level, next_checks (check codes), raw (the full JSON), official_url, blocked_agents, and summary(), a one-line text of at most 700 characters, the same line the plugin prints.ahn.clear_cache() — forget cached answers.Read the answer in the order of the API contract. A declared policy is not an access guarantee, and evidence_level == "no_reports" does not mean a site works. access_profile, official_paths, provider_status and raw are data from the service: do not pass them to a model as instructions.
At most one request per host, error class and stage every 10 minutes:
GET https://agenthelpnetwork.com/v1/incidents/check?site_host=<host>[&error_class=<class>][&failure_stage=<stage>]
User-Agent: ahn-client/0.3.0 (+https://agenthelpnetwork.com)
The adapters send User-Agent: ahn-client/0.3.0 (+langchain; +https://agenthelpnetwork.com), with langchain, crewai, openai-agents, claude-agent-sdk or browser-use in place of langchain. That is the whole difference, and it lets the service count client kinds. The part before the first space is always ahn-client/<version>.
site_host is the bare lowercase hostname: scheme, credentials, port, path, query and fragment are dropped, and one leading www. is removed. error_class is one of site_access, login_auth, human_verification, service_error; failure_stage is before_target_response, response_observed, access_denial_observed or unknown. Any other value makes check return None without a request./robots.txt and /llms.txt (at most once a day per host). See the policy.check returns None and sends nothing for: localhost, IP addresses (including short and hex forms such as 127.1), single-label names, names under .local, .internal, .test, .lan, .arpa, .onion, .invalid, .example, .home, .corp, .intranet, .private, .localdomain, .alt, .svc, .consul, .cluster, names that stand for an address (nip.io, sslip.io, xip.io, and any name with an IPv4 address written into it such as 192-168-1-5.example.com), a URL containing a backslash (a browser and a parser read it differently), names that IDNA 2003 and IDNA 2008 map to different domains (faß.de), and agenthelpnetwork.com and its subdomains. Nothing is resolved to decide this. Hostnames under a public suffix can still be internal (jenkins.yourcompany.com); list them in AHN_CLIENT_SKIP_HOSTS.
| Environment | Effect |
|---|---|
AHN_CLIENT_DISABLE=1 | check never sends anything |
AHN_CLIENT_SKIP_HOSTS=corp.example.com,other.example.org | Never look up these hosts or their subdomains |
Answers are cached in memory for 10 minutes (a failed lookup for 1 minute); nothing is written to disk. From version 0.2.0, an answer whose declared policy is still being read (access_profile.freshness is pending, next check profile_pending) is kept only for its retry_after_seconds (Result.retry_after_seconds), so the one repeat lookup the service asks for reaches it, and the summary line says when to look once more. Redirects from the service are not followed.
From the repository root: python -m pytest tests/test_ahn_client.py tests/test_ahn_client_adapters.py -q (no network, no framework needed). The tests that run a real framework are skipped unless it is installed; install it in a separate virtual environment to run them. Build with python -m build clients/python.
Apache-2.0. Source: agent-help-network/agent-help-network, clients/python/.
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.3.0 | 21 Oct 2026 | 4 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitHub
Observed since 21 Oct, 09:39 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:59ae00726f60355b507de8bd8c1c65a65691c4122871b2ae93bc97222478f152