fix(skills): serve the bundle references the prompt advertises #969

Merged
coilysiren merged 1 commit from fix/bundle-references-reachable into main 2026-08-18 17:26:15 +00:00
Member

#927 split each skill into an inline entrypoint and fetchable references, and
loadSkills appends a Readable references index naming every deferred path.
The composed bundle got the index and not the tool.

The bug

NewAgent built SkillProvider from cfg.Definition.LocalSkillRoots alone, and
LoadBundle threw its reference list away:

pack, _, err := loadSkills(roots)   // LoadSkillpack

So a composed profile rendered an index naming bundle paths, instructed the model
to call read_skill on them, and every one of those calls was refused with a list
of the local-root paths instead.

On sirens-dowel that is not theoretical. The engineer bundle selects 14 skills
carrying references, and coding-* alone defers 96,080 bytes. All of it
advertised, none of it reachable, on a lane with a 12-round tool budget in a live
room. Rounds were being spent learning that the index lies.

The fix

  • bundleRoots is extracted from LoadBundle, so the pack and the references it
    defers come from one walk. Two walks could disagree, which is the class of
    bug this is.
  • LoadBundleReferences serves what the pack left out.
  • NewAgent appends them to the local-root references and builds one provider.

The prompt is byte-identical. The index was already there and the references
were already out of it. Only the served set changes, so no promptBudgets entry
moves.

The local-root load also stops swallowing its error. LoadSkillpack runs on the
same roots twenty lines earlier and hard-fails, so err == nil was unreachable
and read as tolerance that does not exist.

The tests pin the wiring, not just the partition

Reverting only the NewAgent change fails
TestTheAgentServesTheBundleReferencesItsPromptAdvertises with the exact
production symptom:

the prompt advertises .../content/skills/aos-public/coding-go/references/cli.md
and the agent's tool refuses it: no reference at ".../cli.md".
The readable ones are: ../../.agents/skills/coilyco-general/references/guardfile.md

That is the failure a member would have seen as a wasted tool round. The other two
cover the index-equals-served invariant, and that an inline: always reference is
not also fetchable so its text is not paid twice.

Verification

go build ./..., go vet ./..., go test ./... all pass. pre-commit run --files green on all three files, including rendered prompt snapshots, which is
the check that would catch an accidental prompt change.

Scope note

This makes progressive disclosure work for composed skills. It does not
reduce the inline half, which is the 165,624-byte figure on the engineer bundle.
That is agent/compose/roles.kdl and it is #966's call.

Related: #927, #859.

#927 split each skill into an inline entrypoint and fetchable references, and `loadSkills` appends a **Readable references** index naming every deferred path. The composed bundle got the index and not the tool. ## The bug `NewAgent` built `SkillProvider` from `cfg.Definition.LocalSkillRoots` alone, and `LoadBundle` threw its reference list away: ```go pack, _, err := loadSkills(roots) // LoadSkillpack ``` So a composed profile rendered an index naming bundle paths, instructed the model to call `read_skill` on them, and every one of those calls was refused with a list of the **local-root** paths instead. On `sirens-dowel` that is not theoretical. The engineer bundle selects 14 skills carrying references, and `coding-*` alone defers **96,080 bytes**. All of it advertised, none of it reachable, on a lane with a 12-round tool budget in a live room. Rounds were being spent learning that the index lies. ## The fix - `bundleRoots` is extracted from `LoadBundle`, so the pack and the references it defers come from **one walk**. Two walks could disagree, which is the class of bug this is. - `LoadBundleReferences` serves what the pack left out. - `NewAgent` appends them to the local-root references and builds one provider. **The prompt is byte-identical.** The index was already there and the references were already out of it. Only the served set changes, so no `promptBudgets` entry moves. The local-root load also stops swallowing its error. `LoadSkillpack` runs on the same roots twenty lines earlier and hard-fails, so `err == nil` was unreachable and read as tolerance that does not exist. ## The tests pin the wiring, not just the partition Reverting only the `NewAgent` change fails `TestTheAgentServesTheBundleReferencesItsPromptAdvertises` with the exact production symptom: ``` the prompt advertises .../content/skills/aos-public/coding-go/references/cli.md and the agent's tool refuses it: no reference at ".../cli.md". The readable ones are: ../../.agents/skills/coilyco-general/references/guardfile.md ``` That is the failure a member would have seen as a wasted tool round. The other two cover the index-equals-served invariant, and that an `inline: always` reference is not *also* fetchable so its text is not paid twice. ## Verification `go build ./...`, `go vet ./...`, `go test ./...` all pass. `pre-commit run --files` green on all three files, including `rendered prompt snapshots`, which is the check that would catch an accidental prompt change. ## Scope note This makes progressive disclosure work for composed skills. It does **not** reduce the inline half, which is the 165,624-byte figure on the engineer bundle. That is `agent/compose/roles.kdl` and it is #966's call. Related: #927, #859.
fix(skills): serve the bundle references the prompt advertises
All checks were successful
ci / image-build (pull_request) Successful in 44s
ci / test (pull_request) Successful in 1m16s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
5c6622e157
#927 split each skill into an inline entrypoint and fetchable references, and
`loadSkills` appends a "Readable references" index naming every deferred path.
The composed bundle got the index and not the tool.

NewAgent built SkillProvider from cfg.Definition.LocalSkillRoots alone, and
LoadBundle threw its reference list away (`pack, _, err := loadSkills`). So a
composed profile rendered an index naming bundle paths, told the model to call
read_skill on them, and every one of those calls was refused with a list of the
local-root paths instead.

On the sirens-dowel lane that is not theoretical. The engineer bundle selects 14
skills carrying references, and coding-* alone defers 96,080 bytes. All of it was
advertised and none of it was reachable, on a lane with a 12-round tool budget in
a live room.

- bundleRoots is extracted from LoadBundle so the pack and the references it
  defers come from one walk. Two walks could disagree, which is the class of bug
  this is.
- LoadBundleReferences serves what the pack left out.
- NewAgent appends them to the local-root references and builds one provider.

The prompt is byte-identical: the index was already there and references were
already out of it. Only the served set changes, so no budget moves.

The local-root load also stops swallowing its error. LoadSkillpack ran on the
same roots twenty lines earlier and hard-fails, so `err == nil` was unreachable
and read as tolerance that does not exist.

TESTS PIN THE WIRING, NOT JUST THE PARTITION. Reverting the NewAgent change
fails TestTheAgentServesTheBundleReferencesItsPromptAdvertises with the exact
production symptom: `no reference at <bundle path>. The readable ones are:
../../.agents/skills/coilyco-general/references/guardfile.md`. The other two
cover the index-equals-served invariant and that an `inline: always` reference is
not also fetchable, so its text is not paid twice.

`go build`, `go vet`, `go test ./...`, and pre-commit all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
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.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo!969
No description provided.