gate: cap startup/tool-schema payload including plugin disclosures #71

Closed
opened 2026-08-18 12:34:01 +02:00 by buildagent · 1 comment
Member

I040 measured the fixed startup tax for the first time and cut where it was safe. Nothing stops it growing back.

Measured 2026-08-18 (real initialize + tools/list off the wire)

bytes ~tokens
tools/list 46,444 ~11,600
instructions 4,298 ~1,075
total per session, before one query ~50,700 ~12,700

77% of that payload is English prose, not structure — 26,374 bytes of tool descriptions plus 9,370 bytes of per-parameter prose inside inputSchema.

Prior art for the ratchet

Copy has grown three times deliberately (175613a0, 25cd1698, 21c8ae66) and was pinned in place by regression tests asserting the marketing anchors. I040 retargeted those tests and added INSTRUCTIONS_MAX_BYTES = 4096 for the instructions string only. tools/list — the 90% — is still unbounded.

Ask

A test that drives a real initialize + tools/list and asserts total bytes under a ceiling, failing with a diff of the per-tool ranking so the offender is named. Set the ceiling at roughly the current value; it is a ratchet, not a target.

Second lever, untouched

9,370 bytes (20% of the payload) is per-parameter description prose inside inputSchema — search_text alone carries 1,407 B, change_impact 819 B, file_outline 788 B. Independent of the description pass; plausibly another ~2,000 B.

The rule this enforces

From I040: prose in a tool description is not a disclosure. If an agent needs it at call time it goes in the RESPONSE, where it ships with the data. A disclosure attached to the number beats one in a schema the agent read 200k tokens ago — and is cheaper, because it is only paid when the situation arises.

code-index://docs/{topic} exists now as the destination for anything that is reference rather than contract.

Runtime-plugin architecture expansion

#75 adds packages, generations, capabilities, bridges and reason-code families. None of that reference material belongs duplicated across every tool description or parameter schema.

Extend the ratchet to report bytes by category:

  • initialize instructions;
  • tool name/schema structure;
  • tool/parameter prose;
  • plugin-specific prose;
  • resource references.

Plugin state travels in responses only when relevant, preferably as one package dictionary plus compact row handles. Full capability, ABI and reason-code documentation lives under code-index://docs/plugins/*.

The real initialize + tools/list gate must run with the complete #80 surface enabled and fail with a per-tool/per-category size diff. Adding runtime plugins may not raise the fixed session tax without an explicitly reviewed baseline reason.

I040 measured the fixed startup tax for the first time and cut where it was safe. Nothing stops it growing back. ## Measured 2026-08-18 (real `initialize` + `tools/list` off the wire) | | bytes | ~tokens | |---|---:|---:| | `tools/list` | 46,444 | ~11,600 | | `instructions` | 4,298 | ~1,075 | | **total per session, before one query** | **~50,700** | **~12,700** | **77% of that payload is English prose, not structure** — 26,374 bytes of tool descriptions plus 9,370 bytes of per-parameter prose inside `inputSchema`. ## Prior art for the ratchet Copy has grown three times deliberately (`175613a0`, `25cd1698`, `21c8ae66`) and was *pinned in place* by regression tests asserting the marketing anchors. I040 retargeted those tests and added `INSTRUCTIONS_MAX_BYTES = 4096` for the instructions string only. `tools/list` — the 90% — is still unbounded. ## Ask A test that drives a real `initialize` + `tools/list` and asserts total bytes under a ceiling, failing with a diff of the per-tool ranking so the offender is named. Set the ceiling at roughly the current value; it is a ratchet, not a target. ## Second lever, untouched 9,370 bytes (20% of the payload) is per-parameter `description` prose inside `inputSchema` — `search_text` alone carries 1,407 B, `change_impact` 819 B, `file_outline` 788 B. Independent of the description pass; plausibly another ~2,000 B. ## The rule this enforces From I040: **prose in a tool description is not a disclosure.** If an agent needs it at call time it goes in the RESPONSE, where it ships with the data. A disclosure attached to the number beats one in a schema the agent read 200k tokens ago — and is cheaper, because it is only paid when the situation arises. `code-index://docs/{topic}` exists now as the destination for anything that is reference rather than contract. ## Runtime-plugin architecture expansion #75 adds packages, generations, capabilities, bridges and reason-code families. None of that reference material belongs duplicated across every tool description or parameter schema. Extend the ratchet to report bytes by category: - initialize instructions; - tool name/schema structure; - tool/parameter prose; - plugin-specific prose; - resource references. Plugin state travels in responses only when relevant, preferably as one package dictionary plus compact row handles. Full capability, ABI and reason-code documentation lives under code-index://docs/plugins/*. The real initialize + tools/list gate must run with the complete #80 surface enabled and fail with a per-tool/per-category size diff. Adding runtime plugins may not raise the fixed session tax without an explicitly reviewed baseline reason.
buildagent changed title from gate: startup payload budget — cap initialize + tools/list bytes in CI to gate: cap startup/tool-schema payload including plugin disclosures 2026-08-26 13:39:09 +02:00
Author
Member

Closing: this shipped in v0.23.0. Verified first-hand.

crates/mcp-server/tests/startup_payload_budget_e2e.rs, landed by 3a83a50 ("#71/#80 S20"), git tag --contains → v0.23.0.

It spawns the real binary, speaks the real protocol, and measures recv_line() — the bytes the client's socket actually saw, explicitly not a re-serialised constant, which is the difference between measuring the payload and measuring your own belief about it.

It is a two-sided bound, which is the part worth quoting, because a ceiling alone would have been decorative here:

const STARTUP_PAYLOAD_MAX_TOKENS: usize = 16_555;
const STARTUP_PAYLOAD_MIN_TOKENS: usize = 10_000;   // THE FLOOR
const MIN_TOOLS: usize = 18;

THE FLOOR. A ceiling alone cannot tell "we trimmed well" from "we measured nothing": an error envelope, a truncated read, or a server that answered tools/list with an empty array all come in far under budget and would pass.

That is exactly the "which direction does the likely bug push this number?" discipline, applied by the test's own author. Failure output names the three largest tool entries split into desc + schema, so a breach is actionable rather than a number. Four mutations were executed and two honest survivors are recorded rather than hidden.

The other half of this issue is also closed: no_tool_description_restates_a_docs_resource (:379), plus INSTRUCTIONS_MAX_BYTES = 4096 at crates/mcp-server/src/server.rs:16568.

One sub-ask genuinely unbuilt, split out

This issue asks the ratchet to report bytes by category — instructions / schema structure / tool prose / plugin-specific prose / resource references. Three of the five exist. Filed separately.

For whoever picks that up: the ceiling has been raised once, 15,300 → 16,555 for #89, with an itemised bill, and the payload has grown from this issue's measured 46,444 B to 55,081 B. The test's own doc is unambiguous that a raise is "a bill sent to every client on every session" and that the correct response to a breach is to trim — the category split exists precisely to make that trim targetable.

Why this close matters

#80 quotes this issue as live grounds — "#71 does not cap the enlarged startup/schema payload" — and it does cap it, with a floor, on the real wire. Same correction as #65, closed alongside this one.

## Closing: this shipped in **v0.23.0**. Verified first-hand. `crates/mcp-server/tests/startup_payload_budget_e2e.rs`, landed by `3a83a50` ("#71/#80 S20"), `git tag --contains` → **v0.23.0**. It spawns the real binary, speaks the real protocol, and measures `recv_line()` — **the bytes the client's socket actually saw**, explicitly not a re-serialised constant, which is the difference between measuring the payload and measuring your own belief about it. **It is a two-sided bound**, which is the part worth quoting, because a ceiling alone would have been decorative here: ```rust const STARTUP_PAYLOAD_MAX_TOKENS: usize = 16_555; const STARTUP_PAYLOAD_MIN_TOKENS: usize = 10_000; // THE FLOOR const MIN_TOOLS: usize = 18; ``` > THE FLOOR. A ceiling alone cannot tell "we trimmed well" from "we measured nothing": an error envelope, a truncated read, or a server that answered `tools/list` with an empty array all come in far under budget and would pass. That is exactly the "which direction does the likely bug push this number?" discipline, applied by the test's own author. Failure output names the three largest tool entries split into `desc + schema`, so a breach is actionable rather than a number. Four mutations were executed and **two honest survivors are recorded** rather than hidden. The other half of this issue is also closed: `no_tool_description_restates_a_docs_resource` (`:379`), plus `INSTRUCTIONS_MAX_BYTES = 4096` at `crates/mcp-server/src/server.rs:16568`. ### One sub-ask genuinely unbuilt, split out This issue asks the ratchet to report bytes **by category** — instructions / schema structure / tool prose / **plugin-specific prose** / resource references. Three of the five exist. Filed separately. For whoever picks that up: the ceiling has been **raised once**, 15,300 → 16,555 for #89, with an itemised bill, and the payload has grown from this issue's measured 46,444 B to 55,081 B. The test's own doc is unambiguous that a raise is "a bill sent to every client on every session" and that the correct response to a breach is to trim — the category split exists precisely to make that trim targetable. ### Why this close matters **#80 quotes this issue as live grounds** — *"#71 does not cap the enlarged startup/schema payload"* — and it does cap it, with a floor, on the real wire. Same correction as #65, closed alongside this one.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
h-dv/code-index#71
No description provided.