AI - Foundations and good practices creating skills A developer has compiled a set of best practices for defining skills for AI agents, drawing on an online workshop by @mouredev and additional research. The guide covers skill directory conventions such as ./.agents/skills and ./.claude/skills, required SKILL.md files, and YAML header fields including name, description, license, version, and context. It emphasizes precise, structured skill definitions to minimize model hallucination and warns that loading skills into session context should be controlled. Let's talk about skills for AI agents, and we're doing it off the back of attending an online workshop given by @mouredev https://dev.to/mouredev on skills a few days ago. The workshop was really interesting, so much so that I was left wanting more, so I went digging a bit deeper, not so much into what skills do or what they're for, but into the best practices for defining them. The result is this post, where I try to organize the main ideas I've been gathering and putting in order. I hope they're as useful to you as they've been to me. Without further ado... let's get started. Skills are used to automate repetitive processes whose procedure can be described with a high degree of precision. The information inside a skill has to be perfectly structured and fine-tuned so that the model has the least possible chance of making up results. The content of a skill can include: When we start up an agent, it already knows which skills are available; however, skills can be invoked manually by the developer explicit invocation , or automatically by the agent itself implicit invocation , once we've made it aware that they exist. Every skill we use gets loaded into the context of the current session, so it's important to control excessive use when they're not needed. We'll create skills inside the ./.agents/skills directory if they're only for the current project , or in ~/.agents/skills if they're global for every agent running on the machine . 🔎 NOTE This is the standard across the vast majority of agents, but just in case, it's always worth checking how a specific agent handles skills. For example, in Claude Code CLI, if we manually create a skill in our repo, it won't be detected even after restarting the agent. Why? Because for this agent they need to be created in ./.claude/skills . It's not a problem with the skill or its definition, but with where Claude Code looks for it. Each skill must be created inside a directory specific to that skill. Therefore, it's essential that the skill's directory name matches the name we give the skill itself, since that's the name by which the agent will invoke it. On top of that, a SKILL.md file — containing the skill's definition — must always exist inside that directory. Every skill must have a header that describes it. This header is identified by the opening and closing --- characters typical of YAML. ⚠️ IMPORTANT Even though the skill is defined using Markdown, the content of the skill's header must be written in YAML. The content of that header must include the following fields: name Must be between 1 and 64 alphanumeric characters, lowercase, in kebab-case format. It cannot start or end with a hyphen, and it can never contain two or more consecutive hyphens -- . Finally, it must match the name of the directory where the skill is defined, since that's what's used to invoke the skill /skill-name . The regex for this name is: ^ a-z0-9 + - a-z0-9 + $ . description Must be between 1 and 1024 characters. It has to be specific enough for the agent to be able to read its content and determine whether it matches what the user is looking for. To do that, the description should contain specific phrases, verbs, or concrete use cases, for example: Use when the user asks to review code or optimize database queries . license Used to define the legal usage context of the skill or its usage permissions. When setting its value, it's considered good practice to follow the SPDX Short-form License Identifiers standard. The most common values tend to be: MIT , Apache-2.0 , GLP-3.0 , and Proprietary or Commercial . version Contains the string that defines the semantic versioning of the skill, for example: 1.0.0 . It's usually used to configure pipelines, deployment updates, or tracking within certain ecosystems. context inline | fork Lets us define how the skill runs in relation to the agent's main conversation thread. This aspect matters at the level of memory orchestration configuration and environment isolation when the skill is invoked. By default, if this field isn't defined, the skill runs in line with the ongoing conversation; however, there are values we can assign to this field to change that behavior: inline default value The skill runs directly within the current conversation window. The instructions, examples, and tools defined inside SKILL.md are added directly to the current context, and the model retains them in full for use in later parts of the conversation. This setting is recommended when, upon starting multiple tasks that need to work together, the skill needs to know the entire history, tone, and specific references the user has made in order to operate correctly. With all that said, if we choose this option, we need to set guardrails at the description level so the skill knows exactly when it should step back to gather information and then return to where it left off. fork sub-agent or isolated sandbox mode In this mode, you could think of it as creating a metaphorical "child process," where the agent temporarily pauses the main conversation thread, copies the relevant information from the current state, and spins up an isolated sub-agent to process exclusively the task assigned by the skill. At the memory level, the sub-agent created doesn't read the entire memory or history of the main conversation; instead, it prevents the model from getting distracted from that conversation by avoiding drift caused by mixing in other queries. At the token consumption level, since it doesn't take the full main conversation history but rather a small, very specific section, we can reduce the volume of input tokens and get a cheaper, faster execution. Once the skill has completed its task, it gathers, cleans up, and distills a summary of the result and injects it into the main conversation thread before closing the "child process" in which it was operating. With all that said, if we choose this option, we need to be aware that the instructions must be fully self-contained, since the skill won't have access to the complete conversation history. Comparison table | Feature / Behavior | context: inline Default | context: fork | |---|---|---| | Work area | Main conversation window | Isolated sub-agent | | History visibility | Full history preserved | Hidden or heavily restricted | | Token consumption | High grows with conversation length | Low optimized for skill-specific data | | Output obtained | Continuous as the conversation goes on | A simple, structured summary | | Main use case | Interactive assistant e.g., copywriting | Heavy background work e.g., code review | compatibility This field acts as an environment check, making sure the machine where the skill or execution engine is running has the hardware capabilities, software packages, or required LLM engine requirements needed to run the instructions defined inside the skill. For example, we can define the model's minimum reasoning requirements, as well as operating system constraints, CLI packages, or required binaries. If the system scans the skill and detects a compatibility issue, the skill is disabled or hidden to prevent runtime errors. For example: compatibility: model: " =gpt-4o" os: "linux, darwin" dependencies: - "python =3.10" - "ffmpeg" allowed-tools allow-tools This field is defined by a list of values that strictly establish or limit which tools the agent is allowed to use when the skill runs. When defining the content of this property, you should keep the Principle of Least Privilege in mind, so that if a skill needs to read local documentation, it shouldn't be allowed to execute terminal commands. When a skill that has this property defined is executed, the agent automatically suspends/disables any tool not specified in the list, so that even if the model hallucinated and tried to invoke one of the disallowed tools, the model's orchestrator would block it. One of the main advantages of this field is that it prevents excessive consumption caused by the unnecessary use of expensive tools or the execution of dangerous operations files, databases, infrastructure, etc. that aren't clearly authorized. allowed-tools: - internet browse - read local file - parse json disable-model-invocation true | false This field is used to configure the level of "awareness" the model has about whether the skill in question exists or not. When this field isn't defined, or is left at its default value false , when the agent starts up it will have the skill in its list of available skills, and if the user says Check my server logs , the model will analyze the skill's description and, if it matches the goal of the request, it will start running the skill. If this option is set to true , the model will completely ignore this skill, no matter how the agent is being used. The only way to use it is through explicit invocation of that skill, i.e., /skill-name . This setting is particularly useful for destructive workflows, ones that apply irreversible changes, or ones that carry high risk for the system, where a misinterpretation by the model could lead to accidental loss of valuable data or unauthorized system changes. name: wipe-database-cache disable-model-invocation: true metadata This field is an unstructured dictionary designed for development-specific tasks, where we can add additional information related to the skill. It acts as a communication bridge between different platforms, code editors, and frameworks that need a way to define additional information relevant to each one, without breaking the open standard for skill definitions. One of the main uses given to this field is to display the skill's information in a human-friendly format in applications or tools that manage skills. On the other hand, when skills aren't public, this field can be used for tracking their development at a corporate level. metadata: author: "Platform Security Team" category: "DevOps / SRE" icon: "shield-alert" cost center: "fintech-ops-99" Some complete examples of skill headers could be the following: This example uses 'context: fork' so it runs in a secure thread, with low token consumption, to perform heavy backend-level validation. Also, since it runs autonomously, it can be invoked automatically by the model and can include the CLI tools needed to carry out its task. --- name: kubernetes-manifest-validator description: Use this skill when the user provides Kubernetes YAML files, Helm charts, or K8s deployment manifests and requests syntax validation, security linting, or API deprecation checks. license: Apache-2.0 version: 2.4.1 context: fork compatibility: model: " =gpt-4o" os: "linux" dependencies: - "kubeconform =0.6.0" - "trivy =0.45.0" allowed-tools: - read local file - execute terminal command - write local file disable-model-invocation: false metadata: team: "SRE-Core" environment: "staging-validation" severity-tier: "medium" --- --- This example uses 'context: inline' so it runs within the agent's context window, since it needs access to the full conversation history. The tool restriction lets the model run the skill during the conversation. name: market-competitor-analyzer description: Use this skill when the user asks for competitive intelligence, financial market trends, stock ticker comparisons, or landscape analysis regarding corporate competitors. license: MIT version: 1.0.3 context: inline compatibility: model: " =gpt-4-mini" os: "any" dependencies: - "python-yfinance =0.2.0" allowed-tools: - web search - fetch url content - render data chart disable-model-invocation: false metadata: department: "Product-Strategy" billing-code: "mkt-res-2026" ux-icon: "trending-up" --- This skill involves the irreversible deletion of data, so it includes the 'disable-model-invocation: true' setting, making it only invokable by a human. Additionally, the 'compatibility' field restricts it to the specific database it can operate on. --- name: production-database-purger description: Mandatorily hidden from automatic routing. This skill safely drops stale tables, truncates high-volume log schemas, and runs database vacuuming routines on production clusters during maintenance windows. license: Proprietary version: 4.0.0-rc1 context: fork compatibility: model: " =o1-preview" os: "linux, darwin" dependencies: - "postgresql-client-16" - "aws-cli-v2" allowed-tools: - execute sql query - fetch vault secret disable-model-invocation: true metadata: compliance-required: "SOC2-Type-II" requires-human-approval: true criticality: "high" slack-alert-channel: " prod-ops-logs" --- At this point, it's worth noting that even though these fields may belong to a standard, not every agent understands them or uses the same naming to achieve the same goal. The following table shows which agent accepts which header field: Header fields for SKILL.md supported by agent | Header Field | Claude Code CLI | Claude.ai Web | Claude API | AutoGen Studio | CrewAI Core | LangGraph Engine | OpenCode CLI | Aider CLI | CodeRabbit CLI | |---|---|---|---|---|---|---|---|---|---| | name | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | description | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | version | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | | license | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | | context | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ mode | ❌ | ✅ | | allowed-tools | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ perms | ✅ | ✅ | | disable-model-invocation | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ disable | ❌ | ✅ | | compatibility | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ deps | ❌ | ❌ | | metadata | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | T-I-P-O Once we've finished defining the skill's header, it's time for the body. ⚠️ IMPORTANT Unlike the header section, the body of a skill is written using Markdown. What we define here is a compendium of semantic directives, structured in a certain way, that the model is able to internalize as operational instructions. This block is decisive for whether the agent behaves as deterministically as possible, or starts having critical hallucinations during execution. Despite the importance of this block, since it's open source, there's no single, strict structure that universally forces us to define a skill's body in a particular way. That said, in enterprise environments there's a growing convergence toward using the pattern known as T-I-P-O Targets , Inputs , Procedure , and Outputs , which is the de facto minimum accepted to guarantee a baseline of determinism in the model. Target or Objective Defines what this skill's execution is aiming for and what the expected final goal is. When an agent gets lost in a loop of tool calls, it re-evaluates its progress by comparing it against what's defined in this section. Example: Target The absolute objective of this skill is to locate deprecated API endpoints inside the repository, upgrade them to the current SDK version, and ensure the test suite passes with zero errors. Inputs or Prerequisites Defines the exact variables, files, or data formats the agent must receive before starting work. This section matters because it stops the agent from "guessing" or making up data; that way, if the current context doesn't contain these elements, the agent knows it should stop and ask for them. Inputs This skill requires two primary artifacts from the active workspace context: 1. legacy endpoints.json - A manifest listing the raw endpoints. 2. current sdk spec.yaml - The up-to-date OpenAPI schema reference. Procedure or Execution Steps Defines a numbered, sequential list that the agent must follow strictly, step by step. With this, we manage to break the model's reasoning down into manageable subtasks Chain-of-Thought while forcing the agent to follow numbered steps, reducing possible hallucinations in workflows that use multiple tools. Procedure 1. Parse the legacy endpoints.json file using the read file tool. 2. For each endpoint listed, locate its definition in the codebase using grep search . 3. Replace the deprecated syntax with the new methods specified in current sdk spec.yaml . 4. Run the local testing pipeline using execute terminal command command="npm test" . Outputs or Expected Output Format Defines the output contract upon completion of the skill's execution. Here we indicate whether we want a JSON, a Markdown document, a Markdown code block, etc., as well as the exact structure of the final response. This way, the agent's outputs will be easily readable by other automated scripts or by the user, without containing filler text. Outputs Return exclusively a valid JSON block containing the compilation summary. Do not include conversational preambles. Full example of a defined skill: --- name: api-migration-tool description: Use when the user requests an automated upgrade of legacy API endpoints. version: 1.0.0 context: fork allowed-tools: - read file - write file - grep search - execute terminal command disable-model-invocation: false --- Target Migrate deprecated microservice API routing schemas to the v2 standard. Inputs - Workspace variable: target directory - Source config file: api routing.conf Procedure 1. Scan the target directory for any .conf files. 2. Cross-reference keys against the official version 2 documentation wrapper. 3. Apply the structural rewrites into a new temporary branch. 4. Validate the syntax integrity. Outputs Provide a markdown table summarizing: - The file paths modified. - The original lines of code. - The rewritten replacement chunks. 🔎 NOTE As a final note regarding the body of a skill, when we're in a section where we implement an unordered list, we sometimes find that both the hyphen - and the asterisk are used to mark a list item. While it's true that, computationally, it makes no difference to the model, cleanliness and order matter here from a DevEx standpoint, so the use of the hyphen - is encouraged for unordered list items over any other character. These are the basic sections a skill should have. Beyond these, if our application's needs require defining additional sections, we're free to do so as long as it helps fine-tune the skill's use even further. Some additional sections beyond those proposed by the T-I-P-O pattern are the following: Guardrails & Safety Constraints Defines critical restrictions by listing absolute limits, forbidden behaviors, and areas the agent must never touch. This is the main line of defense against data destruction or security breaches. Guardrails & Safety Constraints - NEVER pass raw string variables directly into bash command lines without character escaping. - Do not modify or read any files inside the hidden .git/ or .vault/ internal directories. User Verification Gates Establishes human approval checkpoints, explicitly defining which specific actions must mandatorily halt the agent's autonomous flow to require visual "Ok" or manual confirmation from the user in the chat. User Verification Gates - Trigger: Prior to executing any database truncation or dropping an index. - Action: Halt the script, render the specific SQL payload to the user, and ask: "Do you confirm the execution of this database migration? y/n ". Escalation Protocols This section prevents the agent from getting stuck trying to solve problems that exceed its permission capabilities, instructing it on when to give up and hand the case off to a human user. Escalation Protocols - If a connection timeout error occurs more than 3 consecutive times on port 5432, halt automation. - Do not attempt to guess credentials. Output: CRITICAL Network isolation detected. Escalating ticket to SRE team. State Tracking & Memory Logging With this section we force the model to structure its thought process and internalize state changes in local variables before calling the next tool, solving memory loss in very long workflows. State Tracking & Memory Logging - Before modifying a file, open a