Add skills to your agent
A skill is a folder with a SKILL.md in it: a name, a description, and instructions
for one job. The model reads only the names and descriptions up front, and pulls the full
instructions in when a request calls for that job. That is what lets you ship a dozen
detailed procedures without spending a dozen procedures’ worth of context on every run.
Put the folder in your agent, deploy, and the platform delivers it to the harness.
Write a skill
Section titled “Write a skill”my-agent/├── agent.py├── agent.yaml├── pyproject.toml└── skills/ └── release-notes/ └── SKILL.mdThe directory name is the skill name, and it has to match the name in the frontmatter:
---name: release-notesdescription: Turn a list of merged pull requests into release notes grouped by change type. Use when asked for release notes, a changelog, or a summary of what shipped.---
# Release notes
Turn merged pull requests into notes a reader can skim.
## Steps
1. Group the PRs by type: Added, Changed, Fixed, Removed. Drop anything purely internal unless the user asks for the full list.2. Write one line per entry, starting with a past-tense verb. Say what changed for the reader, not which files moved.3. Put breaking changes first, under their own **Breaking** heading.
## Output
Markdown, one `##` heading per group. No preamble and no closing summary.description is the only thing the model sees before deciding whether to open the skill,
so it carries the whole routing decision. Say what the skill does and when to reach for
it. “Release notes helper” gives the model nothing to match a request against; the
description above names the words a user would actually type.
Anything else in the folder (reference files, scripts, templates) ships with it, and the model can read those once the skill is open.
Names are restricted to letters, digits, ., _ and -. A name outside that set fails
the run, because skill names are passed through to the harness as tool-permission entries.
Ship it
Section titled “Ship it”Nothing to declare in the manifest. The skills/ directory’s existence is the declaration:
tanomis agent deploy . --tenant acme-prodYour agent’s code is baked in at /agent, and on each run the platform mirrors
/agent/skills/ into the session workspace at <workspace>/.claude/skills/. That copy is
what the harness reads, because a run cannot read your image directly: the agent’s working
directory is the session workspace, and paths outside it are refused.
Two bounds are enforced before anything is written: 500 files and 5 MB total. Over either one, the run fails and names the cap it hit. It fails rather than truncating on purpose, because a half-present skill tree is the kind of problem that only shows up the first time someone needs the skill.
Claude Agent SDK
Section titled “Claude Agent SDK”Shipping the folder is the whole job. Project scope is on by default, so the CLI discovers the mirror and the model can use the skills on the next run.
To narrow which of your shipped skills are available on a given run, set skills:
def create_agent(input_payload, context): return ( input_payload.get("prompt", ""), { "system_prompt": "You write release notes.", "skills": ["release-notes"], # or "all" }, )Options can be a plain dict, which keeps your module importable without the runtime. See Claude Agent SDK for the full authoring shape.
Changing skills partway through a session is rejected, because it reshapes the
conversation. Start a new session to change the set.
Frontmatter that triggers an approval prompt
Section titled “Frontmatter that triggers an approval prompt”Keep your frontmatter to name and description unless you need more. A SKILL.md that
declares allowed-tools, disallowed-tools, hooks, or an execution context is treated
by the CLI as a skill that needs confirmation, so every invocation raises a
human-in-the-loop approval instead of running straight through. In an unattended run
that surfaces as a denial rather than a pause.
If you need one of those fields, either expect the prompt or name the skill explicitly in
skills, which registers it as pre-allowed.
Deep Agents
Section titled “Deep Agents”You compile your own graph, so the wiring is yours. Pass both backend= and skills=
to create_deep_agent:
def create_agent(input_payload, context): from deepagents import create_deep_agent from deepagents.backends import FilesystemBackend
backend = FilesystemBackend( root_dir=str(context.workspace_root), virtual_mode=True, ) skills_root = context.workspace_root / ".claude" / "skills"
return create_deep_agent( model=context.runtime_context.model, tools=[...], backend=backend, skills=["/.claude/skills/"] if skills_root.is_dir() else None, )skills takes backend paths, not filesystem paths, which is why the backend is rooted at
the workspace and the source is the path within it. virtual_mode=True is what makes
/.claude/skills/... resolve as a backend path; it is a path-mapping mode rather than a
sandbox, so treat it as wiring, not as a containment control.
The is_dir() guard keeps the agent working when there is no skills/ directory, during
local development or if you remove it later.
Check that it loaded
Section titled “Check that it loaded”Every run with skills present emits an agent_skills_loaded event naming what was found,
so “did my skill actually ship?” is answerable from the run’s event stream:
{ "type": "agent_skills_loaded", "source": "agent_service", "data": { "skills": ["release-notes"], "file_count": 1, "size_bytes": 742, "changed": true, "warnings": [] }}skills lists only the directories that can actually produce a skill. A directory with no
SKILL.md, no frontmatter, no description, or a name that does not match its directory
is left out and reported in warnings instead, because a harness would drop it and the
event should not claim otherwise. SKILL.md is case-sensitive.
If the event is missing entirely, the agent shipped without a skills/ directory. Check
that it sits beside agent.yaml and that your build did not exclude it.
Skills, tools, and connectors
Section titled “Skills, tools, and connectors”Skills are instructions, not capabilities. A skill can tell the model how to use a tool,
but it cannot grant access to one. What the agent may call is decided by policy, by the
connectors attached to the version, and by the authorization ceiling, none of which a skill
can widen.
That is worth keeping in mind when you write one: a skill that says “call the GitHub API” does nothing unless a connector supplying that tool is attached.
Related
Section titled “Related”- Session workspace and state: what else lives in the workspace and what survives a resume.
- Claude Agent SDK and Deep Agents: the harness guides.
- Agent manifest: every field, including
delegation.skills.