Skip to content

skills: add @executor/plugin-skills with per-source attachment#357

Open
RhysSullivan wants to merge 4 commits intomainfrom
worktree-executor-self-configurable
Open

skills: add @executor/plugin-skills with per-source attachment#357
RhysSullivan wants to merge 4 commits intomainfrom
worktree-executor-self-configurable

Conversation

@RhysSullivan
Copy link
Copy Markdown
Owner

@RhysSullivan RhysSullivan commented Apr 21, 2026

Summary

  • New @executor/plugin-skills package. 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 existing tools.list({ query }); loading is tools.invoke(id). No new primitives, no core SDK changes.
  • Exports toStaticSkill(skill): StaticToolDecl so any plugin can declare its own skills in its own staticSources — the global skillsPlugin is just one consumer of the same helper.
  • @executor/plugin-openapi ships its first skill (openapi.adding-a-source, a preview → resolve-auth → addSource walkthrough) and registers it on its own source. tools.list({ sourceId: "openapi" }) returns openapi.previewSpec, openapi.addSource, and openapi.adding-a-source together — the playbook sits literally next to the tools it documents.
  • apps/local wires skillsPlugin() (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.set with 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 a skill://<skill-path>/SKILL.md URI 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 under skill:// 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 together
  • tools.invoke("openapi.adding-a-source") returns the markdown body
  • Skill body mentions both openapi.previewSpec and openapi.addSource (catches drift if a tool is renamed)
  • Skill body does not contain "store it via secrets.set" and does contain "Never accept a secret value in-chat"
  • Duplicate skill ids throw at plugin construction time
  • Empty skill list works (the apps/local case)
  • 49 tests passing across @executor/plugin-skills and @executor/plugin-openapi; typecheck clean in both + apps/local

Follow-ups (not in this PR)

  • StaticSource { tools, skills } as a sibling field, once the MCP Skills Extension SEP stabilizes.
  • Secure secret capture (MCP elicitation wired to a UI that bypasses the model). The skill forbids in-chat values but doesn't yet have a UX to replace it.

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.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 21, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 78a83b6 Apr 21 2026, 01:18 AM

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 21, 2026

Open in StackBlitz

@executor/sdk

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/sdk@357

@executor/plugin-file-secrets

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-file-secrets@357

@executor/plugin-google-discovery

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-google-discovery@357

@executor/plugin-graphql

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-graphql@357

@executor/plugin-keychain

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-keychain@357

@executor/plugin-mcp

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-mcp@357

@executor/plugin-oauth2

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-oauth2@357

@executor/plugin-onepassword

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-onepassword@357

@executor/plugin-openapi

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-openapi@357

@executor/plugin-skills

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-skills@357

@executor/plugin-workos-vault

npm i https://pkg.pr.new/RhysSullivan/executor/@executor/plugin-workos-vault@357

executor

npm i https://pkg.pr.new/RhysSullivan/executor@357

commit: 78a83b6

Comment thread packages/plugins/openapi/src/sdk/skills.ts Outdated
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.
@RhysSullivan RhysSullivan changed the title skills: add @executor/plugin-skills (skills-as-tools) skills: add @executor/plugin-skills with per-source attachment Apr 21, 2026
@mrzmyr
Copy link
Copy Markdown
Contributor

mrzmyr commented Apr 22, 2026

very nice PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants