For people and agents
A docs site with a sidebar, search and an API console for your readers. A version-locked index for their coding agents. Most docs tools build one and bolt on the other.
docs for people and AI agents one source a fraction of the tokens
docspack builds your guides, API reference and CLI reference from the files you already keep. People read a docs site. Agents query a local index that returns only the passages they need, matched to the version you installed: 20× fewer tokens than reading the page.
one source
The Markdown, API description and CLI spec a project already writes, here docspack's own. Parsed once, then emitted as both of these.
for people · published
docspack.dev/docs/agents-and-mcp — cited by the answer
for agents · installed
A docs site with a sidebar, search and an API console for your readers. A version-locked index for their coding agents. Most docs tools build one and bolt on the other.
Markdown, an OpenAPI document and a CLI spec. The site, llms.txt, a Markdown copy of each page and the npm package an agent answers from are all built from them, so they cannot drift apart.
An agent gets the passages that answer its question, capped at 3,000 tokens, not a page of HTML. An API reaches it in LAPIS, a notation 84% smaller than the OpenAPI JSON this site serves.
fewer tokens per answer: 1,711 from docspack against 34,772 for the same documentation page as HTML.
per query, from a local SQLite index. No server to run and no network call.
of 157,715 tokens of documentation reaches the model, at most.
source for the site, the API and CLI reference, llms.txt and the agent package.
Intel(R) Core(TM) Ultra 9 185H · 22 cores · linux x64 · Node v24.21.0 · docspack 1.3.2 · 2026-10-05 · the full measurement
Measured while this page was built: 19 questions against docspack's own documentation, 113 chunks. The largest answer is 1,434 tokens, and the median answer is 5.8× smaller than the pages it came from.
what it builds
Each source becomes a page a person reads and a chunk an agent queries. Nothing is written twice, so the two cannot describe different documentation.
Sheaf llms.txt, a Markdown copy of each page, and a docs package for npm whose answers link back to the page.LAPIS POST /invoices.cmdspec git remote rm finds its command.docspack ask docspack search in the terminal, and docspack verify to find docs that no longer describe the code.for people · /api/example
/invoices Code samples in four languages and a console that sends the request · open the page
for agents · the same operation
createInvoice POST /invoices Creates a draft invoice. > Idempotency-Key?: str @header # Repeat a key to retry safely: the first invoice created under it is returned again rather than a second one being drafted. > body: InvoiceCreate < Invoice # 201 The created invoice. @auth bearerAuth
~75 tokens, against ~5,620 tokens for the whole OpenAPI document · the LAPIS
how it works
A docs package is an ordinary npm package: Markdown split into chunks, plus a manifest describing them. Its version tracks the library's.
// package.json "devDependencies": { "@acme/docspack": "1.4.0" }
One sync walks the docs packages this project depends on and writes their chunks into one SQLite database per machine. Keyed by name and version, so a release is read once no matter how many projects use it. It reads each installed library's own type declarations too, so a name nobody wrote a page about still has an answer.
$ npx docspack sync + @acme/docspack@1.4.0 128 chunks indexed + hono@4.13.3 272 chunks indexed (declarations) = @other/docspack@0.0.8 44 chunks cached
Every agent already has a shell, so the setup is done: no server to spawn, no per-client configuration, nothing resident when nobody is asking. One command writes the instruction into the AGENTS.md or CLAUDE.md this project already keeps — plus a Claude Code skill when there is one — and a second fails CI once that wiring drifts from what the tool does.
$ npx docspack agent install + AGENTS.md + .claude/skills/docspack/SKILL.md # in CI, so a pasted line cannot go stale $ npx docspack agent check ok AGENTS.md
Per query, of which 19ms is Node starting and 48ms is docspack. Invisible next to the seconds an agent spends thinking.
Porter stemming, so authenticate finds a chunk that only says authentication.
docspack mcp serves the same index over the protocol, byte-identical answers, if you would rather have a tool.
instead of
A docs site from Docusaurus or Mintlify serves people well, and an agent can read it too, a whole page at a time and always the latest version. The alternatives are not less capable. They differ on one axis: which version of the truth reaches the model, and how much of it.
| docspack | Docs site | MCP server | Agent skill | |
|---|---|---|---|---|
| A docs site for people | From the same source | Yes | No | No |
| Matches your lockfile | Always | Latest only | Latest only | Authored once |
| Works offline | Yes | No | No | Yes |
| Resident in context | Never | Pasted pages | Tool defs, always | Whole skill, always |
| Names the docs never mention | From the installed build | Absent | Absent | Absent |
| Cost of one answer | ≤ 3,000 tokens | Whole page | Whole page + trip | Whole document |
| Setup per project | One command | None | Per client | Per team |
Closest relative, and no reason it cannot exist — docspack mcp is exactly that. The difference is what sits behind it: a fetching server returns the same text to every project, pays the network on each call, and ranks whole pages because there is no local index.
A skill teaches an agent how to do something — your review checklist, your deploy runbook. It is authored once and loaded whole. Dependency docs change when you bump a version, and nobody wants to hand-edit them. Skills and docspack compose: the skill says “check the docs first”.
Copying Markdown into CLAUDE.md is the right instinct — it just does not survive upgrades. Nothing notices when it goes stale, and a flat folder has no index, so the agent reads the whole file to find one line.
Better recall for vague questions, at the price of an embedding pipeline, a service, and a re-ingestion story on every change. Aimed at a corpus you do not have. Yours is small, textual, and already versioned by the package manager.
measured
Against 831 chunks of one vendor's published
documentation — 157,715 tokens, mirrored from their
llms.txt. Every figure here is generated from
one measurement run, not typed in.
of the documentation reaches the model, at most — and a response is capped at 3,000 tokens no matter what was asked.
tokens for the largest answer measured. A warm CDN returns 34,772 tokens of HTML to answer the same question.
with fetch, DNS and TCP removed from the process. Not configured off — removed, so any attempt to reach out would throw.
of what hono and zod export is mentioned anywhere in their own documentation. The rest is declared in the installed build.
| Step | p50 | p95 | samples |
|---|---|---|---|
| Node starting the runtime, before docspack runs at all | 19.41ms | 23.83ms | 60 |
| docspack ask, end to end a whole process, spawn to answer | 67.34ms | 76.65ms | 60 |
| The query itself discovery, open, rank and budget, in process | 0.3ms | 0.63ms | 800 |
| The full-text search ranking 831 chunks | 0.2ms | 0.44ms | 800 |
| One documentation page, fetched one warm CDN-cached page, measured from the benchmark host | 52ms | 104ms | 20 |
A documentation site is written page by page, around tasks. An API grows name by
name. The two never converge, and nothing measures the difference — so we did,
against two projects that document themselves well. Every uncovered name is
declared in the package already in node_modules, which is
where docspack is already running.
hono@4.13.3
zod@4.4.3
Intel(R) Core(TM) Ultra 9 185H · 22 cores · linux x64 · Node v24.21.0 · docspack 1.3.2 · 2026-10-05 · pnpm --filter @docspack/bench perf reproduces it ·
the raw measurement
hono@4.13.3 · zod@4.4.3 · 2026-09-02 · pnpm --filter @docspack/bench coverage reproduces
it · the raw measurement
your own sources
A context window is exhausted just as easily by a project's own material — notes,
decision records, an export, rows out of a query. docspack index
indexes what you already have, and docspack recall answers from
it. No driver, no server, no embedding step.
Records arrive as JSON, so anything that can emit a row can be indexed. A record
carrying an id becomes exactly one chunk under that id — a row's identity
is its key, and splitting it would either duplicate that key or throw it away.
Your files change underneath the index. Published documentation is immutable for the life of its version; your notes are not, and are often edited by the same agent asking about them. So each source's size, modification time and hash are recorded, and an answer leads with a warning naming what has moved. A stale answer is quoted correctly and still wrong, which is worse than no answer.
recall is a separate command from ask on purpose.
ask promises an answer from the versions you installed, and one filter in
the query path keeps that promise: your notes can never reach an answer about a
dependency, and a dependency's documentation can never reach an answer about your notes.
# Markdown you already have $ npx docspack index --from ./notes # or rows, from anything that can emit JSON $ sqlite3 -json shop.db 'select id, title, body as text from posts' \ | npx docspack index --from-json - $ npx docspack recall "what did we decide about retries" ## @local/notes@0.0.0/retry-policy We settled on three attempts with exponential backoff. Anything that touches billing is never retried automatically. # edit a source, and the next answer says so NOTE: the corpus is out of date. 1 indexed source has changed since it was built: notes/retries.md.
Measured, not assumed — on 269 chunks of prose nobody
wrote for retrieval, against 17 questions fixed before the first run. Reading
everything grep suggests is the more accurate arm. It costs about
14× the tokens.
269 chunks · ~93,518 tokens · 17 held-out questions · commit c221778
the format
Any tool can read one. The manifest maps chunks to search terms, so indexing needs no model in the loop. Every field is written down in the package specification, and the rest of the manual is in the documentation.
node_modules/@acme/docspack/ ├─ package.json holds the version ├─ llms.txt table of contents └─ .llms/ ├─ manifest.json chunk routing table ├─ chunks/ │ ├─ api-auth.md │ └─ webhooks.md └─ schemas/ optional openapi
{
"name": "@acme/docspack",
"version": "1.4.0",
"chunks": [
{
"id": "api-auth",
"tokens": 150,
"tags": ["authentication", "bearer"]
}
]
}
Split Markdown at headings, or walk an OpenAPI document. The only command that
touches the network reads a project's public llms.txt.
$ npx docspack build --from ./docs $ npx docspack build --openapi ./api.json $ npx docspack build hono
publish docs · sheaf
docspack answers an agent from documentation somebody published. Sheaf is where that documentation becomes the site a person reads. The same Markdown is parsed once into a content graph, and both the pages and a docspack package can be emitted from it, so the two cannot describe different documentation. This site is built that way: the manual you are one click from is rendered from the graph.
A docs framework usually hands you an app and one exit. Sheaf hands you a graph and components: render them in the Astro site, React app or Preact app you already have, at whatever path you already own. There is no generated project, so there is nothing to eject from.
A graph knows where its pages are published, so a chunk cites the URL and anchor it came from rather than a filename. The agent's answer carries a link a person can open — which is the part a folder of Markdown cannot know.
sheaf Sheaf's content graph: a directory of Markdown as one serializable object.
sheaf-astro Renders a Sheaf graph with Astro's own Markdown pipeline.
sheaf-react The Sheaf docs shell — sidebar, page, contents and pager, built on cascivo.
sheaf-emit Turns a Sheaf graph into a docspack package, so one source serves both audiences.
// one directory of Markdown, parsed once const graph = await buildGraph("./docs"); // → the page a person reads const { html, headings } = await renderPage(page); // → the package their agent answers from await emitDocspackPackage(graph, { baseUrl: "https://acme.dev/docs", }); // which is what puts a link in the answer <!-- docspack: from https://acme.dev/docs/verify#drift -->
Early — v0.5.0, and versioned accordingly · how the two halves fit · npm · source
feedback
An agent has the documentation, the installed library and a failing program in front of it at the same moment. That signal is worth capturing, and today it evaporates.
The obvious version — let the agent file an issue — points a firehose at the people the whole model depends on. curl ended its bug bounty in January 2026 over AI-generated slop. So the goal is not to make feedback easy. It is to make high-signal feedback cheap and low-signal feedback impossible to transmit.
Claims must be falsifiable: drift has to name the identifier, anything
else has to carry a reproduction, and there is no kind for "this page is confusing".
Repeats increment a counter, so twelve agents hitting one bug produce one report that
says twelve.
Nothing is sent, and nothing can be — docspack contains no code that transmits a
report. A vendor who wants them declares a channel in their own
package.json, and a human decides whether to open the prefilled issue.
# the agent records what it found $ npx docspack feedback add \ --chunk @acme/docspack@1.4.0/api-auth \ --kind drift \ --evidence "client.setKey is not exported" # a human reviews, then decides $ npx docspack feedback submit 182ec604a6ea drift @acme/docspack@1.4.0/api-auth Open: https://github.com/acme/sdk/issues/new?… // the vendor opts in — or gets nothing "feedback": { "github": "acme/sdk", "accepts": ["drift"] }
guarantees
sync, ask, search and list make no network requests. They read node_modules and a local SQLite file.
The shared index may hold five versions of a library. A project only ever sees the one it installed.
Three chunks and 3,000 tokens by default, counted from the manifest before content is returned.
Community packages are labelled, and a manifest path that escapes the package is refused rather than read.
questions
docspack builds a project's documentation for two readers from one source. Markdown, an OpenAPI document and a CLI spec become a docs site for people and an npm package for AI agents. The package installs alongside the code, so an agent's answers match the installed version, and docspack answers from a local index with one command, offline, in a few thousand tokens at most.
No. docspack is a CLI: the agent runs docspack ask in the shell it already has. There is nothing resident when nobody is asking. An MCP server ships too (docspack mcp) and serves the same index, for assistants that cannot run commands.
Build one yourself with docspack build --from ./docs, from an OpenAPI document, or from a project's public llms.txt. The result is a normal npm package you can publish or keep private.
That is the common case: measured against two projects that document themselves well, their own documentation mentions about half of what they export. docspack sync also indexes each installed library's type declarations, so an answer about an undocumented name leads with the declaration from the build you installed and says the documentation does not cover it. Declarations are matched by name and never ranked against prose, and docspack sync --no-artifacts turns them off.
Three chunks and 3,000 tokens by default. The cap is counted from the manifest before any content is returned, so a query cannot overrun it. Measured against one vendor's published docs, at most 1.08% of the corpus reaches the model.
No. sync, ask, search and list make no network requests at all, and docspack contains no code that transmits a feedback report. The only command that touches the network is docspack build, which reads a project's public llms.txt when you ask it to.
MIT. Requires Node 22.5.0+. Currently v1.5.0. The package format is specified, and breaking it is a major release.
Library authors, API vendors and CLI maintainers. init reads the project
and proposes a package; the build writes the agent package beside your site.
# reads docs/, an OpenAPI document, the git remote and the license $ npx docspack init # the agent package, from the same source as the site $ npx docspack build # prepublishOnly runs doctor --strict first $ npm publish
The first command installs the docs beside the code. The second indexes them. The
third tells your agent the index exists, and docspack agent check fails
CI if that wiring drifts.
$ pnpm add -D @acme/docspack $ pnpm dlx docspack sync $ pnpm dlx docspack agent install