skills: add @executor/plugin-skills with per-source attachment#357
Open
RhysSullivan wants to merge 4 commits intomainfrom
Open
skills: add @executor/plugin-skills with per-source attachment#357RhysSullivan wants to merge 4 commits intomainfrom
RhysSullivan wants to merge 4 commits intomainfrom
Conversation
A skill is a named markdown document surfaced through the existing tool
catalog — no new registry, no new agent API, no storage. The plugin
registers each skill as a static tool under a `skills` control source
whose handler returns the body verbatim. Discovery is
`tools.list({ query })`; loading is `tools.invoke(id)`. Naming is the
only signal: a `Skill:` description prefix plus a `<plugin>.<slug>`
tool name so skills surface alongside the tools they document when
callers search the relevant keyword.
Ships one skill — `skills.openapi.adding-a-source` — describing the
preview → resolve-auth → addSource flow, wired into apps/local.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
executor-cloud | 78a83b6 | Apr 21 2026, 01:18 AM |
@executor/sdk
@executor/plugin-file-secrets
@executor/plugin-google-discovery
@executor/plugin-graphql
@executor/plugin-keychain
@executor/plugin-mcp
@executor/plugin-oauth2
@executor/plugin-onepassword
@executor/plugin-openapi
@executor/plugin-skills
@executor/plugin-workos-vault
executor
commit: |
RhysSullivan
commented
Apr 21, 2026
The test file doubles as executable documentation for how a skill is
discovered (`tools.list({ query })`) and loaded (`tools.invoke(id)`) —
if it stops reading like the agent-facing flow, the plugin has drifted.
Also adds an openapi-side integration test that pins the naming-as-
attachment convention: `openapi.adding-a-source` lands in the same
result set as `openapi.previewSpec` / `openapi.addSource` for a plain
`query: "openapi"` search, and the skill body mentions both tools (so
renaming either one will fail the test).
The previous body said "ask the user for the value, then store it via
secrets.set under an id you'll reference in step 3." That routes the
raw secret through the LLM's context window — once it's there it's
already leaked, so doing `secrets.set` on it doesn't help. Same logic
for the client-credentials bullet ("store the client id/secret as
secrets").
Reword to the rule that's actually correct anyway: the agent only ever
picks an id and references it in step 3; the user provisions the value
out of band through the UI or CLI. The authorization-code flow was
already safe (URL handoff, token captured server-side) and is left
alone.
Secure secret capture from the agent's perspective is still an open
problem — whatever UI we wire up next has to land the value without
round-tripping through the model. For now the skill just forbids it.
Per-source skills are the primary attachment model — the MCP Skills
Over MCP WG has converged on skills-as-resources where a skill's URI
scopes it to the server that ships it. Our static-tool system is
effectively in-process MCP, so the attachment point should match:
skills live under the same sourceId as the tools they document.
Previously `openapi.adding-a-source` sat under the global `skills`
source and relied on substring-search ranking to land next to the
real openapi tools. Now it's registered by the openapi plugin's own
`staticSources`, so `tools.list({ sourceId: "openapi" })` returns
previewSpec, addSource, and the playbook together — no convention
needed. The global `skillsPlugin` stays wired in apps/local for
future cross-cutting / user-authored skills (currently empty).
To keep on-the-wire shape identical between the two registration
sites, `@executor/plugin-skills` now exports `toStaticSkill(skill):
StaticToolDecl` — shared "Skill:" prefix, empty input schema,
handler that returns the markdown body. When the MCP Skills SEP
stabilizes, replacing this helper's output with a dedicated
StaticSkillDecl is the mechanical part of the refactor.
Background and long-term plan: notes/skills.md.
Contributor
|
very nice PR! |
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.
Summary
@executor/plugin-skillspackage. A skill is a markdown body plus a name + description; the plugin registers each one as a static tool whose handler returns the body. Discovery is the existingtools.list({ query }); loading istools.invoke(id). No new primitives, no core SDK changes.toStaticSkill(skill): StaticToolDeclso any plugin can declare its own skills in its ownstaticSources— the globalskillsPluginis just one consumer of the same helper.@executor/plugin-openapiships its first skill (openapi.adding-a-source, a preview → resolve-auth → addSource walkthrough) and registers it on its own source.tools.list({ sourceId: "openapi" })returnsopenapi.previewSpec,openapi.addSource, andopenapi.adding-a-sourcetogether — the playbook sits literally next to the tools it documents.apps/localwiresskillsPlugin()(empty, reserved for cross-cutting / user-authored skills) — openapi owns its own skills directly.Safety: no user-typed secrets in the agent's context
The skill body was reworded so it never instructs the agent to accept a secret value in chat and call
secrets.setwith it. That path routes the raw value through the LLM before it gets stored, which is exactly the leak we want to avoid. The new rule: pick an id, reference it in step 3, the user provisions the value out of band. A test pins the policy so a future edit can't silently regress.Context: MCP Skills Over MCP WG
The first-class-primitive draft (SEP-2076:
skills/list+skills/get) was closed 2026-02-24. The Skills Over MCP WG (promoted from Interest Group four days before opening this PR) has accepted skills-as-resources with askill://<skill-path>/SKILL.mdURI scheme. Because our static-tool system is effectively in-process MCP, the attachment point should match: skills live under the sourceId of the server/plugin that ships them. That's why openapi owns its own skill now; when the SEP stabilizes, renaming "static tool with markdown body" → "static skill underskill://URI" is a shape change inside the same place. Full background and the deferred refactor plan:notes/skills.md.Test plan
tools.list({ sourceId: "openapi" })returns previewSpec, addSource, and adding-a-source togethertools.invoke("openapi.adding-a-source")returns the markdown bodyopenapi.previewSpecandopenapi.addSource(catches drift if a tool is renamed)secrets.set" and does contain "Never accept a secret value in-chat"@executor/plugin-skillsand@executor/plugin-openapi; typecheck clean in both +apps/localFollow-ups (not in this PR)
StaticSource { tools, skills }as a sibling field, once the MCP Skills Extension SEP stabilizes.