DEV Community

Cover image for AI - Foundations and best practices creating skills
Dailos Rafael Díaz Lara
Dailos Rafael Díaz Lara

Posted on

AI - Foundations and best practices creating skills

🇪🇸 Versión en español

Let's talk about skills for AI agents, and we're doing it off the back of attending an online workshop given by @mouredev on skills a few days ago.

🎥 Full video on YouTube

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.

🤓 Introduction

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:

  • Prompt
  • Rules
  • Tools
  • MCPs
  • Other skills
  • etc.

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.

💻 Creating Skills

📍 Skill location

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.

🎩 Skill header

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 (🔥 required)

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 (🔥 required)

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 (optional)

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 (optional)

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.

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 (optional)

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"
Enter fullscreen mode Exit fullscreen mode
  • allowed-tools (optional) (also known as 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.

For example:

  allowed-tools:
    - internet_browse
    - read_local_file
    - parse_json
Enter fullscreen mode Exit fullscreen mode

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.

For example:

  name: wipe-database-cache
  disable-model-invocation: true
Enter fullscreen mode Exit fullscreen mode
  • metadata (optional)

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.

For example:

  metadata:
    author: "Platform Security Team"
    category: "DevOps / SRE"
    icon: "shield-alert"
    cost_center: "fintech-ops-99"
Enter fullscreen mode Exit fullscreen mode

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"
---
Enter fullscreen mode Exit fullscreen mode
---
# 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"
---
Enter fullscreen mode Exit fullscreen mode
# 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"
---
Enter fullscreen mode Exit fullscreen mode

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

🛢 Body of a skill (the T-I-P-O pattern)

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.
Enter fullscreen mode Exit fullscreen mode
  • # 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.

For example:

  # 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.
Enter fullscreen mode Exit fullscreen mode
  • # 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.

For example:

  # 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")`.
Enter fullscreen mode Exit fullscreen mode
  • # 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.

For example:

  # Outputs

  Return exclusively a valid JSON block containing the compilation summary. Do not include conversational preambles.
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

🔎 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.

For example:

  # 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.
Enter fullscreen mode Exit fullscreen mode
  • # 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.

For example:

  # 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)".
Enter fullscreen mode Exit fullscreen mode
  • # 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.

For example:

  # 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.`
Enter fullscreen mode Exit fullscreen mode
  • # 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.

For example:

  # State Tracking & Memory Logging

  - Before modifying a file, open a `<state>` block to log the original file hash and line count.
  - Maintain a rolling list of modified assets in your tool call parameters to avoid circular file edits.
Enter fullscreen mode Exit fullscreen mode
  • # Chain-of-Thought Auditing

Here, what we do is force the agent to justify each action using specific XML tags (like <thought>) before invoking terminal commands, which makes debugging and auditing the agent's behavior far easier.

For example:

  # Chain-of-Thought Auditing

  - Every tool call must be preceded by a `<reasoning>` block containing:
    1. Why this tool is necessary now.
    2. The expected outcome of the invocation.
Enter fullscreen mode Exit fullscreen mode
  • # Performance & Cost Optimization

Here we can prevent the agent from uncontrollably consuming the API budget (or exhausting the context window), by regulating the amount of text it can read or write in a single iteration.

For example:

  # Performance & Cost Optimization

  - When parsing log files, use range parameters to inspect a maximum of 150 lines per tool call.
  - Avoid re-reading large context files if the content was already logged in the active scratchpad.
Enter fullscreen mode Exit fullscreen mode
  • # Compliance & Regulatory Standards

This section matters when we're working with certain data, since it ensures that the deliverables generated by the agent (such as source code or data reports) comply with strict legal or organizational regulations for the sector (SOC2, GDPR, ISO), or from the company itself.

For example:

  # Compliance & Regulatory Standards

  - All telemetry methods designed by this skill must completely sanitize PII (Personally Identifiable Information).
  - Ensure encryption-in-transit configurations use TLS 1.3 as a baseline.
Enter fullscreen mode Exit fullscreen mode
  • # Workspace Clean-up & Idempotency

With this property we can guarantee the hygiene of the local system, ensuring the agent deletes its temporary execution files and that, if the skill runs twice in a row, the result is identical without duplicating data.

For example:

  # Workspace Clean-up & Idempotency

  - Upon task completion or premature failure, execute an explicit cleanup step to delete `/tmp/cache_*.json`.
  - Design every code refactor to be completely idempotent; running the skill twice must yield zero changes on the second run.
Enter fullscreen mode Exit fullscreen mode
  • # Corporate Style & Terminology Glossaries

Here we can unify business terms and the agent's voice when it generates technical documentation, reports, or text responses aimed at end clients or company leadership.

For example:

  # Corporate Style & Terminology Glossaries

  - Use the term "Client Workspace" instead of "Tenant Folder" across all markdown outputs.
  - Keep tone formal and highly concise; eliminate words like "obviously", "simply", or conversational expressions.
Enter fullscreen mode Exit fullscreen mode
  • # Diagnostic & Telemetry Footprints

This property injects digital signatures and standardized logs into Git commits or the headers of files created by the agent, to uniquely identify which changes were made by the AI and which version of the skill was used.

For example:

  # Diagnostic & Telemetry Footprints

  - Append this precise signature at the end of every modified file header:
    `/* Automated optimization applied via agent-skill: db-optimizer (v2.4.1) */`
Enter fullscreen mode Exit fullscreen mode

🗃 More complex skills

As we develop a skill, it can become increasingly complex, which makes our SKILL.md file practically unworkable due to the amount of information, instructions, examples, or similar content it can contain. The most likely result is:

  • 😰 loading all this content into an agent,
  • 🤯 early context saturation,
  • 🔥 increasing unnecessary token consumption, and
  • 💀 degrading the model's response.

The solution to this lies in a process called Skill Atomization, through which heavy logic, external resources, and referential examples are extracted from the skill, turning it into a declarative orchestrator, keeping the file's content under 50 or 100 lines of text. This makes the skill's initialization speed within the agent very high and increases the system's scalability through independent reference updates.

This atomization process is carried out through two actions: Rigorous structuring and Technical linking.

  • Rigorous structuring

Inside the directory where we've defined the SKILL.md file, we start creating directories with semantically sensible names.

Inside each of those directories, we create the corresponding files that will hold the information we want to extract from the original skill.

The directory structure depends solely on the development team, but there is indeed a certain tendency to have certain established directories, which don't need to be implemented if our skill doesn't require them, but if we do, it's recommended to keep the same naming.

An example of rigorous structuring could be this:

  my-complex-agent-skill/
    ├── SKILL.md                 # (Required   ) Main file (Orchestrator and Frontmatter)
    ├── scripts/                 # (Optional   ) Executable code that offloads heavy logic from the LLM
    │   └── optimize_matrix.py   #               Numerical computation/complex analysis script
    ├── assets/                  # (Optional   ) Static data and validation schemas
    │   └── database_schema.json #               Reference database structure
    ├── references/              # (Optional   ) Style guides, manuals, or dense documentation
    │   └── code_style_guide.md  #               Formatting rules the LLM only reads if needed
    └── examples/                # (Optional   ) Few-Shot Example library (user stories)
        ├── standard_case.md
        └── edge_case_timeout.md
Enter fullscreen mode Exit fullscreen mode
  • Technical linking

Now that we've extracted the excess information from our skill into independent sections of our directory structure, we need to link that content back inside the file that's left.

For this we'll use explicit relative paths to the content we want to reference. Agents are able to read these paths and, through the use of internal tools, can access the files on demand, only when the procedure section requires it.

With this:

  • ✅ we avoid loading all this content into an agent up front,
  • ✅ we avoid early context saturation,
  • ✅ we reduce unnecessary token consumption, and
  • ✅ we avoid early degradation of the model's response.

To do this, there are two commonly used patterns: Direct relative link and Footnote reference link.

  • Direct relative link

    Used for immediate dependencies the agent must always inspect before running a procedure.

    For example:

    # Inputs
    
    This skill requires the project context infrastructure to match the configuration rules specified in the core [Database Architectural Reference Schema](./assets/database_schema.json).
    
  • Footnote reference link

    Used for dependencies that are worth having linked but whose loading happens only in very specific situations, meaning the agent won't load these references into context unless absolutely necessary.

    Beyond that, they're also often used in very long procedures, pushing references to the end of the file and keeping the main text free of clutter.

    For example:

    # Procedure
    
    1. Pull the latest Docker manifest using the environment variables.
    2. Build the staging container and verify the cluster health check endpoints.
    3. In case the build triggers a pipeline schema violation, fetch the resolution steps immediately.
    
    # Error Handling & Edge Cases
    
    * If the server returns a 503 error, verify if your service mesh matches the internal corporate architecture layout.
    
    ---
    
    # Resource Footnotes / Lazy-Load References: ./assets/health_check_spec.json: ./references/pipeline_troubleshooting_guide.md: ./references/corporate_network_mesh_v2.md
    

Now then, how can we start externalizing a skill? Well, we can start by laying out the following steps:

1. Move scripts out of the prompt context (/scripts)

Since explaining to an agent in natural language the operations a script must carry out is complex and consumes tokens unnecessarily, we can create a scripting file in a language of our choice that performs that operation.

In the skill's header, within the allowed-tools section, we'll grant execution permission for the execute_terminal_command command and invoke our script from the skill's text.

For example:

  ---
  name: my-custom-skill
  description: ...
  allowed-tools:
    - execute_terminal_command
  ---

  # Procedure

  1. Do not compute matrix variances manually. Instead, trigger the native optimization script:
    `execute_terminal_command(command="python3 ./scripts/optimize_matrix.py --path=.")`
Enter fullscreen mode Exit fullscreen mode

2. Externalize the example library (/examples)

We need to be very careful with how we use the linked files in this section, since, generally speaking, because they contain extensive, concrete examples, they can take up a lot of space, raising both input and output token consumption if we misuse the examples.

Ideally, each example should be its own independent file, so that linking to it allows loading one or another depending on the agent's needs.

For example:

  # Expected Workflows

  Before formatting your final response, read and analyze the corresponding execution logs inside the example library based on the current workload:
  - For standard microservice queries, read [Standard Flow Case](./examples/standard_case.md).
  - For database connection timeouts, read [Timeout Recovery Case](./examples/edge_case_timeout.md).
Enter fullscreen mode Exit fullscreen mode

3. Load documentation on demand (/references)

API documentation, procedures, etc., can saturate an agent's context very quickly, and on top of that, keeping it updated would also require modifying the skill's content.

If we extract that documentation into independent, isolated files, we can selectively load them only when needed.

For example:

  # Error Handling & Edge Cases

  If a compilation error occurs due to typing differences, do not attempt to guess the syntax. Read the internal reference document [Type Definition Manual](./references/code_style_guide.md) before attempting a second patch rewrite.
Enter fullscreen mode Exit fullscreen mode

🧐 Good (✅) and bad (❌) practices to follow when defining a skill

At the header level

  • Sync the name and the folder: Keep the name field written in kebab-case (lowercase with hyphens) and make sure it exactly matches the name of the containing folder to avoid indexing failures.
  • Keep semantic descriptions tight: Write the description field using action verbs and specific trigger keywords (e.g., "Use when the user requests an API optimization"). This optimizes routing and prevents accidental activations.
  • Apply the Principle of Least Privilege: Declare exclusively, in allowed-tools, the tools the skill strictly needs to achieve its goal, blocking access to dangerous system commands if they aren't required.
  • Force human approval for critical tasks: Set disable-model-invocation: true on destructive or production skills (like deployments or database purges) to force the skill to only be activated via a slash command (/) typed by a person.
  • Specify environment dependencies: Use the compatibility field to list minimum versions of system binaries (e.g., python>=3.10, docker), so the framework halts execution before generating a terminal error.
  • Leverage metadata for governance: Use the metadata block systematically in corporate environments to record the owning team, cost center, and compliance identifiers (e.g., compliance: SOC2).

  • Duplicate skill names: Using the same name field in different SKILL.md files within the repository, which causes collisions and makes the orchestrator ignore components at random.

  • Create generic or ambiguous descriptions: Writing descriptions like description: "An AI assistant to help you write code". This causes the LLM to activate the skill constantly for common tasks, saturating the context window.

  • Grant universal permissions out of laziness: Declaring wildcards for tools or including terminal execution tools (execute_terminal_command) in skills that only need to read data.

  • Ignore version control: Leaving the version field static at 1.0.0 indefinitely, preventing CI/CD pipelines from verifying whether production agents are running the most recently validated behavior.

  • Confuse the role of context: fork: Configuring a skill as context: inline when it needs to process thousands of lines of server logs, causing the main chat to fill up with noise and burn through the token budget.

  • Omit the license field in shared skills: Leaving the license field empty in shared internal repositories, exposing development teams to intellectual property compliance issues.

At the body level

  • Adopt the standard T-I-P-O structure: Always organize the body of the document using the # Target, # Inputs, # Procedure, and # Outputs blocks to guide the model through a deterministic flow.
  • Write imperative, numbered procedures: Use numbered lists (1., 2., 3.) in the procedure section to force sequential, step-by-step reasoning (Chain-of-Thought).
  • Standardize unordered bullets with hyphens: Use only hyphens (-) for lists of constraints, inputs, or tools. Reserve asterisks (*) exclusively for bold (**) or italics (*), making it easier for syntax parsers to read.
  • Externalize dense manuals via footnotes: Apply lazy loading by moving paths to manuals or secondary guides to the bottom of the page ([Reference 1]: ./references/guide.md), keeping the main flow free of filler text.
  • Define strict output contracts: In the # Outputs section, specify the exact response format (e.g., a valid JSON schema or a Markdown table), and explicitly forbid conversational preambles like "Sure, here is your summary".
  • State negative constraints assertively: Dedicate an independent section to safety restrictions (# Guardrails & Safety Constraints) and write prohibitions in uppercase and imperative form (e.g., "NEVER run recursive deletes").

  • Mix bullet styles within the same block: Randomly combining hyphens (-) and asterisks (*) within the same list, which can break context segmentation in certain parsing engines.

  • Write instructions as free-form narrative prose: Writing the procedure as a long paragraph instead of a structured list. Models tend to skip secondary instructions when they're buried in dense blocks of text.

  • Embed extensive source code within the instructions: Pasting complete Python or Bash scripts into the prompt body. This drastically degrades the model's attention and drives up execution costs.

  • Assume the agent knows the current environment: Writing procedures without first defining the # Inputs section, causing the agent to try to guess file paths, variable names, or database environments.

  • Use ambiguous or conditional language: Using phrases like "Please try to optimize the query if you think it is a good idea". Production agents require direct, deterministic instructions (e.g., "Analyze query latency using the EXPLAIN tool").

  • Overload the skill with too many secondary goals: Trying to make a single SKILL.md file handle code analysis, cloud deployments, and database optimization simultaneously. If the scope grows, split it into independent skills.

At the level of robustness, error handling, and scalability

  • Design clear escape routes for tool errors: Dedicate a section to # Error Handling detailing exactly what the agent should do if a tool returns an error, times out, or returns empty data.
  • Delegate numeric and algorithmic processing to scripts: Instead of asking the model to analyze a matrix or a giant JSON via prompts, write a native script in /scripts and have the agent run it, processing only the output summary.
  • Implement modular Few-Shot Examples: Store complex interaction examples in independent files inside an /examples directory and link to them on demand, avoiding saturating the agent's initial context.
  • Verify the integrity of relative links via CI/CD: Implement an automated script in your integration pipeline that validates that all references to ./assets/, ./scripts/, or ./references/ inside your skills actually exist physically and aren't broken.
  • Set halt conditions (Halt Conditions): Explicitly instruct the agent to stop execution immediately and request human supervisor intervention if it encounters critical permission issues (e.g., 403 Unauthorized) or persistent network failures.
  • Keep the skill's core under 100 configuration tokens: Design the main SKILL.md as a lightweight, minimalist conductor that delegates to external resources, guaranteeing ultra-fast startups and optimal memory consumption.
  • Define project-level skills according to that project's needs: When a skill is used in isolated projects, it's not advisable to extract it for global consumption, since any agent would load it regardless of whether it needs it for that particular repository or not. We should only create global skills, or promote a local skill to global, when we're 100% certain that skill will be used by every project.

  • Allow infinite retry loops: Omitting contingency instructions for failures, which causes the agent to try running the same faulty tool over and over in an infinite cycle that burns through your API budget.

  • Hide system error messages: Instructing the model to ignore terminal failures (e.g., 2> /dev/null). If the agent masks errors, diagnosing anomalous behavior in production environments becomes impossible.

  • Hardcode credentials, absolute paths, or secrets: Writing passwords, API tokens, or absolute paths like /Users/username/project in the body of the skill. This breaks the agent's portability across different systems and creates a critical security vulnerability.

  • Blindly trust long-term context memory: Designing a procedure that depends on the agent remembering a piece of data provided at the start of the general chat, especially when operating in context: inline configurations.

  • Validate changes using the production environment itself: Allowing a code refactoring skill to apply direct modifications to the main branch (main) without forcing the prior execution of the local unit test suite on an isolated branch.

  • Update external scripts without updating the skill's manual: Modifying the input parameters of an automation script in ./scripts/ but forgetting to update the corresponding tool-calling rules in the body of the SKILL.md file, causing the agent to invoke commands with outdated syntax.

👋 Final conclusions

It's clear that if there's one thing that hasn't changed with AI's arrival in the world of development, it's that best practices are more necessary now than ever, and a clear sign of that is the special care we need to take when defining our skills.

I hope this content has been useful to you. If you have any questions, feel free to reach out to me. Here are my profiles on X, LinkedIn, and Github.

🙏 Acknowledgments

  • Of course, to @mouredev for taking the time to prepare and share the workshop that was the origin of everything you've read, if you've made it this far.

Top comments (0)