Skip to main content
Agents choose skills by truncating and loading them all into the system message, which increases costs, degrades skill selection performance, and induces context rot for the rest of the session. We address this by using two TypeSafe requests per turn, one to rank skills and one to verify the choice, and reduce incorrect skill loads by more than half. An agent with a large skill roster makes its choice on almost no information. The roster reaches it as an index: one line per skill, with the description truncated so the full text doesn’t crowd out the conversation. Hermes, the agent harness used here, cuts it to 60 characters by default. For example, at that width the skill that edits .pptx files reads nearly the same as the one that authors them. Ask for a pitch deck and the agent may load the wrong one. On a turn where no skill fits at all, it may still load one anyway, because a list of names invites a guess. This cookbook leaves the descriptions alone and uses progressive disclosure instead, reading all 182 skills cheaply and then reading three of them in detail. Two TypeSafe requests go in front of the decision on which skill to load, if any. The first ranks every skill in the roster against the user’s turn and answers whether the turn needs a skill at all. The second re-reads only the top three, now with each skill’s full description and the opening of its instructions, and is free to reject all of them. The winner’s name goes into one extra line of the agent’s system prompt for that turn:
The agent keeps its full index and its own judgement, and that one line only tells it which entry to look at first. The roster itself never changes, so any prefix caching over it still holds. Over 488 requests against claude-haiku-4-5-20251001, using skills from the Hermes roster: The third row shows the floor for making mistakes is not zero, because an agent given the right skill still does not always load it, and no selection method, however good, gets past that. You end up with a suggest() function that returns at most one skill name, a suggestion_block() that wraps it for the system prompt, and the harness that produced the table above, ready to point at your own roster.

Setup

  • Install the TypeSafe client, the Anthropic client, and the shared cookbook helpers.
  • Set a TypeSafe API key, and an Anthropic key for the agent being measured.
Note: the code blocks below are one script, in order. To follow along, put them in a single file in the order shown.

Caching results

JsonCache saves each call’s result, keyed on its inputs, so re-running replays the numbers below instead of calling either API. Delete json_cache.json to run live. The published run used jev-1.12 and claude-haiku-4-5-20251001, rendered 2026-07-31.

Step 1: load the roster

hermes_roster.json holds the 182 skills of NousResearch/hermes-agent (MIT) at one pinned commit. Each record holds a skill’s name and category, the description as the index shows it, the full description, and the opening of its SKILL.md. The index below, and the instructions above it in the prompt, are copied from Hermes.

Step 2: score the agent on its own

requests.json holds 488 single-turn requests, 315 of them covered by exactly one skill and the other 173 covered by nothing. The covered requests were written by Claude Sonnet 5 from each skill’s own SKILL.md, so the labels are trustworthy and the requests are easier than the ones users send. The 173 uncovered ones were all written to punish guessing: 85 everyday requests, 42 technical questions no skill serves (explain what a monad is), and 46 that ask for something specific the roster has no skill for, like post this to Mastodon on a roster that covers X and nothing else. Scoring reads the agent’s first response only. Both numbers are error rates, so lower is better on each:
  • wrong load: of the covered requests, the share where the first skill_view call was not the covering skill. A turn that loaded nothing at all counts as a miss.
  • needless load: of the uncovered requests, the share where the agent called skill_view at all.
The suggestion goes in its own block of the system prompt, after the roster rather than inside it, so the roster text is identical on every turn to maintain prefix caching. The agent has a minimal set of tools, including skill_view to load a skill using a free-text name. The name must match the skill exactly for a correct load.
The agent runs first with nothing but its roster, the way it works today. Its two error rates are the baseline the rest of the cookbook measures against.
Wrong loads land in the right skill’s own category far more often than chance would put them there, so the hard part is telling a few lookalikes apart. The agent is already looking in roughly the right place.

Step 3: rank the whole roster

One request carries two kinds of question:
  • which is a Choice over all 182 skill names, with the index description as each option’s criteria (the same text the agent itself gets). Its probabilities are the ranking.
  • three Nouls about the request, printed below, each asking a different way whether it wants an action taken rather than an explanation given. prose_suffices counts the other way round. Their mean decides whether to suggest anything at all, and under 0.30 nothing is suggested.
Both go out in one request, so the ranking and the check cost one round trip. Write these three to ask whether an action is wanted. A question about subject matter will not separate explain what a monad is from a request that needs a skill, since both are software. One Choice holds a roster this size comfortably. A few times larger and you would split it into chunks and rank each one, then run this same shortlist step over the winners.
The Notes.app request is unambiguous, and its top option is the right one. Nothing a ranking can do will save the Mastodon one: the three questions say a skill is wanted, because posting to an account is an action, and with a skill for posting to X and nothing for Mastodon the closest skill wins anyway. That leaves the deck. Both leaders are .pptx skills, and on 60 characters the wide Choice puts the editing skill ahead of the authoring one, for a request about authoring a deck.

Step 4: rerank the top three

Three options leave room for the full description plus the opening of each skill’s own SKILL.md, so the second request puts the same question to better evidence:
  • which is a Choice over the shortlist, with that longer text as each option’s criteria.
  • fits::{name} is one Noul per candidate: does this skill do the specific thing the request asks for? Each is answered on its own, so they can all come back low, and a shortlist whose highest one lands under 0.30 gets dropped entirely.
The two .pptx skills separate once each one brings its own text: the deck request flips to the authoring skill. The fits nouls and the Choice disagree there: the nouls score the editing skill higher while the Choice picks the authoring one. They are deciding different things. The Choice settles which skill, and the nouls settle whether to say anything at all. The Mastodon request survives both checks. Its best fits noul lands above 0.30, so the recipe suggests the X skill for a request about Mastodon. Most requests like it do get caught, but a second pass can only reject what the wide ranking hands it, and here that was three near-misses. The function below is the whole recipe: two requests and two thresholds, with at most one skill name coming back. To point it at your own roster, replace hermes_roster.json. Every question above reads name, description, description_full, and body out of that file, and nothing else knows about Hermes.

Step 5: measure the suggestion

Each of the 488 requests goes to the agent three times, one measured turn each. The runs differ only in what the agent is told: The third is not achievable; it is the ceiling the other two get measured against. The wording of that suggestion is doing two jobs. It says the suggestion can be ignored, because pushing harder wins compliance on wrong suggestions too, and a wrong one is worse than none. And a turn with nothing to suggest still sends a sentence saying so; sending nothing at all would leave the roster’s own “err on the side of loading” instruction unopposed.
The suggestion fixes many more requests than it breaks, but it does break some the agent had right on its own. A confident wrong suggestion is more persuasive than no suggestion at all, which is the price of putting one in front of the turn.
output

What the results show

  • Wrong loads fell from 16.8% to 7.3% and needless ones from 9.8% to 4.0%, which is most of the gap between guessing from a truncated index and being handed the answer.
  • Some requests the agent had right on its own come back wrong once a suggestion is attached. Counts are above.
If an agent of yours carries a large roster, the shape to copy is a cheap ranking over everything followed by a close look at two or three, with both steps allowed to come back empty-handed.

Open it in the playground

The code below builds a playground link for the deck request from step 4, with each candidate’s full description and body excerpt as its criteria.
Open the shortlist + questions in the TypeSafe playground →

What’s next

The same shape shows up elsewhere: Intent Routing for routing to a handler rather than a skill, Confidence for picking the two thresholds, and Speculative Fan-Out for putting every question in one request.