Skip to content

Commit 4c025d6

Browse files
committed
docs(ai-chat): add Agent Skills pattern page
New docs/ai-chat/patterns/skills.mdx covering Phase 1 end-to-end: folder layout, SKILL.md format, skills.define + chat.skills.set, auto-wired loadSkill/readFile/bash tools, built-in CLI bundling, path scoping rules, and mixing with custom tools. Links to the AI SDK cookbook pattern we build on.
1 parent d843cae commit 4c025d6

2 files changed

Lines changed: 215 additions & 1 deletion

File tree

docs/ai-chat/patterns/skills.mdx

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
---
2+
title: "Agent Skills"
3+
sidebarTitle: "Agent Skills"
4+
description: "Ship reusable capabilities (folders with SKILL.md + scripts) that a chat agent discovers and invokes on demand."
5+
---
6+
7+
Agent skills are reusable capabilities you ship as folders — a `SKILL.md` describing when and how to use them, plus optional scripts, references, and assets. The chat agent sees a short description of each skill in its system prompt, loads the full instructions on demand via a `loadSkill` tool, and invokes the bundled scripts via `bash` — all without you wiring anything up manually.
8+
9+
Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills). Works with any provider (OpenAI, Anthropic, Gemini, etc.) — not tied to Anthropic's server-side skills.
10+
11+
## Why skills?
12+
13+
Compared to regular AI SDK tools:
14+
15+
- **Tools** are typed functions you pre-declare. Great when you know up-front exactly what capability the agent needs.
16+
- **Skills** are folders the model discovers and reads on demand. Great when the capability is a bundle of instructions + helper scripts that would be awkward to encode as a single tool.
17+
18+
PDFs are the canonical example: you don't want to ask the LLM to parse PDF bytes inline. You want it to `bash scripts/extract.py report.pdf` using a bundled `pdfplumber` wrapper. A skill ships the script, the instructions, and any reference notes together.
19+
20+
Skills are also [dashboard-editable](/ai-chat/skills/overview) in Phase 2 — a platform team can tighten a skill's description or "when to use" text without a redeploy. Phase 1 (today) is SDK-only.
21+
22+
## Trust model
23+
24+
Skills are **developer-authored code**, not end-user-supplied. The same developer who writes the `chat.agent()` writes the skill bundle. The trust boundary is identical to any `tool.execute` handler the developer writes — scripts run directly in the Trigger.dev worker container, no sandboxing required.
25+
26+
This makes skills different from the Claude Code / end-user model where arbitrary user-provided skills need isolation. Don't accept skill paths from untrusted input.
27+
28+
## Skill folder layout
29+
30+
A skill is a directory under your project (conventionally `trigger/skills/{id}/`):
31+
32+
```
33+
trigger/skills/time-utils/
34+
├── SKILL.md # Required — frontmatter + instructions
35+
├── scripts/
36+
│ ├── now.sh
37+
│ └── add.sh
38+
├── references/
39+
│ └── timezones.txt
40+
└── assets/ # Optional — templates, data files, etc.
41+
```
42+
43+
### SKILL.md
44+
45+
Frontmatter is YAML-subset — only `name` and `description` are required:
46+
47+
```md
48+
---
49+
name: time-utils
50+
description: Compute and format dates/times in arbitrary timezones. Use when the user asks "what time is it", timezone conversions, or date math.
51+
---
52+
53+
# Time utilities
54+
55+
## When to use
56+
57+
- The user asks for the current time in a timezone
58+
- The user wants date math ("3 days from now")
59+
60+
## Scripts
61+
62+
### `scripts/now.sh [TZ]`
63+
Prints the current time in the given IANA timezone (default `UTC`).
64+
65+
### `scripts/add.sh DAYS [TZ]`
66+
Prints a date `DAYS` days from now.
67+
68+
## Tips
69+
- IANA timezone names only (`America/New_York`, not `EST`).
70+
- See `references/timezones.txt` for a cheat-sheet.
71+
```
72+
73+
The **description** is what the model sees in its system prompt — write it like you're explaining to the agent when to reach for the skill.
74+
75+
The **body** is loaded on demand via the `loadSkill` tool when the agent decides to use the skill. Write it like documentation for the agent.
76+
77+
## Defining and using a skill
78+
79+
```ts trigger/chat.ts
80+
import { chat } from "@trigger.dev/sdk/ai";
81+
import { skills } from "@trigger.dev/sdk";
82+
import { streamText } from "ai";
83+
import { openai } from "@ai-sdk/openai";
84+
85+
const timeUtilsSkill = skills.define({
86+
id: "time-utils",
87+
path: "./skills/time-utils",
88+
});
89+
90+
export const agent = chat.agent({
91+
id: "docs-chat",
92+
onChatStart: async () => {
93+
chat.skills.set([await timeUtilsSkill.local()]);
94+
},
95+
run: async ({ messages, signal }) => {
96+
return streamText({
97+
model: openai("gpt-4o"),
98+
messages,
99+
abortSignal: signal,
100+
...chat.toStreamTextOptions(),
101+
});
102+
},
103+
});
104+
```
105+
106+
`skills.define({ id, path })` does two things:
107+
108+
1. Registers the skill with the Trigger.dev build system so the CLI **automatically bundles the folder** into your deploy image at `/app/.trigger/skills/{id}/`. No `trigger.config.ts` changes, no build extension — it just works.
109+
2. Returns a `SkillHandle` you use at runtime.
110+
111+
`skill.local()` reads the bundled `SKILL.md` from disk and returns a `ResolvedSkill` with the parsed frontmatter + body + on-disk path.
112+
113+
`chat.skills.set([...])` stores the resolved skills for the current run. `chat.toStreamTextOptions()` spreads them into `streamText` automatically:
114+
115+
- The frontmatter `description` lands in the system prompt under "Available skills:".
116+
- Three tools are added: `loadSkill`, `readFile`, `bash` — scoped per skill.
117+
118+
## What gets auto-injected
119+
120+
When you spread `chat.toStreamTextOptions()` with skills set, the AI SDK call receives three tools:
121+
122+
### `loadSkill({ name })`
123+
124+
Returns the full `SKILL.md` body for the named skill. The model calls this first when it decides a skill is relevant, to load the full instructions.
125+
126+
### `readFile({ skill, path })`
127+
128+
Reads a file inside the skill's bundled folder. Paths are relative to the skill's root and are rejected if they attempt to escape via `..` or absolute paths. Output is capped at 1 MB per call.
129+
130+
Use for reference files and templates that the model should read literally:
131+
132+
```
133+
readFile({ skill: "time-utils", path: "references/timezones.txt" })
134+
```
135+
136+
### `bash({ skill, command })`
137+
138+
Runs a bash command with `cwd` set to the skill's root. Stdout and stderr are captured and returned (each capped at 64 KB per call, with tail truncation). The turn's abort signal propagates — cancelling the run kills the child process.
139+
140+
Use to invoke the skill's bundled scripts:
141+
142+
```
143+
bash({ skill: "time-utils", command: "bash scripts/now.sh America/Los_Angeles" })
144+
```
145+
146+
Script runtime expectations are yours to manage. If your skill uses `extract.py`, your deploy image needs Python — add it via your build config the same way you would for any other task dependency.
147+
148+
## How discovery works in the model
149+
150+
The model sees a short preamble appended to your system prompt:
151+
152+
```
153+
Available skills (call `loadSkill` to read the full instructions before using one):
154+
- time-utils: Compute and format dates/times in arbitrary timezones...
155+
- pdf-processing: Extract text from PDFs, fill forms...
156+
```
157+
158+
When the user asks something that matches a description, the model calls `loadSkill({ name: "time-utils" })` to load the body, then follows the body's instructions — typically by calling `bash` or `readFile` on the bundled scripts.
159+
160+
This is **progressive disclosure**: each skill costs ~100 tokens up front (its one-line description), and only the ones the model actually uses pay the full context cost.
161+
162+
## Mixing skills with custom tools
163+
164+
If you also define your own AI SDK tools, pass them through `chat.toStreamTextOptions()` so the merge is explicit:
165+
166+
```ts
167+
return streamText({
168+
model: openai("gpt-4o"),
169+
messages,
170+
abortSignal: signal,
171+
...chat.toStreamTextOptions({
172+
tools: {
173+
webFetch, // your tool
174+
deepResearch, // your tool
175+
},
176+
}),
177+
});
178+
```
179+
180+
Your tools win on name conflicts. (Pick names that don't collide with `loadSkill` / `readFile` / `bash` to keep things predictable.)
181+
182+
## Bundling
183+
184+
Bundling is **built-in to the CLI** — there's no extension to import. When you run `trigger deploy` or `trigger dev`:
185+
186+
1. esbuild bundles your task code as usual.
187+
2. The CLI forks the indexer locally against the bundled output, collects every `skills.define({ path })` registration.
188+
3. Each skill's folder is copied to `{outputPath}/.trigger/skills/{id}/` via a recursive copy.
189+
4. The existing Dockerfile `COPY` picks up `.trigger/skills/` along with the rest of the bundle — no Dockerfile changes.
190+
191+
If you're running `trigger dev`, the same layout appears in the local dev output directory, so `skill.local()` works the same way.
192+
193+
## Path scoping rules
194+
195+
- `skill.path` always resolves to `${process.cwd()}/.trigger/skills/{id}/` at runtime. Don't hardcode paths elsewhere.
196+
- `readFile` rejects `..` segments and absolute paths — the tool only exposes files inside the skill's own directory.
197+
- `bash` runs with `cwd` set to the skill's root. Inside the script, relative paths resolve against the skill directory.
198+
- Cross-skill access isn't provided — each skill is isolated by design. If two skills need to share data, either duplicate the shared file or consolidate the skills.
199+
200+
## Limitations in Phase 1
201+
202+
- `skill.resolve()` (backend-managed overrides) is not available yet. It throws a "not available in Phase 1, use `.local()`" error. Phase 2 ships dashboard-editable `SKILL.md` text.
203+
- No per-skill metrics in the dashboard yet.
204+
- No Anthropic `/v1/skills` integration — use the portable path today; the Anthropic optimization comes in Phase 4.
205+
206+
## Full example
207+
208+
See `references/ai-chat/src/trigger/skills/time-utils/` in the Trigger.dev monorepo for a working skill that bundles two bash scripts and a reference cheat-sheet, wired into a `chat.agent` that answers timezone questions.
209+
210+
## Related
211+
212+
- [AI SDK cookbook — Agent Skills](https://ai-sdk.dev/cookbook/guides/agent-skills) — the userland pattern we build on
213+
- [Anthropic Agent Skills](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview) — Anthropic's codified version (server-side, optional future integration)

docs/docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@
109109
"ai-chat/patterns/database-persistence",
110110
"ai-chat/patterns/branching-conversations",
111111
"ai-chat/patterns/code-sandbox",
112-
"ai-chat/patterns/human-in-the-loop"
112+
"ai-chat/patterns/human-in-the-loop",
113+
"ai-chat/patterns/skills"
113114
]
114115
},
115116
"ai-chat/client-protocol",

0 commit comments

Comments
 (0)