making the site readable to agents
companion piece to this site. the case study is the ten-second skim. this is the map of every machine-readable door, why each one exists, and which scanners we are designing against.
the setup
a hiring manager still spends ten seconds on a portfolio. a hiring agent does not. it fetches a URL, follows a link header, asks for markdown, or opens an MCP session. if the only honest representation of the work is a client-rendered page, the agent invents a worse one from GitHub READMEs and third-party profiles.
that is no longer hypothetical. Ora ranks agent experience across the public web. in august 2026 they partnered with Vercel on is-agentic.com — a free scan whose technical checks are Ora's, grouped into essentials an ordinary site needs and recommended checks that only fire when you actually ship an API, an MCP server, or a developer portal. the methodology is reverse-engineered from real agent runs, not a style guide.
i did not paste a score into this post. a rank without a current scan is the same class of lie as a hardcoded endpoint count, and we already burned that one. the scanners are the bar. the rest of this is what the site actually serves when something knocks.
what ships
these are live routes, not a backlog. each one is generated from the same postgres read-model as the human pages, so a stale job title cannot be "fixed" on /experience while /resume.md and the MCP tool still say intern.
| URL | what an agent gets |
|---|---|
| /llms.txt | curated index. first non-comment line is a blockquote elevator pitch read from the profile, not a second bio. advertised on every HTML page as rel="describedby" (llmstxt.org v2) |
| Accept: text/markdown on the same URL | markdown twin. browsers still get HTML. Vary: Accept on the markdown response so a CDN cannot mix the two |
| /{page}.md, /projects/{slug}.md, /writing/{slug}.md | explicit twins for clients that append .md instead of negotiating |
| /agent.md | compact markdown index |
| /openapi.json + /reference | live OpenAPI 3.1, Scalar UI. the homepage hero is the one numeric mention, linked here, counted from the spec |
| /mcp | Streamable HTTP MCP. read-only catalog tools. card at /.well-known/mcp.json |
| /.well-known/agent-card.json | A2A card: MCP, OpenAPI, llms.txt, and markdown bindings |
| /resume.md, /resume.pdf, /resume.yaml | the same events table. student apps are on /experience and omitted here |
| /robots.txt | named allowlist for retrieval and training crawlers, plus User-agent: * allow |
| a markdown 404 | unknown paths return a short body that points at /llms.txt, the sitemap, and /mcp instead of the app shell |
there is also npx bokendell — a convenience wrapper over the same public data. agents should prefer MCP or REST; the CLI is for humans who want JSON in a pipe.
the rules that actually mattered
one number, from the spec
the site used to claim two different OpenAPI counts depending on which page you were on. an agent that fetches /llms.txt and then /openapi.json will notice. /llms.txt now counts operations from the live spec and omits the claim if the fetch fails. the homepage hero is the only place a human sees the digit, and it links to /reference.
that is the whole "agent SEO" job in miniature: do not write a fact the machine can check and then get it wrong.
negotiate; do not fork the site
Accept: text/markdown at https://bokendell.com/ returns markdown. Accept: text/html returns HTML. is-agentic's own reports do the same thing, which is how you know the pattern is load-bearing and not a party trick.
the implementation is middleware plus a pure helper (packages/agent-content/negotiation.ts) so the Accept math is unit-tested without an edge runtime. adding a page without registering it in isKnownPath() is how you 404 a real URL for agents while browsers still work — or the reverse. that file is the lockstep list.
404s that recover
an agent that guesses /AGENTS.md or /llms.md should not get a react tree. the markdown 404 names the miss and lists where to look next. agents.md is an alias because one agent guessed the plural.
robots is an allow-list, on purpose
default is allow. then an explicit list of retrieval bots (ChatGPT-User, Claude-User, Perplexity-User, …) and training bots (GPTBot, ClaudeBot, Google-Extended, …). visibility beats scraping paranoia on a public portfolio. phone stays off the profile row for the opposite reason: every surface that renders it is crawled.
MCP is a catalog, not a product
tools are reads: projects, posts, experience, resume, activity. zero write tools. the A2A card's securitySchemes is empty. if an agent can mutate the site, the site is no longer a portfolio.
what we are not doing in this post
hive has a writeup; it does not live in this repo's content/projects/. bagman and swarm already have internal-framed case studies. the long versions of those — what i actually learned shipping a voice caddy, a monorepo CLI, a personal OS — wait until those products are something a stranger can use. filling /writing with unlaunched essays is the same honesty bug as a padded GitHub heatmap.
this post is the one that is true today: the agent surface is shipped, it is the differentiator on this site, and the scanners at is-agentic.com and ora.ai are how we will know if it is any good.
how to poke it
B=https://bokendell.com
curl -s "$B/llms.txt" | head
curl -sI -H 'Accept: text/html' "$B/resume" | grep -i link
curl -s -o /dev/null -w '%{content_type}\n' -H 'Accept: text/markdown' "$B/"
curl -s -o /dev/null -w '%{http_code}\n' "$B/.well-known/agent-card.json"
curl -s -X POST "$B/mcp" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'
a browser Accept header must still get HTML. if negotiation starts serving markdown to people, the scanners are the least of the problem.
after the next production deploy i will run the public is-agentic scan against bokendell.com and put the report URL here if the number is one i am willing to sign.