Chapter 4 of 7 · 9 min read
Routing cues in the description, action-changing steps in the body
On this page
The platform team's registry runs a lint rule on every merge: SKILL.md body must be under 800 tokens. The pull request that added refund-partial-crossledger failed it at 2,310. The author did the obvious thing and moved out the largest block, a 940-token JSON schema for a ledger entry, into reference/ledger-schema.md. Still 1,370, still red. Out went the two worked examples, 610 tokens, into examples/. Body at 760 tokens, CI green, merged on a Thursday.
By the following Tuesday the ledger service had rejected 41 refund attempts with 422 unprocessable_entity. The agent had been constructing ledger entries with a field called counterparty_account, which is what the field is called in the customer-facing API and is not what it is called on the ledger, and omitting idempotency_key entirely, because nothing in the remaining 760 tokens said the field existed. The skill selected correctly every time. The body loaded every time. The one piece of content without which the central action of the skill cannot be performed was sitting in a file the model read on 44% of runs.
The author was not careless. They applied the only rule the team had given them, and that rule is about bytes. Tier placement is not a question about size, and a rule expressed in tokens will get it wrong in both directions with perfect consistency.
Three questions, asked in order
The criterion that works is about when the content is needed relative to a decision the model has already made.
The first question is whether the content helps decide that this skill applies at all. That content belongs in the description, because the description is the only thing resident when the decision is made. Triggering situations belong here — the states of the world in which someone would want this. So do the words those people actually use. What does not belong here is anything that presumes the decision has already gone this way, which is most of what an author instinctively wants to write, because an author is thinking about the task and the model reading the description is thinking about a hundred other candidates.
The second question is whether the content changes what the agent does on a run that has selected the skill. Steps, ordering constraints, the field names of the call it has to construct, the rule that says never issue the refund before the ledger reversal has settled — all of that changes the output, and it belongs in the body, where it arrives unconditionally once the skill is chosen. The test is counterfactual and it is worth applying literally: delete this paragraph, imagine a competent run that never sees it, and ask whether the run's actions differ. If they do, the paragraph is body content regardless of whether it is nine tokens or nine hundred.
The third question applies to what is left, and it is whether the content is consulted for a subset of cases rather than needed on the typical run. The exhaustive error-code table that matters when a call comes back 409 and never otherwise, the six worked examples that a run consults when it hits an unusual shape, the 4,000-token OpenAPI fragment covering endpoints this procedure touches once a quarter — those are lookups, and lookups belong in bundled files. The distinguishing property is that the run can proceed correctly without them and knows when it cannot.
Notice that the ledger schema fails the third question badly. Every run of this skill constructs a ledger entry; there is no subset. It fails the second question's counterfactual too, since a run without it produces counterparty_account and a 422. It was moved because it was large, and size was uncorrelated with any of the three answers.
Writing the description out of the requests
The first question has a practical difficulty that the other two do not, which is that the author knows what the skill does and does not know what people will type. Descriptions written from the README describe the artifact. Requests describe a situation, in the vocabulary of whoever is stuck.
The original description of the refund skill read:
---
name: refund-partial-crossledger
description: Implements the cross-ledger partial refund procedure defined in
RFC-208, covering reversal entry creation and settlement reconciliation.
---That is accurate and it is about the document rather than the moment. Nobody who needs it types "cross-ledger partial refund procedure". The team pulled six months of support-console transcripts and counted the phrases that appeared in requests a human had later resolved with this procedure:
$ rg -oiN '(partial refund|refund part|refund some|chargeback|credit note|\
reverse (the )?payment|refund .{0,12}(fee|tax|shipping))' transcripts/ \
| sort | uniq -c | sort -rn | head
412 partial refund
287 refund part
193 chargeback
166 credit note
98 reverse the payment
71 refund the shippingChargeback and credit note are not the same operation as a partial refund, which is exactly why they matter: those are the words people arrive with when the operation they need turns out to be this one. The rewritten description carries the situations and the vocabulary, and it stays inside the twenty-five-token range that keeps the resident block affordable:
---
name: refund-partial-crossledger
description: Refund part of a payment — a line item, a fee, tax or shipping —
when the charge spans two ledgers. Use for partial refunds, credit notes, and
chargebacks that need a manual reversal entry. Not for full refunds, which
the payments API handles directly.
---The final clause does work out of proportion to its four tokens. A description that says where the skill stops is the cheapest available fix for the near-neighbour clustering the previous chapters kept running into, because it gives the model a discriminating signal that the request itself often supplies. The company also has refund-full, and before that clause the two competed on every request containing the word refund.
Where the refund skill's content actually lands
The skill has seven distinguishable pieces of content. Applying the three questions to each gives a placement; applying the 800-token cap gives a different one.
| content | tokens | the three questions say | the size cap said | consequence of the cap |
|---|---|---|---|---|
| triggering situations and request vocabulary | 24 | description | description | correct, by luck |
| six-step reversal procedure | 430 | body | body | correct |
| ledger entry schema, required fields | 940 | body — every run builds one | reference/, deferred | 41 rejected refunds |
| ordering rule: reversal settles before refund posts | 55 | body | body | correct |
| two worked examples, common cases | 610 | body — they are how the steps are read | examples/, deferred | steps followed literally, edge cases mishandled |
| ledger error-code table, 38 codes | 1,600 | reference/, deferred | reference/, deferred | correct, by luck |
| RFC-208 rationale and history | 780 | not in the skill at all | inlined in body, under cap | 780 tokens paid on every run, changes nothing |
The last row is the half of the size-cap failure that produces no incident. Nothing about the RFC's history changes an action, so it survived every review by being small enough not to trip the lint, and it was paid for on all 1,100 runs a week that selected the skill. A rule that is easy to lint gets enforced consistently, and consistency in the wrong direction is what makes this failure durable rather than occasional.
The two worked examples are the interesting row, because they are the case where the three questions and the intuition of the team most often disagree. Examples feel like reference material — they are illustrations, they are long, they repeat what the steps already said. What they actually do on a run is disambiguate the steps. The procedure says "create the reversal entry against the originating ledger"; the example shows that for a shipping-fee refund the originating ledger is the merchant's rather than the platform's, which is not derivable from the step. Delete the example and the run's actions change, so it is body content. A different skill's examples, showing three variations of an output format that the step already specifies unambiguously, would fail that counterfactual and belong in a file.
After the rewrite the body sits at 1,480 tokens, which is above the cap the team had been enforcing, and the correct outcome is that the cap goes rather than the schema. The registry's lint rule was replaced with two checks that are about placement instead of length: a body may not contain a section whose deletion leaves every action unchanged, which is enforced in review rather than automatically, and any file referenced by the body must be reachable and non-empty from a clean checkout, which is mechanical. The first of those is a judgment call and is where reviewers now spend their attention.
Two teams, both right about their own traffic
This argument gets run in production by teams who have reached opposite conclusions and can each show numbers. One keeps every body under 200 tokens and pushes everything else into bundled files, and it works for them because their skills are lookup-shaped: given an incident, the agent needs one of forty runbooks, each run touches one, and deferral means the other thirty-nine cost nothing. Another writes 2,000-token bodies with no bundled files at all, and it works for them because their skills are procedure-shaped: a single long transformation where nearly every paragraph fires on nearly every run, and any fetch boundary inside it is a place where a run can proceed without something it needed.
Both are describing their own answer to the third question and generalising it into a rule about size. The workload determines the shape: a skill whose content is needed by 95% of its runs has no third-question content to defer, and a skill whose content is needed by 5% of its runs is almost all third-question content. The number of tokens involved says nothing about which of those you have. The proposed compromise, a cap somewhere in the middle at 800 tokens with bundled files required beyond it, is the one rule guaranteed to be wrong for both, and this chapter's opening is what that looks like when it lands on a real skill.
What this split is not
It is not a document-organisation problem, though it uses the same vocabulary. Splitting a long file into shorter files for humans optimises for a reader who can follow a link at zero cost and will notice if the link is broken. The tier boundary optimises for a reader who pays a round trip per link, follows some fraction of them, and reports nothing when it does not.
It is also not compression. Nothing in the three questions asks the author to shorten anything. The 940-token schema stays 940 tokens; it moves in, not out. Teams that read progressive disclosure as a mandate to make bodies small end up rewriting content that was correct, and the rewriting is where field names get dropped.
And it does not decide what belongs in a skill at all. The RFC rationale is not misplaced across tiers; it should not be in the skill, and it belongs in the RFC where an engineer can read it. The three questions sort content that has already earned its place, and a fair amount of what authors reach to defer is content that fails an earlier test entirely.
The placement rule has one class of content it handles badly, and the failure is severe enough to have its own chapter. Prerequisites — obtain a scoped token, set the region, confirm the environment is not production — read like setup notes, which look like third-question material, and they are needed by every run. The next chapter is about what happens when a run follows every visible instruction and dies at the first API call.
Progressive disclosure — read the current version at https://daviddominguez.dev/learn/en/progressive-disclosure/routing-cues-in-the-description-action-changing-steps-in-the-body