feat(knowledge): section action docs by header and serve a digest to agents (v1.57.0) - #197
Conversation
…agents (v1.57.0) `one --agent actions knowledge` now returns a digest instead of the whole document. Action docs are split on markdown headings; the sections an agent needs to build a correct request (method/URL, headers, description, enforcement rules, required + optional parameters, sample request, gotchas, error handling) are returned in full, and the size drivers (Success Response, Response Fields, worked examples) are listed in a table of contents with ids and char counts. Docs at or under 8k chars are returned whole. The agent loads more by name, id, or alias with `--section` (repeatable or comma-separated; a parent brings its children), or everything with `--full`. Both are served from the disk cache with no network call. An unknown name returns an error listing every available section. Backward compatible: `knowledge` and `method` keep their shape, the CLI execution guide preamble still leads the markdown, and a truncated digest ends with a notice naming the omitted sections and the exact commands to load them, so an agent that only reads the text still knows the document continues. `truncated`, `sections[]`, `omitted`, `omittedChars`, and `more` are added to the JSON envelope. Human output is unchanged (full document). Fixtures are three real action docs (gmail custom template, stripe, github) with internal hostnames scrubbed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bU7frFFLis1hLrcR1w2HY
… add GraphQL headings A batch run over all 359 cached action docs found three gaps in the digest rules: - Required fields nested under a deferred sub-heading (Notion's `Request Body > Option B > Required ...`) were dropped with their parent. A deferred H3+ node that contains essentials now keeps its own text and decides its children individually. - The budget back-fill could pull random schema types out of appended "Data Models — Chunk N" H1 sections. Only H2+ sections are back-filled. - GraphQL docs use `Request Variables`, `Path Parameters`, and `Query Parameters`; those are now essential. `Type` was removed from the essential set (it only appears inside schema appendices). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bU7frFFLis1hLrcR1w2HY
From a subagent trial of the digest: - `--section` responses reported `truncated: true` because the doc as a whole was not present. An agent looping "while truncated, fetch more" would never stop. The requested sections are returned whole, so the flag is now false there; `sections[]` still shows what else exists. - Add `resolved` (the section ids each name matched) so alias and prefix matches like `examples` -> `example-usage` are visible. - Add a one-line digest banner at the top of the markdown, right after the execution guide, so an agent that stops reading early still knows the document continues. The detailed notice stays at the end. - `more.section` now says `<id or heading>` to match the footer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bU7frFFLis1hLrcR1w2HY
…uplicate headings From a subagent trial against Notion's 134k-char Append Block Children doc (414 headings): - The `sections` list was 45 KB — more than the digest — and was re-sent on every `--section` call, so a 795-char section cost 47 KB. The digest now collapses the list to the action doc's own H2/H3s plus one entry per appended chunk (`children` counts the hidden ones, `sectionsCollapsed: true`, `sectionCount`). Section responses no longer carry the list at all. `--toc` returns the complete list. Not-found errors use the same collapse. Digest: 57 KB -> 19 KB; one section: 47 KB -> 1.6 KB. - `--section Response` on a doc with two `## Response` headings returned only the first, silently. Exact id and exact heading matches are now unioned, so both copies come back. - Appended H1 chunks are labelled `(appendix)` in the notice so a second copy of the endpoint doc no longer reads as if the core doc were missing. The notice names at most 12 omitted sections. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bU7frFFLis1hLrcR1w2HY
"mostly response shapes" understated what is omitted on schema-heavy docs (Notion's append block children defers the block schemas). Name the three kinds instead. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bU7frFFLis1hLrcR1w2HY
Measured over all 359 cached docs, the full-JSON payload had grown for 258 of them: the table of contents listed every heading (~1.9k chars) even on small docs returned whole, wiping out the digest's savings on the median doc. Untruncated responses now carry no section list; truncated ones list only what was omitted, since included sections are already the headings of the markdown. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bU7frFFLis1hLrcR1w2HY
siddharth-bhansali
left a comment
There was a problem hiding this comment.
Review — approve ✅
Reviewed the approach, ran the tests, and independently stress-tested the digest across the full local corpus. This is a strong, well-scoped change that solves the CLI knowledge-truncation problem cleanly. Highlights of what I verified beyond the PR's own results:
Independent batch over all 203 locally-cached docs (46+ platforms):
- 0 parse errors, 0 essential sections dropped, 0 digests larger than source, 40% total chars saved, ceiling ~8k. The core correctness invariant ("every doc keeps method/URL + required params") holds across the whole corpus, not just the fixtures.
- Enumerated every deferred heading across the corpus — they're all appropriately non-essential (responses, response-fields, notes, examples, optional params, errors, filters/sorting). No request-shaping heading is being deferred, so the essential allowlist is well-tuned to the actual doc conventions.
Edge cases (all clean): no-heading doc returns whole; empty doc no-ops; a 30k essential section is capped to ~6k with a clear [truncated — N more chars, load with --section] marker instead of blowing the budget; a doc that is entirely deferred content collapses to a TOC. parseSections/buildDigest are pure and never threw.
Design: the section-tree + essential-allowlist + deferred-TOC + on-demand load is the right pattern — it's lossless (nothing removed, just deferred and retrievable) and deterministic/pure (hence the 39 solid unit tests). The truncated-digest banner + closing notice reliably tell an agent the document continues, which the subagent trials confirm.
Follow-ups (none blocking)
- MCP parity — the important one. The MCP path (
@withone/mcp) still returns full docs.knowledge-sections.tsis pure by design so it can be imported there; this is the natural next PR and the bigger win for the benchmark (MCP is where a large tool surface hurts most). - Allowlist is doc-structure-dependent. It validated clean on 203 docs, but it's tuned to current Intelliscan heading conventions — worth a periodic re-audit (the batch check above is a 30-line script) or a CI guard if heading conventions drift. Low priority.
- The two backend defects the Gmail trial surfaced (passthrough dropping repeated query params; custom Gmail actions needing the connection key in the body) are real and worth their own tickets — outside this PR.
Nice work. LGTM to merge.
Summary
one --agent actions knowledgenow returns a digest instead of the whole document. Action docs are split on markdown headings; the sections an agent needs to build a correct request (method/URL, headers, description, enforcement rules, required + optional parameters, sample request, gotchas, error handling) come back in full, and the size drivers (success response sample, response fields table, worked examples, scraped reference schemas) are listed by name and size so the agent can ask for exactly what it needs. Docs at or under 8k chars are returned whole.Section and
--toccalls are served from the disk cache with no network round trip. Human (non-agent) output is unchanged.Backward compatible.
knowledgeandmethodkeep their shape and the execution-guide preamble still leads the markdown. A truncated digest carries a one-line banner at the top and a notice at the end naming the omitted sections and the exact commands to load them, so an agent that only reads the text still knows the document continues. The JSON addstruncated,sections[](omitted sections only, withid,heading,chars),omitted,omittedChars, andmore. An unknown section name returns an error listing every available section.Results
Measured on the full JSON an agent receives, across all 359 action docs cached locally (46 platforms). Full report with charts, per-platform breakdown, and the ten biggest wins: https://claude.ai/code/artifact/1ded5790-c441-4120-b005-fdfd73f6dcc1
By source-document size:
No bucket gets worse. The ceiling is the number that matters for agent reliability: nothing exceeds 16.2k chars now, where seven docs used to exceed 50k.
Subagent trials
Five fresh subagents, none told the feature existed, each ran a real search → knowledge → execute loop through the dev build on read-only actions.
Response), worked first time--toc39kEvery agent recognised the digest as partial and quoted
truncatedand the closing notice. No execute failed because of missing documentation.What the trials found and fixed
--sectioncall (a 795-char section cost 47 KB). It is now collapsed for mega-docs, sent only on truncated responses, and lists only omitted sections. That section now costs 1.4 KB.--sectionresponses saidtruncated: true, which an agent looping until the flag clears would never escape. Nowfalse, with aresolvedlist showing what each name matched.## Responseblocks in a scraped doc now come back.Not in this PR
@withone/mcppackage and still returns the full document.src/lib/knowledge-sections.tsis pure (no I/O) so it can be imported there for parity.metadataHeadersarrays silently lose entries even though the docs recommend them), and the custom Gmail actions require the connection key inside the body as well as positionally.Trade-off
Response Fields is deferred by default. For reporting tasks it sometimes holds the one detail that matters (Stripe: "amount is in the smallest currency unit"). Agents in the trials loaded it when needed, which is the intended path and costs one cached call.
Test plan
src/lib/knowledge-sections.test.tsagainst three real docs as fixtures (canonical, custom-action, and GraphQL templates; headings inside code fences; multi-H1 docs; size cap; nested required fields; TOC collapse; alias and duplicate-heading lookup)src/lib/guide-content.ts,skills/one/SKILL.md,README.md,src/cli.tshelp,src/commands/onboard.tsnpm test: 570 pass; the 6 failures inconfig.test.tsare pre-existing onmain(macOS/varvs/private/varsymlink)🤖 Generated with Claude Code
https://claude.ai/code/session_016bU7frFFLis1hLrcR1w2HY