Skip to content

Commit c172866

Browse files
authored
🤖 feat: expose agent skills as slash commands (#1673)
Expose Agent Skills as slash commands. - Adds `agentSkills.list` / `agentSkills.get` oRPC endpoints so the UI can discover/read skills. - Surfaces skills in `/` autocomplete and supports `/skill-name <message>` to apply a skill for that send. - Injects the selected skill body via `additionalSystemInstructions` and annotates the message with `muxMetadata` so history shows the original `/skill-name ...` text. Validation: - `bun test src/browser/utils/slashCommands/suggestions.test.ts` - `bun test src/cli/server.test.ts` - `make static-check` --- <details> <summary>📋 Implementation Plan</summary> # Plan: Expose Agent Skills as Slash Commands ## Goal Make **Agent Skills** discoverable and invokable via the chat **slash command** UX (type `/` → autocomplete), so a user can explicitly apply a skill and have it included in the model context for that send. ## Recommended approach: “Skill-as-command” + per-send context injection **Net LoC estimate (product code only): ~350–550 LoC** ### UX / behavior - **Autocomplete:** skills appear alongside existing commands in the `/` suggestions list. - Display: `/{skillName}` with description. - Show scope hint (`(project)`, `(user)`, `(built-in)`). - **Invocation:** user writes: - `/{skillName} <their message>` - **On send:** 1. Detect that the first token is a known skill name. 2. Load the skill package from backend. 3. Send the remainder of the user’s message normally, but inject the skill body into **system context** for this request. --- ## Implementation plan ### 1) Backend: expose Agent Skills over ORPC 1. **Add ORPC schemas** in `src/common/orpc/schemas/api.ts`: - `agentSkills.list` → returns `AgentSkillDescriptor[]` - `agentSkills.get` → returns `AgentSkillPackage` for a `skillName` - Reuse `SkillNameSchema` / `AgentSkillDescriptorSchema` / `AgentSkillPackageSchema` from `src/common/orpc/schemas/agentSkill.ts`. 2. **Add ORPC handlers** in `src/node/orpc/router.ts`: - `agentSkills.list`: call `discoverAgentSkills(runtime, discoveryPath)`. - `agentSkills.get`: call `readAgentSkill(...)` and return `result.package`. 3. **Defensive limits:** - Enforce a **max injected skill body size** (chars) to avoid exploding system prompts (truncate with a clear sentinel). - Treat read failures as non-fatal: return a typed error to the UI. ### 2) Frontend: fetch skill descriptors for suggestions 1. **Load skill descriptors** in `src/browser/components/ChatInput/index.tsx` similarly to provider list: - `const [agentSkillDescriptors, setAgentSkillDescriptors] = useState<AgentSkillDescriptor[]>([])` - `useEffect(() => api.agentSkills.list(...))` 2. Cache policy: - Refresh on `workspaceId` / `projectPath` change. - (Optional later) hot-reload via fs watcher / event stream. ### 3) Slash suggestions: include skills 1. Extend `SlashSuggestionContext` (`src/browser/utils/slashCommands/types.ts`) to accept skill descriptors: - `agentSkills?: AgentSkillDescriptor[]`. 2. Update `getSlashCommandSuggestions()` (`src/browser/utils/slashCommands/suggestions.ts`) to merge: - existing static command suggestions - **dynamic skill suggestions** (alphabetical; filtered by partial token) 3. Update `CommandSuggestions` UI (`src/browser/components/CommandSuggestions.tsx`) to visually distinguish skills: - Either extend `SlashSuggestion` with `kind` + `rightLabel`, or bake scope into `description`. ### 4) Sending pipeline: parse + inject skill context 1. In `ChatInput/index.tsx` send handler: - Before calling `parseCommand()`, detect `/{skillName}` as the first token (using `agentSkillDescriptors`). 2. If it’s a skill invocation: - Call `api.agentSkills.get({ skillName, ...discoveryContext })`. - Compute: - `userMessageTextWithoutCommand` - `additionalSystemInstructions` that wraps the skill content, e.g.: - header: `Agent Skill applied: {name} ({scope})` - body: the skill markdown `package.body` - Send via `api.workspace.sendMessage({ message, options: { ...sendMessageOptions, additionalSystemInstructions } })`. 3. Optional but recommended: annotate UI via `muxMetadata` so the transcript shows which skill was applied. ### 5) Edge cases to handle - User sends only `/{skillName}` (no remainder message): intercept and avoid sending an empty message. - Unknown `/{token}`: fall back to existing behavior (normal slash command parse; else send as plain text). - Multiple skills in one line: MVP supports only the first token. ### 6) Tests - Unit tests for: - suggestion list merging + filtering - splitting `/{skillName} rest of message` - Integration tests for ORPC endpoints: - `agentSkills.list` returns descriptors - `agentSkills.get` returns package + respects project/global precedence --- <details> <summary>Notes / rationale</summary> - Mux already lists skill descriptors in the system prompt (`buildAgentSkillsContext()`), enabling model-initiated tool calls. This plan adds explicit user invocation + UI discoverability without changing the core tool-call mechanism. - Per-send injection avoids introducing persistent “pinned skill” state. </details> </details> --- _Generated with [`mux`](https://github.com/coder/mux) • Model: `openai:gpt-5.2` • Thinking: `xhigh` • Cost: $1.29_ --------- Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent b895f43 commit c172866

27 files changed

Lines changed: 1231 additions & 112 deletions

docs/agents/agent-skills.mdx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ This keeps the system prompt small while still making skills discoverable.
1616

1717
## Where skills live
1818

19-
mux discovers skills from two roots:
19+
mux discovers skills from three roots:
2020

2121
- **Project-local**: `<projectRoot>/.mux/skills/<skill-name>/SKILL.md`
2222
- **Global**: `~/.mux/skills/<skill-name>/SKILL.md`
23+
- **Built-in**: shipped with mux
2324

24-
If a skill exists in both locations, **project-local overrides global**.
25+
If a skill exists in multiple locations, the precedence order is: **project-local > global > built-in**.
2526

2627
<Info>
2728
mux reads skills using the active workspace runtime. For SSH workspaces, skills are read from the
@@ -80,6 +81,13 @@ metadata:
8081

8182
mux injects an `<agent-skills>` block into the system prompt listing the available skills.
8283

84+
You can apply a skill in two ways:
85+
86+
- **Explicit (slash command)**: type `/{skill-name}` (optionally followed by a message: `/{skill-name} ...`) in the chat input. mux will send your message normally (or a small default message if you omit one), and persist a synthetic snapshot of the skill body in history immediately before that message.
87+
- Example: `/init` runs the built-in `init` skill to bootstrap `AGENTS.md`. You can override it with `~/.mux/skills/init/SKILL.md` (or `.mux/skills/init/SKILL.md` for a single project).
88+
- Type `/` to see skills in the suggestions list.
89+
- **Agent-initiated (tool call)**: the agent can load skills on-demand.
90+
8391
To load a skill, the agent calls:
8492

8593
```ts
@@ -101,7 +109,8 @@ agent_skill_read_file({ name: "my-skill", filePath: "references/template.md" });
101109

102110
## Current limitations
103111

104-
- There is no `/skill` command or UI activation flow yet; skills are loaded on-demand via tools.
112+
- Slash command invocation supports only a single skill as the first token (for example `/{skill-name}` or `/{skill-name} ...`).
113+
- Skill bodies may be truncated when injected to avoid accidental mega-prompts.
105114
- `allowed-tools` is not enforced by mux (it is tolerated in frontmatter, but ignored).
106115

107116
## Further reading

scripts/zizmor.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88
# Fallback: if Docker isn't available/running (common on dev machines), download a
99
# prebuilt zizmor binary from GitHub Releases and run it directly.
1010

11+
if ! command -v docker >/dev/null 2>&1; then
12+
echo "⚠️ docker not found; skipping zizmor" >&2
13+
exit 0
14+
fi
15+
16+
if ! docker info >/dev/null 2>&1; then
17+
echo "⚠️ docker daemon not running; skipping zizmor" >&2
18+
exit 0
19+
fi
20+
1121
set -euo pipefail
1222

1323
cd "$(dirname "${BASH_SOURCE[0]}")/.."

0 commit comments

Comments
 (0)