How to author a NanoClaw skill: SKILL.md anatomy, idempotent apply steps, REMOVE.md, registration tests, and the path from a private skill to an upstream PR.
A skill is a markdown file that Claude Code executes — there’s no engine you must integrate with, so writing one is mostly writing good instructions. The bar upstream holds skills to is docs/skill-guidelines.md, and it boils down to three principles: single responsibility (one independently useful customization with one cohesive reason to change), open for extension, closed for modification (mostly add files; keep reach-ins into existing code to a line or two that calls your own code), and a test for every functional integration point (one that goes red if the wiring is deleted or drifts). This page covers the conventions; the overview explains the model.
Don’t start from a blank file. The /learn skill distills a SKILL.md from a source — a directory, a URL, pasted notes, or the work you just did in a conversation — authored to these same guidelines, and refines an existing skill the same way. Generate the first draft, then tighten it by hand against the conventions below.
Every skill is a directory under .claude/skills/<name>/ with a SKILL.md in the Claude Code skills format:
---name: my-skilldescription: What this skill does and when to use it.---Instructions here...
name — lowercase, alphanumeric plus hyphens, max 64 characters. This becomes the slash command (/my-skill).
description — required, and load-bearing: it’s how Claude decides whether to invoke the skill. Write what it does and when to use it. manage-mounts even lists its trigger phrases: Triggers on "mounts", "mount allowlist", "agent access to directories", "container mounts".
The body is prose an agent can run. Real skills converge on the same shape: numbered apply steps, then a build-and-validate step at the end. Keep it under 500 lines — move detail to separate reference files in the skill directory — and put code in separate files, never inline in the markdown.
Apply must be idempotent: a repeated or interrupted install must complete safely, and a skill that half-applies twice is a bug. /update-skills refreshes installed channel and provider payloads through their structured directives: it overwrites skill-owned files and advances dependency and CLI-manifest pins, while skipping credential prompts, .env writes, wiring, restarts, and ordinary build/test directives. Validation runs afterward, and any unresolved input or agent fallback blocks completion. Three conventions from the in-tree skills:Every step guards itself. Each step states its own skip condition — “skipped if the line is already present”, “overwrite — the branch is canonical” — so a partial apply completes instead of duplicating lines. The trunk channel and provider skills (the ones the setup wizard drives) go further: each mechanical step also carries an nc:<kind> directive fence (copy, append, dep, json-merge, plus run, prompt, operator, env-set) that a deterministic engine can apply from the same document an agent reads. Directives are idempotent by construction (copy overwrites, append skips-if-present, env-set sets-if-absent), and anything the engine can’t apply falls back to the prose beside the fence — so the prose must stand on its own. For a contributed skill the fences are optional: you’re held to the checklist on this page; opting in brings the conformance suite and the directive lint (pnpm exec tsx scripts/skill-directives.ts .claude/skills/<name>/SKILL.md) with it. Grammar and semantics: docs/skill-directives.md.Pinned versions. Dependencies install at an exact version — add-telegram pins @chat-adapter/telegram@<exact-version> in its nc:dep fence; the supply-chain policy rejects ranges and latest. Same for Dockerfile-installed binaries: pin with an ARG <X>_VERSION= line.Present-tense DO steps only. A skill is a standalone artifact with no memory of its own edits. No “earlier versions did X”, no “no changes needed here” — those are flagged anti-patterns. So is a separate VERIFY.md: tests are the verification, and the final step is always pnpm run build plus running the skill’s own tests.
REMOVE.md is required exactly when apply leaves anything behind. A pure instruction-only skill that copies nothing needs none. When it’s required, it must reverse every change apply made — the most common review rejection is an incomplete one. add-telegram’s REMOVE.md is the template; it mirrors the apply step for step:
Delete the barrel import line from src/channels/index.ts (delete, never comment out), then rm every copied file — adapter, helpers, setup step, and tests
Delete the STEPS map entry from setup/index.ts
Remove the skill’s env vars from .env and re-sync to the container
pnpm uninstall the dependency
Rebuild and restart the service
Each removal step is itself idempotent (“skip if already gone”).
Every reach-in with a functional consequence gets a test that goes red if the wiring is deleted or drifts. Tests targeting host code use vitest; container code uses bun:test under container/agent-runner/. Tests travel with the skill — they ship in the skill’s directory (or, for channels, on the registry branch) and apply copies them into the project tree, so they run against the composed system via pnpm test. There’s also a dedicated config, vitest.skills.config.ts, that picks up tests under .claude/skills/**/tests/ — run it with pnpm exec vitest run --config vitest.skills.config.ts.The most common shape is the registration test: import the real barrel, assert the registry contains your entry. add-telegram’s telegram-registration.test.ts imports src/channels/index.ts and asserts telegram is registered — it goes red if the import line is deleted, if the barrel fails to evaluate, or if the adapter package isn’t installed (the unmocked import throws), so it guards the dependency for free. Don’t test your function directly instead: calling your own code bypasses the wiring and stays green when the barrel line is deleted.When the edit lives somewhere you can’t invoke (a main() call, a container boot file), fall back to a structural test. add-ollama-tool ships one that parses the container’s index.ts with the TypeScript compiler API and asserts the mcpServers object literal has an ollama entry pointing at the right module.Two rules apply across all of them: mock only genuinely external services (a fake HTTP server, stubbed creds), never the package you’re guarding; and pnpm run build is an always-on leg, because typed drift slips past runtime tests. Finally, the robustness check from the guidelines: apply your skill with a small, cheap model. If it fumbles the instructions, the instructions are too vague — fix them, don’t blame the model.
A template for a small operational skill — instruction-only, so no REMOVE.md and no integration tests, just idempotent steps. Adapt the phases to your task:
---name: rotate-agent-logsdescription: Archive and truncate NanoClaw agent logs. Triggers on "rotate logs", "logs too big", "archive agent logs".---# Rotate Agent Logs## Pre-flightCheck `ls data/logs/*.log` — if no log file exceeds 50MB, tell the usernothing needs rotating and stop. Every step below is safe to re-run.## 1. ArchiveFor each oversized log, copy it to `data/logs/archive/<name>-<date>.log.gz`(create the directory if missing, skip files already archived today).## 2. Truncate and restartTruncate the rotated logs, then restart the service so file handles reopen:```bashsource setup/lib/install-slug.shlaunchctl kickstart -k gui/$(id -u)/$(launchd_label) # macOSsystemctl --user restart $(systemd_unit) # Linux```## VerifyConfirm the archive files exist and the live logs are small again. Reportboth paths to the user.
For your own install, just create the directory: drop .claude/skills/<name>/SKILL.md into your checkout and Claude Code picks it up as /<name> — no registration, no build step. This is the right home for anything specific to your setup, and where every skill should start.To contribute upstream, your skill needs to be useful beyond your own setup and meet the guidelines bar above. Upstream recognizes four types — channel/provider skills (channel code on the channels registry branch, provider code in the skill’s payload/), utility skills (code files in the skill directory), operational skills (instructions only), and container skills (container/skills/, loaded by the agent inside the container) — each with its own PR path. See Contributing for the flow, and note that channel and provider skills are PRed against main: a provider’s code goes in the skill’s payload/ in the same PR, and maintainers land a channel’s code on the channels branch from your work.
Skills are not sandboxed. Unlike agent containers, a skill runs in your Claude Code session on the host, with whatever permissions that session has — read untrusted skills before running them.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"NanoClaw","colors":{"primary":"#2DD4BF","light":"#5EEAD4","dark":"#14B8A6"},"logo":{"light":"https://mintcdn.com/qwibitai-nanoclaw-8/U-zx_S2Vkyzi0Gli/images/nanoclaw-logotype-dark.svg?fit=max\u0026auto=format\u0026n=U-zx_S2Vkyzi0Gli\u0026q=85\u0026s=7fabf1abf0eb70d18a0235dd67255fcb","dark":"https://mintcdn.com/qwibitai-nanoclaw-8/U-zx_S2Vkyzi0Gli/images/nanoclaw-logotype-dark.svg?fit=max\u0026auto=format\u0026n=U-zx_S2Vkyzi0Gli\u0026q=85\u0026s=7fabf1abf0eb70d18a0235dd67255fcb","href":"https://nanoclaw.dev"},"favicon":"https://mintcdn.com/qwibitai-nanoclaw-8/U-zx_S2Vkyzi0Gli/images/favicon.svg?fit=max\u0026auto=format\u0026n=U-zx_S2Vkyzi0Gli\u0026q=85\u0026s=59a44855795dae3f826d473592ade7a0","appearance":{"default":"dark","strict":true},"background":{"decoration":"gradient"},"navbar":{"primary":{"type":"github","href":"https://github.com/nanocoai/nanoclaw"}},"footer":{"socials":{"github":"https://github.com/nanocoai/nanoclaw","discord":"https://discord.gg/VDdww8qS42","x":"https://x.com/Gavriel_Cohen","youtube":"https://www.youtube.com/@NanoClaw","website":"https://nanoclaw.dev"},"links":[{"header":"Legal","items":[{"label":"MIT License","href":"https://github.com/nanocoai/nanoclaw/blob/main/LICENSE"}]}]},"search":{"prompt":"Search NanoClaw docs..."},"seo":{"indexing":"navigable"},"fonts":{"heading":{"family":"Inter","weight":600},"body":{"family":"Inter","weight":400}},"styling":{"eyebrows":"breadcrumbs","latex":false},"banner":{"content":"[Join the Discord community](https://discord.gg/VDdww8qS42) — get help, share what you're building, or just say hi.","dismissible":true},"contextual":{"options":["copy","view","chatgpt","claude","cursor"],"display":"header"},"metadata":{"timestamp":true},"navigation":{"global":{"anchors":[{"anchor":"Changelog","icon":"clock-rotate-left","href":"/changelog"}]},"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"qwibitai-nanoclaw-8","hasAudioNarration":false,"actualSubdomain":"qwibitai-nanoclaw-8","customDomains":["docs.nanoclaw.dev"],"gitSource":{"type":"github","owner":"glifocat","repo":"nanoclaw-docs","deployBranch":"main","contentDirectory":"","isPrivate":false,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6ad9d4e10febba82de2fda10","collectionId":"e8f44256-8726-4d08-8a35-a07089f9f5e4","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ad9d4e10febba82de2fda10"},"feedback":{"thumbs":true,"edits":true,"issues":true,"agentFeedback":true},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"CONTEXTUAL_FEEDBACK":{"status":"DISABLED"},"CODE_SNIPPET_FEEDBACK":{"status":"DISABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6ad9d4e10febba82de2fda10","clientVersion":"0.0.3971","searchFilterCounts":[{"language":"default","tag":"Documentation","count":49},{"language":"default","tag":"Channels","count":12},{"language":"default","tag":"Operate","count":9},{"language":"default","tag":"Reference","count":8},{"language":"default","tag":"Understand","count":7},{"language":"default","tag":"Templates","count":6},{"language":"default","tag":"Extend","count":5},{"language":"default","tag":"Operating","count":5},{"language":"default","tag":"Build with agents","count":4},{"language":"default","tag":"Get started","count":4}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":false,"enableContextualStarterQuestions":false,"enableAskAiSearchBar":true},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-v7-file-tree","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"pause-universal-search-reconcile","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"per-site-search-delta","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-private-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-nav-generations","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-agent-model-picker","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"isAtlas":false,"googleFontsInlined":false},"customCss":[{"filePath":"custom.css","content":".quickstart-walkthrough [data-component-part=step-title]{font-size:1.25rem;line-height:1.5;font-weight:650;color:#f3f4f6}.quickstart-walkthrough .step-container{padding-bottom:2rem}.quickstart-walkthrough .setup-terminal{margin:1.25rem 0;overflow:hidden;border:1px solid #33413f;border-radius:12px;background:#101715;color:#e5e7eb}.setup-terminal-label{padding:.65rem 1rem;border-bottom:1px solid #33413f;color:#bac8c3;font-size:.75rem;letter-spacing:.02em}.setup-terminal-screens{padding:1rem}.setup-terminal-screen+.setup-terminal-screen{border-top:1px solid #33413f;margin-top:1rem;padding-top:1rem}.setup-terminal-order{color:#bac8c3;font-size:.75rem;margin-bottom:.6rem}.setup-terminal-options,.setup-terminal-question{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.875rem;line-height:1.65;overflow-wrap:anywhere}.setup-terminal-question{display:flex;gap:.7rem;color:#f3f4f6}.setup-terminal-question>span:first-child{color:#5eead4;flex-shrink:0}.setup-terminal-options{list-style:none;padding:0;margin:.8rem 0 0}.setup-terminal-options li{display:flex;gap:.7rem;padding:.5rem .65rem;border-radius:5px}.setup-terminal-options li>span:first-child{flex-shrink:0}.setup-terminal-options .setup-terminal-selected{background:#173b32;color:#d1fae5}.setup-terminal figcaption{padding:.75rem 1rem;border-top:1px solid #33413f;font-size:.75rem;line-height:1.5;color:#bac8c3}.quickstart-walkthrough .setup-check{display:flex;gap:.8rem;margin:1.5rem 0;padding:.8rem 1rem;border-left:3px solid #5eead4;background:#10251f;color:#e5e7eb;font-size:.9375rem;line-height:1.65}.setup-check-icon{color:#5eead4}.setup-check-label{font-size:.75rem;font-weight:600;color:#99f6e4;margin-bottom:.2rem}"}],"page":{"pagePath":"extend/writing-skills","slug":"/extend/writing-skills","pageMetadata":{"title":"Writing skills","description":"How to author a NanoClaw skill: SKILL.md anatomy, idempotent apply steps, REMOVE.md, registration tests, and the path from a private skill to an upstream PR.","keywords":["writing skills","SKILL.md","REMOVE.md","skill authoring","idempotency","registration test","skill-guidelines","vitest","integration points"],"href":"/extend/writing-skills"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"How to author a NanoClaw skill: SKILL.md anatomy, idempotent apply steps, REMOVE.md, registration tests, and the path from a private skill to an upstream PR.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,li:`li`,ol:`ol`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Tip,Warning}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Tip)_missingMdxReference(`Tip`,true);if(!Warning)_missingMdxReference(`Warning`,true);return _jsxs(_Fragment,{children:[_jsxs(_components.p,{children:[`A skill is a markdown file that Claude Code executes — there’s no engine you must integrate with, so writing one is mostly writing good instructions. The bar upstream holds skills to is `,_jsx(_components.a,{href:`https://github.com/nanocoai/nanoclaw/blob/main/docs/skill-guidelines.md`,children:`docs/skill-guidelines.md`}),`, and it boils down to three principles: `,_jsx(_components.strong,{children:`single responsibility`}),` (one independently useful customization with one cohesive reason to change), `,_jsx(_components.strong,{children:`open for extension, closed for modification`}),` (mostly add files; keep reach-ins into existing code to a line or two that calls your own code), and `,_jsx(_components.strong,{children:`a test for every functional integration point`}),` (one that goes red if the wiring is deleted or drifts). This page covers the conventions; `,_jsx(_components.a,{href:`/extend/overview`,children:`the overview`}),` explains the model.`]}),`\n`,_jsx(Tip,{children:_jsxs(_components.p,{children:[`Don’t start from a blank file. The `,_jsx(_components.a,{href:`/reference/skills-catalog#operational-skills`,children:_jsx(_components.code,{children:`/learn`})}),` skill distills a `,_jsx(_components.code,{children:`SKILL.md`}),` from a source — a directory, a URL, pasted notes, or the work you just did in a conversation — authored to these same guidelines, and refines an existing skill the same way. Generate the first draft, then tighten it by hand against the conventions below.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`anatomy-of-a-skill-md`,children:`Anatomy of a SKILL.md`}),`\n`,_jsxs(_components.p,{children:[`Every skill is a directory under `,_jsx(_components.code,{children:`.claude/skills/\u003cname>/`}),` with a `,_jsx(_components.code,{children:`SKILL.md`}),` in the `,_jsx(_components.a,{href:`https://code.claude.com/docs/en/skills`,children:`Claude Code skills format`}),`:`]}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`6`,language:`markdown`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`markdown`,children:_jsxs(_components.code,{language:`markdown`,numberOfLines:`6`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`---`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`name`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`my-skill`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`description`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`What this skill does and when to use it.`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`---`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`Instructions here...`})}),`\n`]})})}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`name`}),` — lowercase, alphanumeric plus hyphens, max 64 characters. This becomes the slash command (`,_jsx(_components.code,{children:`/my-skill`}),`).`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`description`}),` — required, and load-bearing: `,_jsx(_components.strong,{children:`it’s how Claude decides whether to invoke the skill`}),`. Write what it does `,_jsx(_components.em,{children:`and`}),` when to use it. `,_jsx(_components.code,{children:`manage-mounts`}),` even lists its trigger phrases: `,_jsx(_components.code,{children:`Triggers on \"mounts\", \"mount allowlist\", \"agent access to directories\", \"container mounts\"`}),`.`]}),`\n`]}),`\n`,_jsx(_components.p,{children:`The body is prose an agent can run. Real skills converge on the same shape: numbered apply steps, then a build-and-validate step at the end. Keep it under 500 lines — move detail to separate reference files in the skill directory — and put code in separate files, never inline in the markdown.`}),`\n`,_jsx(Heading,{level:`2`,id:`conventions-that-make-a-skill-safe-to-re-run`,children:`Conventions that make a skill safe to re-run`}),`\n`,_jsxs(_components.p,{children:[`Apply must be `,_jsx(_components.strong,{children:`idempotent`}),`: a repeated or interrupted install must complete safely, and a skill that half-applies twice is a bug. `,_jsx(_components.code,{children:`/update-skills`}),` refreshes installed channel and provider payloads through their structured directives: it overwrites skill-owned files and advances dependency and CLI-manifest pins, while skipping credential prompts, `,_jsx(_components.code,{children:`.env`}),` writes, wiring, restarts, and ordinary build/test directives. Validation runs afterward, and any unresolved input or agent fallback blocks completion. Three conventions from the in-tree skills:`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Every step guards itself.`}),` Each step states its own skip condition — “skipped if the line is already present”, “overwrite — the branch is canonical” — so a partial apply completes instead of duplicating lines. The trunk channel and provider skills (the ones the setup wizard drives) go further: each mechanical step also carries an `,_jsxs(_components.strong,{children:[_jsx(_components.code,{children:`nc:\u003ckind>`}),` directive fence`]}),` (`,_jsx(_components.code,{children:`copy`}),`, `,_jsx(_components.code,{children:`append`}),`, `,_jsx(_components.code,{children:`dep`}),`, `,_jsx(_components.code,{children:`json-merge`}),`, plus `,_jsx(_components.code,{children:`run`}),`, `,_jsx(_components.code,{children:`prompt`}),`, `,_jsx(_components.code,{children:`operator`}),`, `,_jsx(_components.code,{children:`env-set`}),`) that a deterministic engine can apply from the same document an agent reads. Directives are idempotent by construction (`,_jsx(_components.code,{children:`copy`}),` overwrites, `,_jsx(_components.code,{children:`append`}),` skips-if-present, `,_jsx(_components.code,{children:`env-set`}),` sets-if-absent), and anything the engine can’t apply falls back to the prose beside the fence — so the prose must stand on its own. For a contributed skill the fences are `,_jsx(_components.strong,{children:`optional`}),`: you’re held to the checklist on this page; opting in brings the conformance suite and the directive lint (`,_jsx(_components.code,{children:`pnpm exec tsx scripts/skill-directives.ts .claude/skills/\u003cname>/SKILL.md`}),`) with it. Grammar and semantics: `,_jsx(_components.a,{href:`https://github.com/nanocoai/nanoclaw/blob/main/docs/skill-directives.md`,children:`docs/skill-directives.md`}),`.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Pinned versions.`}),` Dependencies install at an exact version — `,_jsx(_components.code,{children:`add-telegram`}),` pins `,_jsx(_components.code,{children:`@chat-adapter/telegram@\u003cexact-version>`}),` in its `,_jsx(_components.code,{children:`nc:dep`}),` fence; the supply-chain policy rejects ranges and `,_jsx(_components.code,{children:`latest`}),`. Same for Dockerfile-installed binaries: pin with an `,_jsx(_components.code,{children:`ARG \u003cX>_VERSION=`}),` line.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Present-tense DO steps only.`}),` A skill is a standalone artifact with no memory of its own edits. No “earlier versions did X”, no “no changes needed here” — those are flagged anti-patterns. So is a separate VERIFY.md: tests are the verification, and the final step is always `,_jsx(_components.code,{children:`pnpm run build`}),` plus running the skill’s own tests.`]}),`\n`,_jsx(Heading,{level:`2`,id:`remove-md`,children:`REMOVE.md`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.code,{children:`REMOVE.md`}),` is required `,_jsx(_components.strong,{children:`exactly when apply leaves anything behind`}),`. A pure instruction-only skill that copies nothing needs none. When it’s required, it must reverse `,_jsx(_components.em,{children:`every`}),` change apply made — the most common review rejection is an incomplete one. `,_jsx(_components.code,{children:`add-telegram`}),`’s REMOVE.md is the template; it mirrors the apply step for step:`]}),`\n`,_jsxs(_components.ol,{children:[`\n`,_jsxs(_components.li,{children:[`Delete the barrel import line from `,_jsx(_components.code,{children:`src/channels/index.ts`}),` (delete, never comment out), then `,_jsx(_components.code,{children:`rm`}),` every copied file — adapter, helpers, setup step, `,_jsx(_components.em,{children:`and tests`})]}),`\n`,_jsxs(_components.li,{children:[`Delete the `,_jsx(_components.code,{children:`STEPS`}),` map entry from `,_jsx(_components.code,{children:`setup/index.ts`})]}),`\n`,_jsxs(_components.li,{children:[`Remove the skill’s env vars from `,_jsx(_components.code,{children:`.env`}),` and re-sync to the container`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.code,{children:`pnpm uninstall`}),` the dependency`]}),`\n`,_jsx(_components.li,{children:`Rebuild and restart the service`}),`\n`]}),`\n`,_jsx(_components.p,{children:`Each removal step is itself idempotent (“skip if already gone”).`}),`\n`,_jsx(Heading,{level:`2`,id:`testing-your-skill`,children:`Testing your skill`}),`\n`,_jsxs(_components.p,{children:[`Every reach-in with a functional consequence gets a test that goes red if the wiring is deleted or drifts. Tests targeting host code use vitest; container code uses `,_jsx(_components.code,{children:`bun:test`}),` under `,_jsx(_components.code,{children:`container/agent-runner/`}),`. Tests `,_jsx(_components.strong,{children:`travel with the skill`}),` — they ship in the skill’s directory (or, for channels, on the registry branch) and apply copies them into the project tree, so they run against the composed system via `,_jsx(_components.code,{children:`pnpm test`}),`. There’s also a dedicated config, `,_jsx(_components.code,{children:`vitest.skills.config.ts`}),`, that picks up tests under `,_jsx(_components.code,{children:`.claude/skills/**/tests/`}),` — run it with `,_jsx(_components.code,{children:`pnpm exec vitest run --config vitest.skills.config.ts`}),`.`]}),`\n`,_jsxs(_components.p,{children:[`The most common shape is the `,_jsx(_components.strong,{children:`registration test`}),`: import the real barrel, assert the registry contains your entry. `,_jsx(_components.code,{children:`add-telegram`}),`’s `,_jsx(_components.code,{children:`telegram-registration.test.ts`}),` imports `,_jsx(_components.code,{children:`src/channels/index.ts`}),` and asserts `,_jsx(_components.code,{children:`telegram`}),` is registered — it goes red if the import line is deleted, if the barrel fails to evaluate, `,_jsx(_components.em,{children:`or`}),` if the adapter package isn’t installed (the unmocked import throws), so it guards the dependency for free. Don’t test your function directly instead: calling your own code bypasses the wiring and stays green when the barrel line is deleted.`]}),`\n`,_jsxs(_components.p,{children:[`When the edit lives somewhere you can’t invoke (a `,_jsx(_components.code,{children:`main()`}),` call, a container boot file), fall back to a `,_jsx(_components.strong,{children:`structural test`}),`. `,_jsx(_components.code,{children:`add-ollama-tool`}),` ships one that parses the container’s `,_jsx(_components.code,{children:`index.ts`}),` with the TypeScript compiler API and asserts the `,_jsx(_components.code,{children:`mcpServers`}),` object literal has an `,_jsx(_components.code,{children:`ollama`}),` entry pointing at the right module.`]}),`\n`,_jsxs(_components.p,{children:[`Two rules apply across all of them: mock only genuinely external services (a fake HTTP server, stubbed creds), never the package you’re guarding; and `,_jsx(_components.code,{children:`pnpm run build`}),` is an always-on leg, because typed drift slips past runtime tests. Finally, the robustness check from the guidelines: apply your skill with a small, cheap model. If it fumbles the instructions, the instructions are too vague — fix them, don’t blame the model.`]}),`\n`,_jsx(Heading,{level:`2`,id:`a-minimal-template`,children:`A minimal template`}),`\n`,_jsx(_components.p,{children:`A template for a small operational skill — instruction-only, so no REMOVE.md and no integration tests, just idempotent steps. Adapt the phases to your task:`}),`\n`,_jsx(CodeBlock,{filename:``,numberOfLines:`32`,language:`markdown`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`markdown`,children:_jsxs(_components.code,{language:`markdown`,numberOfLines:`32`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`---`})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`name`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`rotate-agent-logs`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#116329`,\"--shiki-dark\":`#569CD6`},children:`description`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`: `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`Archive and truncate NanoClaw agent logs. Triggers on \"rotate logs\",`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` \"logs too big\"`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`, `}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`\"archive agent logs\"`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#B5CEA8`},children:`.`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`---`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-light-font-weight\":`bold`,\"--shiki-dark\":`#569CD6`,\"--shiki-dark-font-weight\":`bold`},children:`# Rotate Agent Logs`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-light-font-weight\":`bold`,\"--shiki-dark\":`#569CD6`,\"--shiki-dark-font-weight\":`bold`},children:`## Pre-flight`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`Check `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#CE9178`},children:\"`ls data/logs/*.log`\"}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` — if no log file exceeds 50MB, tell the user`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`nothing needs rotating and stop. Every step below is safe to re-run.`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-light-font-weight\":`bold`,\"--shiki-dark\":`#569CD6`,\"--shiki-dark-font-weight\":`bold`},children:`## 1. Archive`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`For each oversized log, copy it to `}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#CE9178`},children:\"`data/logs/archive/\u003cname>-\u003cdate>.log.gz`\"})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`(create the directory if missing, skip files already archived today).`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-light-font-weight\":`bold`,\"--shiki-dark\":`#569CD6`,\"--shiki-dark-font-weight\":`bold`},children:`## 2. Truncate and restart`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`Truncate the rotated logs, then restart the service so file handles reopen:`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:\"```bash\"})}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#DCDCAA`},children:`source`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` setup/lib/install-slug.sh`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`launchctl`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` kickstart`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -k`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` gui/`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`$(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`id`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` -u`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`)`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:`/`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`$(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`launchd_label`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# macOS`})]}),`\n`,_jsxs(_components.span,{className:`line`,children:[_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`systemctl`}),_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-dark\":`#569CD6`},children:` --user`}),_jsx(_components.span,{style:{color:`#0A3069`,\"--shiki-dark\":`#CE9178`},children:` restart`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:` $(`}),_jsx(_components.span,{style:{color:`#953800`,\"--shiki-dark\":`#DCDCAA`},children:`systemd_unit`}),_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`) `}),_jsx(_components.span,{style:{color:`#6E7781`,\"--shiki-dark\":`#6A9955`},children:`# Linux`})]}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:\"```\"})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#0550AE`,\"--shiki-light-font-weight\":`bold`,\"--shiki-dark\":`#569CD6`,\"--shiki-dark-font-weight\":`bold`},children:`## Verify`})}),`\n`,_jsx(_components.span,{className:`line`}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`Confirm the archive files exist and the live logs are small again. Report`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{style:{color:`#1F2328`,\"--shiki-dark\":`#D4D4D4`},children:`both paths to the user.`})}),`\n`]})})}),`\n`,_jsx(Heading,{level:`2`,id:`private-skill-or-upstream-contribution`,children:`Private skill or upstream contribution?`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`For your own install`}),`, just create the directory: drop `,_jsx(_components.code,{children:`.claude/skills/\u003cname>/SKILL.md`}),` into your checkout and Claude Code picks it up as `,_jsx(_components.code,{children:`/\u003cname>`}),` — no registration, no build step. This is the right home for anything specific to your setup, and where every skill should start.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`To contribute upstream`}),`, your skill needs to be useful beyond your own setup and meet the guidelines bar above. Upstream recognizes four types — channel/provider skills (channel code on the `,_jsx(_components.code,{children:`channels`}),` registry branch, provider code in the skill’s `,_jsx(_components.code,{children:`payload/`}),`), utility skills (code files in the skill directory), operational skills (instructions only), and container skills (`,_jsx(_components.code,{children:`container/skills/`}),`, loaded by the agent inside the container) — each with its own PR path. See `,_jsx(_components.a,{href:`/concepts/contributing`,children:`Contributing`}),` for the flow, and note that channel and provider skills are PRed against `,_jsx(_components.code,{children:`main`}),`: a provider’s code goes in the skill’s `,_jsx(_components.code,{children:`payload/`}),` in the same PR, and maintainers land a channel’s code on the `,_jsx(_components.code,{children:`channels`}),` branch from your work.`]}),`\n`,_jsx(Warning,{children:_jsx(_components.p,{children:`Skills are not sandboxed. Unlike agent containers, a skill runs in your Claude Code session on the host, with whatever permissions that session has — read untrusted skills before running them.`})}),`\n`,_jsx(Heading,{level:`2`,id:`related-pages`,children:`Related pages`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/extend/overview`,children:`Skills: how NanoClaw extends itself`}),` — the model: additive installs, the `,_jsx(_components.code,{children:`channels`}),` registry branch, provider payloads, the skill categories`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/concepts/contributing`,children:`Contributing`}),` — repo layout, test configs, and the upstream PR flow`]}),`\n`,_jsxs(_components.li,{children:[_jsx(_components.a,{href:`/reference/skills-catalog`,children:`Skills catalog`}),` — every skill that ships with NanoClaw, for more worked examples`]}),`\n`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Writing skills","description":"How to author a NanoClaw skill: SKILL.md anatomy, idempotent apply steps, REMOVE.md, registration tests, and the path from a private skill to an upstream PR.","keywords":["writing skills","SKILL.md","REMOVE.md","skill authoring","idempotency","registration test","skill-guidelines","vitest","integration points"],"href":"/extend/writing-skills"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"Anatomy of a SKILL.md","slug":"anatomy-of-a-skill-md","depth":2,"children":[]},{"title":"Conventions that make a skill safe to re-run","slug":"conventions-that-make-a-skill-safe-to-re-run","depth":2,"children":[]},{"title":"REMOVE.md","slug":"remove-md","depth":2,"children":[]},{"title":"Testing your skill","slug":"testing-your-skill","depth":2,"children":[]},{"title":"A minimal template","slug":"a-minimal-template","depth":2,"children":[]},{"title":"Private skill or upstream contribution?","slug":"private-skill-or-upstream-contribution","depth":2,"children":[]},{"title":"Related pages","slug":"related-pages","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"qwibitai-nanoclaw-8","siteName":"NanoClaw","title":"Writing skills - NanoClaw","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-10-20T17:08:48.681Z","gitLastModified":"2026-10-20T17:07:48.000Z","originalFileLocation":"extend/writing-skills.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=\"dark\";if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"dark\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"How to author a NanoClaw skill: SKILL.md anatomy, idempotent apply steps, REMOVE.md, registration tests, and the path from a private skill to an upstream PR.\">\u003cmeta data-mint-head name=\"application-name\" content=\"NanoClaw\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Writing skills - NanoClaw\">\u003cmeta data-mint-head property=\"og:description\" content=\"How to author a NanoClaw skill: SKILL.md anatomy, idempotent apply steps, REMOVE.md, registration tests, and the path from a private skill to an upstream PR.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"NanoClaw\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.nanoclaw.dev/extend/writing-skills\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://qwibitai-nanoclaw-8.mintlify.app/_mintlify/api/og/v2?division=Extend\u0026amp;title=Writing+skills\u0026amp;description=How+to+author+a+NanoClaw+skill%3A+SKILL.md+anatomy%2C+idempotent+apply+steps%2C+REMOVE.md%2C+registration+tests%2C+and+the+path+from+a+private+skill+to+an+upstream+PR.\u0026amp;theme=0741fc673de6ce854a783acf\u0026amp;signature=ea56a23091868cb21e627a4bb980461e44124dd4995a236c89debbfc9d6d3d52\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Writing skills - NanoClaw\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"How to author a NanoClaw skill: SKILL.md anatomy, idempotent apply steps, REMOVE.md, registration tests, and the path from a private skill to an upstream PR.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://qwibitai-nanoclaw-8.mintlify.app/_mintlify/api/og/v2?division=Extend\u0026amp;title=Writing+skills\u0026amp;description=How+to+author+a+NanoClaw+skill%3A+SKILL.md+anatomy%2C+idempotent+apply+steps%2C+REMOVE.md%2C+registration+tests%2C+and+the+path+from+a+private+skill+to+an+upstream+PR.\u0026amp;theme=0741fc673de6ce854a783acf\u0026amp;signature=ea56a23091868cb21e627a4bb980461e44124dd4995a236c89debbfc9d6d3d52\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/qwibitai-nanoclaw-8/_VTx1OtifLne3AF6/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"NanoClaw\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#2DD4BF\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.nanoclaw.dev/extend/writing-skills\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/extend/writing-skills.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.nanoclaw.dev/#organization\",\"name\":\"NanoClaw\",\"url\":\"https://docs.nanoclaw.dev\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/qwibitai-nanoclaw-8/U-zx_S2Vkyzi0Gli/images/nanoclaw-logotype-dark.svg?fit=max\u0026auto=format\u0026n=U-zx_S2Vkyzi0Gli\u0026q=85\u0026s=7fabf1abf0eb70d18a0235dd67255fcb\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.nanoclaw.dev#website\",\"name\":\"NanoClaw\",\"url\":\"https://docs.nanoclaw.dev\",\"publisher\":{\"@id\":\"https://docs.nanoclaw.dev/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.nanoclaw.dev/extend/writing-skills#webpage\",\"url\":\"https://docs.nanoclaw.dev/extend/writing-skills\",\"name\":\"Writing skills\",\"description\":\"How to author a NanoClaw skill: SKILL.md anatomy, idempotent apply steps, REMOVE.md, registration tests, and the path from a private skill to an upstream PR.\",\"dateModified\":\"2026-10-20T17:07:48.000Z\",\"isPartOf\":{\"@id\":\"https://docs.nanoclaw.dev#website\"},\"breadcrumb\":{\"@id\":\"https://docs.nanoclaw.dev/extend/writing-skills#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.nanoclaw.dev/extend/writing-skills#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Extend\",\"item\":\"https://docs.nanoclaw.dev/extend/overview\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Writing skills\",\"item\":\"https://docs.nanoclaw.dev/extend/writing-skills\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.nanoclaw.dev/extend/writing-skills#article\",\"headline\":\"Writing skills\",\"name\":\"Writing skills\",\"description\":\"How to author a NanoClaw skill: SKILL.md anatomy, idempotent apply steps, REMOVE.md, registration tests, and the path from a private skill to an upstream PR.\",\"url\":\"https://docs.nanoclaw.dev/extend/writing-skills\",\"mainEntityOfPage\":{\"@id\":\"https://docs.nanoclaw.dev/extend/writing-skills#webpage\"},\"dateModified\":\"2026-10-20T17:07:48.000Z\",\"keywords\":\"writing skills, SKILL.md, REMOVE.md, skill authoring, idempotency, registration test, skill-guidelines, vitest, integration points\",\"publisher\":{\"@id\":\"https://docs.nanoclaw.dev/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.nanoclaw.dev#website\"}}]}\u003c/script>\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"NanoClaw\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"NanoClaw","nav":{"global":{"anchors":[{"anchor":"Changelog","icon":"clock-rotate-left","href":"/changelog"}]},"tabs":[{"tab":"Documentation","groups":[{"group":"Get started","icon":"rocket","pages":[{"title":"What is NanoClaw?","icon":"house","sidebarTitle":"Introduction","tag":"UPDATED","href":"/introduction"},{"title":"Quick start","icon":"bolt","tag":"UPDATED","href":"/quickstart"},{"title":"Installation","icon":"download","href":"/installation"},{"title":"Migrate from v1 or OpenClaw","icon":"route","href":"/migrate-from-v1"}]},{"group":"Channels","icon":"tower-broadcast","pages":[{"title":"Channels overview","href":"/channels/overview"},{"title":"WhatsApp","href":"/channels/whatsapp"},{"title":"Telegram","href":"/channels/telegram"},{"title":"Discord","href":"/channels/discord"},{"title":"Slack","sidebarTitle":"Slack","tag":"UPDATED","href":"/channels/slack"},{"title":"Mattermost","href":"/channels/mattermost"},{"title":"Signal","href":"/channels/signal"},{"title":"iMessage","href":"/channels/imessage"},{"title":"Microsoft Teams","tag":"UPDATED","href":"/channels/teams"},{"title":"Dial","href":"/channels/dial"},{"title":"CLI channel","href":"/channels/cli"},{"title":"More channels","href":"/channels/more-channels"}]},{"group":"Operate","icon":"gauge","pages":[{"title":"Configuration","tag":"UPDATED","href":"/operate/configuration"},{"title":"The ncl admin CLI","href":"/operate/ncl-cli"},{"title":"Credentials","tag":"UPDATED","href":"/operate/credentials"},{"title":"Credential gateways","tag":"NEW","href":"/operate/gateways"},{"title":"Hardening","href":"/operate/hardening"},{"title":"Get started with the Echo hardened runtime","sidebarTitle":"Hardened runtime","href":"/guides/echo-hardened-runtime"},{"title":"Upgrading","href":"/operate/upgrading"},{"title":"Uninstall NanoClaw","icon":"trash","href":"/operate/uninstall"},{"title":"Troubleshooting","href":"/operate/troubleshooting"}]},{"group":"Build with agents","icon":"wand-magic-sparkles","pages":[{"title":"Your first agent","href":"/guides/first-agent"},{"title":"Customize an agent","href":"/guides/customize-an-agent"},{"title":"Schedule recurring work","href":"/guides/scheduled-tasks"},{"title":"Build a multi-agent team","href":"/guides/multi-agent-swarm"}]},{"group":"Templates","icon":"layer-group","pages":[{"title":"Getting started with a template","tag":"UPDATED","href":"/templates/getting-started"},{"title":"Templates","href":"/templates/overview"},{"title":"Using a template","tag":"UPDATED","href":"/templates/using-templates"},{"title":"Updating a stamped agent","href":"/templates/updating"},{"title":"Building a template","href":"/templates/building"},{"title":"Submitting a template","href":"/templates/submitting"}]},{"group":"Extend","icon":"puzzle-piece","pages":[{"title":"Skills: how NanoClaw extends itself","href":"/extend/overview"},{"title":"Give agents tools","href":"/extend/tools"},{"title":"Agent providers","tag":"UPDATED","href":"/extend/providers"},{"title":"Agent self-modification","description":"What agents can change about themselves — editing their own memory freely, and requesting packages or MCP servers through admin approval.","href":"/extend/self-modification"},{"title":"Writing skills","href":"/extend/writing-skills"}]},{"group":"Understand","icon":"lightbulb","pages":[{"title":"Architecture","description":"How NanoClaw works from the top down: a host process that routes messages between channel adapters and per-session agent containers, with SQLite files as the only transport.","href":"/concepts/architecture"},{"title":"Entity model","href":"/concepts/entity-model"},{"title":"Agent memory","href":"/concepts/agent-memory"},{"title":"Isolation levels","tag":"UPDATED","href":"/concepts/isolation-levels"},{"title":"Container lifecycle","tag":"UPDATED","href":"/concepts/container-lifecycle"},{"title":"Security model","href":"/concepts/security"},{"title":"Contributing to NanoClaw","href":"/concepts/contributing"}]},{"group":"Changelog","icon":"clock-rotate-left","pages":[{"title":"Releases","icon":"tag","href":"/changelog/index"},{"title":"Docs updates","icon":"file-pen","href":"/changelog/docs-updates"}]}]},{"tab":"Reference","pages":[{"title":"Reference","href":"/reference/ncl-cli"}]}]},"title":"Writing skills - NanoClaw","buildId":"dpl_DFJrtvUi8HS5F9nkooKuDJsuD5BM","deploymentId":"dpl_DFJrtvUi8HS5F9nkooKuDJsuD5BM","dynamics":["10e4515597"],"chunks":["BannerMarkdown-BVNUlwBc.js","dist-aeztFa9U.js","lib--scc4DXi.js","lib-B8HbPgcC.js","clsx-rL8ZCCw4.js","html-void-elements-mCSPHezU.js","katex-YMM-FMeD.js","marked.esm-CZWythHk.js"]}