Ship a package's skills, so tool selection works on a clone - #181
Merged
Conversation
Selection narrows a Bot's tools to the ones its matching skills declare, and a deployment starts with no skills. There was no skills.yaml, nothing seeded any, and nothing ever wrote one with origin "catalogue" — so on every fresh clone there was nothing to match against and the narrowing never switched on. The feature shipped inert. Left to a screen it stays that way: somebody maps tools to skills by hand, in every deployment, again after every new connector. That is curation a product with a services team absorbs and a template cannot. OpenBot is a template, so the declaration ships with the thing that declares it. A tenant package may now carry skills.yaml. Slug, title, summary, instructions, and the serverId/toolName refs the skill needs. Seeded on boot as deployment skills, so everybody sees them in the / menu and connecting a connector is the only step left. The file is optional, so every existing package loads unchanged and ships none. Declared refs are deliberately not checked against the tools this deployment has seen, unlike the API path: a package is written before anybody connects anything, so it necessarily names tools for connectors that may not exist yet, and refusing would mean a template could only ship skills for connectors it could guarantee, which is none. An unknown ref sits inert, because the offer is still intersected with the grants. A skill somebody wrote here keeps its name. The / namespace is shared and first to take a name keeps it; a collision is skipped and logged rather than thrown, because anybody signed in may write a skill and throwing would let one person stop the deployment booting by choosing a name. The example package ships four, all Drive: find-a-document, whats-changed, who-owns-this, check-a-claim.
davidmckayv
requested review from
MikeRyanDev,
guidovizoso and
tylerslaton
as code owners
August 22, 2026 20:30
This was referenced Aug 22, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem #178 left
Tool selection narrows a Bot's tools to the ones its matching skills declare. A deployment starts with no skills at all — there is no
skills.yaml, nothing seeds any, and nothing has ever written one (origin: "catalogue"exists in the schema and no code path produces it).So on every fresh clone there is nothing to match against, every Bot falls to
nothing-declared, and the narrowing never switches on. I shipped the mechanism inert and did not notice.#180 spotted the same gap and fills it with a screen where you tick tools per skill. That works, but it makes the manual step permanent: somebody maps tools to skills by hand, in every deployment, again after every new connector. That is curation a product with a services team absorbs. OpenBot is a template.
What this does
A tenant package may carry
skills.yaml:Seeded on boot as deployment skills. Everybody sees them in the
/menu; which Bots carry them is decided in Admin like any other. Connecting a connector is the only step left.Four decisions worth naming
The file is optional. Every package written before this loads unchanged and ships no skills. Present and malformed is still refused, like every other package file.
Declared refs are not checked against known tools, unlike the API path. A package is written before anybody connects anything, so it necessarily names tools for connectors that may not be added yet — and that is precisely what makes them work the moment somebody does. Refusing would mean a template could only ship skills for connectors it could guarantee, which is none of them. An unknown ref sits inert; the offer is still intersected with the Bot's grants.
skill_toolscarries no foreign key for this reason.Naming a tool grants nothing, unchanged. This PR does not touch the grant path, the intersection in
grantedTools, or the policy.A skill somebody wrote here keeps its name. The
/namespace is shared and first to take it wins. A collision is skipped and logged, not thrown — unlike the agent case directly above it in the same function. Anybody signed in may write a skill, so throwing would let one person stop the deployment booting by choosing a name.skills.yamlis in the package checksum, so editing it is a package change and the next boot reseeds rather than reporting itself unchanged.Proof
server/tests/tenant-package.test.ts, 13 new tests, 40 in the file, all passing. Parsing: no file, empty file, refs carried through, a ref for a connector nobody added, a skill with no tools, a slug nobody could type, a ref that is notserverId/toolName, missing instructions. Seeding against a real database: lands withorigin: catalogueand null owner, unknown refs stored anyway, re-seeding replaces the declared set rather than adding to it, and a user-written slug survives with the deployment still booting.Full suite: 1302 pass, 0 fail. Typecheck, lint, format clean.
Driven in the browser. Restarted the local deployment against the example package and watched it seed:
All four render on
/skillsunder Workspace skills with their/commands, and the pre-existing user skill stays under Your skills.Honest limits
With only Drive connected you are still under the floor. Drive publishes four tools, and narrowing starts above twelve, so a clone with one connector sees no narrowing — correctly. What this changes is that the second and third connector need no clicks: the skills are already there declaring what they need. It also makes the shipped skills useful on their own as
/commands, which they are regardless of selection.Only Drive is covered, because Drive is the only catalogue connector. A package for a deployment with its own MCP servers writes its own refs.
No screen shows what a Bot would actually be offered. A skill can declare a tool its Bot does not hold; that is correct and invisible until
mcp.tools_discoveredrecords it. #180 names the same gap and it is still real.On #180
Not opposed on craft — it is careful work and honestly reported. But it should not be the path everyone walks. If this lands, the picker becomes a convenience for someone hand-writing a bespoke skill, next to a package that already ships the common ones. Worth deciding which lands first, since both touch the same declaration.