Persistence (Advanced)
- Build your first harness
- Host options
- Send and show media
- Connect clients
- Write a client in any language
- Access and tenants
- Run in the terminal
- Deploy a harness
- Send inputs safely
- Run a tool without a model
- Durable sessions
- Run background work on workers
- List, rename, and fork sessions
- Lifecycle and retention
- Run side effects once
- Wait for a person
- Build on the session log
- Control how a turn ends
- Store settings per thread
- Compact a long session
- Fork and reset a thread
- One log for a session tree
- Write a plugin
- Track usage and cost
- Ask before risky tool calls
- Give your agent coding tools
- Sandbox and extend the coding tools
- Run the coding tools for many threads
- Undo a turn
- Build a coding agent
- Delegate to coding agents
- Switch between agent profiles
- Auth and connectors
- Share a thread between people
- Connect model providers
- Build your own UI
- Code mode in a harness
- Work until a goal is met
- Run agents from a harness
- Hand off a conversation
- Add skills
- Use MCP servers
- Give your agent a computer
- Use from any MCP client
- Use a harness from an editor (ACP)
- Overview
- Quick Start
- ProvidersUpdated
- Harnesses
- Harness Auth
- Workspace
- Tools
- Policy
- Lifecycle & Snapshots
- Portable Snapshots
- Keep Files After Reload
- Save a Named Version
- Branch From a Version
- Send a Frozen File
- Let the Agent Save and Fork
- Pick Which Files to Keep
- What a Snapshot Stores
- Instance Durability
- Durable Runs
- Events
Sandboxes (Advanced)
- Runtime Context
- Bring Your Own Key
- Keep Keys on the Server
- Debug Logging
- Multimodal Content
- Files API
- Per-Model Type Safety
- Runtime Adapter Switching
- Tree-Shaking
- Extend Adapter
- Extend Adapter: Advanced
- Typed Pre-Configured Options
- Test with a Fake Model
- Prompt Caching
- Mid-Conversation Changes
- Approval Flow Processing
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.