- MCP Server ToolsUpdated
- Managed MCP with chat()Updated
- Manual MCPUpdated
- MCP Type GenerationUpdated
- MCP Client InputUpdated
- MCP Server Tools: AdvancedNew
- MCP EventsNew
- MCP AppsUpdated
- Serve Tools over HTTPUpdated
- Serve Resources and PromptsUpdated
- Ask for InputUpdated
- Draft Text from a ToolUpdated
- MCP Server TasksUpdated
- MCP Server AuthUpdated
- MCP Server SessionsUpdated
- MCP Server on stdioUpdated
Persistence (Advanced)
- GenerationsUpdated
- Realtime Voice ChatUpdated
- Text-to-SpeechUpdated
- TranscriptionUpdated
- Transcription: AdvancedNew
- Voice CreationUpdated
- Audio RecordingUpdated
- Audio GenerationUpdated
- Image GenerationUpdated
- Image Generation: AdvancedNew
- Video GenerationUpdated
- Video Generation: AdvancedNew
- Live GenerationUpdated
- World GenerationUpdated
- Generation HooksUpdated
- Build your first harnessUpdated
- Host optionsNew
- Send and show mediaUpdated
- Connect clientsUpdated
- Write a client in any languageNew
- Access and tenantsNew
- Run in the terminalUpdated
- Deploy a harnessUpdated
- Send inputs safelyUpdated
- Run a tool without a model
- Durable sessionsUpdated
- Run background work on workersNew
- List, rename, and fork sessionsUpdated
- Lifecycle and retentionNew
- Run side effects onceUpdated
- Wait for a personNew
- Build on the session logUpdated
- Control how a turn endsUpdated
- Store settings per threadUpdated
- Compact a long sessionUpdated
- Fork and reset a threadUpdated
- One log for a session treeUpdated
- Write a pluginUpdated
- Track usage and costUpdated
- Ask before risky tool callsUpdated
- Give your agent coding toolsUpdated
- Sandbox and extend the coding toolsUpdated
- Run the coding tools for many threadsNew
- Undo a turnUpdated
- Build a coding agentUpdated
- Delegate to coding agentsUpdated
- Switch between agent profilesUpdated
- Auth and connectorsUpdated
- Share a thread between peopleUpdated
- Connect model providersUpdated
- Build your own UIUpdated
- Code mode in a harnessUpdated
- Work until a goal is metUpdated
- Run agents from a harnessUpdated
- Hand off a conversationNew
- Add skillsUpdated
- Use MCP serversUpdated
- Give your agent a computerNew
- Use from any MCP clientUpdated
- Use a harness from an editor (ACP)Updated
- OverviewUpdated
- Quick StartUpdated
- ProvidersUpdated
- HarnessesUpdated
- Harness AuthUpdated
- WorkspaceUpdated
- ToolsUpdated
- PolicyUpdated
- Lifecycle & SnapshotsUpdated
- Portable SnapshotsUpdated
- Keep Files After ReloadUpdated
- Save a Named VersionUpdated
- Branch From a VersionUpdated
- Send a Frozen FileUpdated
- Let the Agent Save and ForkUpdated
- Pick Which Files to KeepUpdated
- What a Snapshot StoresUpdated
- Instance DurabilityUpdated
- Durable RunsUpdated
- EventsUpdated
Sandboxes (Advanced)
- Runtime ContextUpdated
- Bring Your Own KeyUpdated
- Keep Keys on the ServerUpdated
- Debug LoggingUpdated
- Multimodal ContentUpdated
- Files APIUpdated
- Per-Model Type SafetyUpdated
- Runtime Adapter SwitchingUpdated
- Tree-ShakingUpdated
- Extend AdapterUpdated
- Extend Adapter: AdvancedNew
- Typed Pre-Configured OptionsUpdated
- Test with a Fake ModelUpdated
- Prompt CachingUpdated
- Mid-Conversation ChangesUpdated
- Approval Flow ProcessingUpdated
Add skills to a harness
Your users keep skills as SKILL.md folders, in the repo and in their home folder, and they want to call them from your agent like any other command. The skills plugin reads those folders, gives the model the list of skills, and makes each skill a /<name> command. A skill that a user adds while the session runs works at once.
The plugin reads the folders with node:fs, so it runs on Node.js. It does not work on Cloudflare Workers.
1. Add the plugin#
npm i @tanstack/ai-skillspnpm add @tanstack/ai-skillsyarn add @tanstack/ai-skillsbun add @tanstack/ai-skillsimport { homedir } from 'node:os'
import { join } from 'node:path'
import { defineHarness } from '@tanstack/ai-harness'
import { openaiText } from '@tanstack/ai-openai'
import { skills } from '@tanstack/ai-skills/harness'
// One time, outside `plugins`, so the sessions share the folder watchers.
const agentSkills = skills({
dirs: ['./.agents/skills', join(homedir(), '.acme', 'skills')],
})
export const assistant = defineHarness({
name: 'acme/assistant',
adapter: openaiText('gpt-6.1-sol'),
plugins: () => [agentSkills],
})- dirs lists the skill folders. Each one holds skill folders with a SKILL.md.
- When two folders have a skill with the same name, the first folder wins. Put the repo folder first, so a project can replace a user's skill.
- A folder that does not exist yet is fine. Its skills show once the user makes it.
2. Call a skill#
/release-notes write them for v2 starts a turn with "Use the release-notes skill: write them for v2". The model loads the skill with load_skill, then does the task. /release-notes alone asks for the skill with no task.
The model also sees the list of skills, so it can load one on its own when a task matches.
Names that are taken#
A skill with the name of another command gets a prefix: a model skill is /skill:model, and /model stays the model picker. Pass reserved with the names that your UI handles itself:
export const cliSkills = skills({
dirs: ['./.agents/skills'],
reserved: ['help', 'exit'],
})/skills lists every skill, its folder, and its command.
Skills that change while the session runs#
The plugin watches the folders, and the commands follow them:
- A new SKILL.md adds its command.
- A removed skill takes its command away.
- An edited description updates the command.
- A broken SKILL.md is left out until it is fixed.
Each change sends a harness.commands.changed event, so a UI can show the new command list at once.
The plugin watches each folder once for each host. All sessions of the harness on that host share the watchers, so many open sessions still use one watcher for each folder. A change only tells the sessions to update. The plugin reads the folders again at the start of each turn.
If the plugin cannot watch a folder, it logs one warning with the folder and the reason. An example is a Linux system with no free file watchers. Changes in that folder still show at the next turn.
Many skills#
The model's list has one line for each skill: the name and the first sentence of the description. When the list is longer than maxCatalogTokens (default 4000), the skills of the earlier folders stay, and the rest leave the model's list. A skill that the user calls with its command always stays in it.
The other options of withSkills pass through, for example maxCatalogTokens and requireApproval:
export const manySkills = skills({
dirs: ['./.agents/skills'],
maxCatalogTokens: 8000,
})What you have now#
- The skills of your folders, in the model's list and as /<name> commands.
- New and changed skills that work in a running session, with no restart.
Next: Agent Skills has the SKILL.md format, and Write a harness plugin shows the ctx.commands API that the plugin uses.