Recipe 01 — Two hops with requests
Goal
Answer a question about a documentation site using only its llms.txt and one page, with
nothing but requests. This is the reading model spec v2 describes — “view or search the
index, then follow the relevant links” — and it is the floor for cost: if a question can be
answered this way, no retrieval layer beats it.
When not to use it
- The question is an exact token (an env var, a flag, a header). Descriptions rarely contain them; use the keyword layer (recipe-03).
- The index has a
## Sectionsblock instead of page links. That is a split root; add the section hop (recipe-02). - You need more than about two pages. Past that, read
llms-small.txtor query the facts layer instead of hopping.
Steps
- GET
/llms.txt. Parse the link lines with the same regex the lint uses (LINK_REinllms_lint.py), so anything that lints as a link parses as one here. - Score each line’s name and notes against the question’s tokens; take the best.
- GET that URL with
.mdappended — after stripping any trailing slash, because a v2 twin is/reference/usage.md, not/reference/usage/.md. - Hand the page to whatever answers — a model, a grep, a human.
import re, requests
LINK_RE = re.compile(r"^\s*[-*]\s+\[([^\]]*)\]\(([^)\s]+)\)\s*(?::\s*(.*))?$")
def two_hop(root: str, question: str) -> tuple[str, str, str]:
index = requests.get(f"{root}/llms.txt", timeout=10).text
links = [m.groups() for m in map(LINK_RE.match, index.splitlines()) if m]
q = set(re.findall(r"\w+", question.lower()))
name, url, notes = max(
links,
key=lambda l: len(q & set(re.findall(r"\w+", f"{l[0]} {l[2] or ''}".lower()))),
)
if not url.startswith("http"):
url = f"{root}/{url.lstrip('/')}"
twin = url if url.endswith(".md") else url.rstrip("/") + ".md"
page = requests.get(twin, headers={"Accept": "text/markdown"}, timeout=10).text
return url, twin, page
url, twin, page = two_hop(
"https://llms-explorer.pages.dev",
"how do I serve a markdown twin with the right headers?",
)
print(url, twin)
The scorer is deliberately naive — a bag-of-words overlap. It is enough when the index’s descriptions are extractive (the lint’s D-attributes exist to make them so), and it fails loudly when they are not, which is a finding about the file rather than the code.
Expected output
The two URLs and the page text. Against this site’s own index the question above scores
/examples/recipe-09/ highest — its title and description share more tokens with the question
than any other line — and the twin is that route with the slash traded for .md:
https://llms-explorer.pages.dev/examples/recipe-09/ https://llms-explorer.pages.dev/examples/recipe-09.md
The page comes back as markdown, not HTML: frontmatter, headings the facts file anchors to,
a few thousand characters. If the second request returns HTML, the site has no twins and the
Accept header was ignored — a serving finding (N6 for a dead target, H2 for the wrong
content type), not a parsing one.
Pointed at a split root the same code lands on a section index rather than a page —
code.claude.com’s root is ## Sections with overview/llms.txt under it, so two_hop
returns the section file and you need the extra hop of
recipe-02.
Cost
Estimated: about 3.5k tokens — this site’s index is 9,325 bytes (~2.3k tokens at chars/4; the rubric’s bar is 10 KB) plus one page (~1.1k tokens for the twin above). Two HTTP requests, zero embeddings, zero model tokens until you hand the page to something.
Runnable in step 4 (playground).