If you’ve been using Claude and wondering how it suddenly knows how to generate a perfectly formatted PowerPoint or fill out a PDF form without you explaining anything, that’s Agent Skills doing the work. It’s one of Anthropic’s newer features and it’s surprisingly underused by the network automation community, which is a shame because it’s genuinely well-suited for what we do.

In this post I’m going to explain what Skills are, how they actually work under the hood, and then build a real network automation skill from scratch, one that gives Claude the procedural knowledge to query NetBox, pull device configs, and follow your team’s runbook steps, automatically, every time, without you explaining it in every single prompt.


What Is a Skill?

A Skill is a folder. That’s it. A folder with a SKILL.md file inside it, and optionally some scripts and reference files alongside it.

netbox-query-skill/
├── SKILL.md
├── scripts/
│   └── query_netbox.py
└── REFERENCE.md

The SKILL.md file has two parts: a YAML frontmatter block at the top that tells Claude what the skill is and when to use it, and a Markdown body that contains the actual procedural instructions.

When you give Claude access to a skill, it doesn’t load the whole thing into context upfront. It uses a progressive disclosure model, and understanding that is the key to building skills that actually work well.


How Skills Load: The Three Levels

This is the part most blog posts skip, and it matters a lot for how you write your SKILL.md.

Level 1: Metadata (Always Loaded)

Only the YAML frontmatter gets loaded at startup. It costs around 100 tokens per skill. This is how Claude knows the skill exists and when it should reach for it.

---
name: network-automation
description: >
  Use for network automation tasks: querying NetBox for device inventory,
  IP addresses, and VLANs; pulling running configurations from devices;
  and following network change runbook procedures. Trigger when the user
  asks about specific devices, IP lookups, config retrieval, or network changes.  
---

That description is doing serious work. It’s not documentation, it’s the trigger condition. Claude reads it to decide whether to activate the skill at all. Write it like you’re writing a routing rule, not a README.

Level 2: Instructions (Loaded When Triggered)

When Claude decides the skill is relevant, it reads the full body of SKILL.md into context via bash. This is your procedural knowledge, step-by-step instructions, workflows, gotchas, and anything Claude needs to know to do the job correctly.

Level 3: Scripts and Resources (Loaded Only As Needed)

Your skill folder can contain Python scripts, reference files, schemas, and anything else Claude might need. These sit on the filesystem and only get touched when Claude’s instructions tell it to run or read them. A script that runs via bash never loads its source code into the context window, only the output comes back. This is what makes skills token-efficient for heavy operations.


Prerequisites

  • Claude.ai Pro/Max/Team/Enterprise plan with code execution enabled (Settings → Features)
  • OR Claude API access with the code-execution-2025-08-25, skills-2025-10-02, and files-api-2025-04-14 beta headers
  • A NetBox instance with API access
  • Python 3.10+ with requests and netmiko installed in your skill’s environment

API surface gotcha: Skills on the Claude API run with no network access. Your scripts cannot make outbound HTTP calls or SSH connections from within the API sandbox. If you need live network access from scripts, use Claude Code (full network access) or Claude.ai (configurable). Know your surface before you build.


Building the Skill: Folder Structure

network-automation-skill/
├── SKILL.md              ← Required. Instructions + YAML frontmatter.
├── REFERENCE.md          ← NetBox endpoint reference, loaded on demand.
└── scripts/
    ├── query_netbox.py   ← Runs via bash, output returned to Claude.
    └── get_device_config.py

The SKILL.md File

This is the heart of it. Let’s build it section by section.

---
name: network-automation
description: >
  Use for network automation tasks involving network devices and infrastructure.
  Trigger when the user asks to: query NetBox for devices, IPs, VLANs, or prefixes;
  retrieve running configurations from routers or switches; look up inventory data;
  or follow network change and troubleshooting procedures.
  Do not trigger for general networking theory questions.
---

# Network Automation Skill

This skill gives you access to live network inventory and device data.
Follow the procedures below exactly. Do not guess at device hostnames,
IP addresses, or VLAN IDs, always verify against NetBox first.

---

## Rule #1: NetBox Before You Touch Anything

Before connecting to any device, query NetBox to confirm:
- The device exists and its status is "active"
- You have the correct hostname and management IP
- The device type so you use the right connection parameters

If NetBox returns no results or the device is not "active", stop and
report back to the user before proceeding.

---

## Querying NetBox

Run the NetBox query script with the appropriate endpoint and filters:

\```bash
python scripts/query_netbox.py --endpoint dcim/devices --params '{"site": "nyc-dc1", "status": "active"}'
\```

Common endpoints and when to use them:

| What You Need          | Endpoint              | Example Filter                        |
|------------------------|-----------------------|---------------------------------------|
| Device inventory       | dcim/devices          | {"site": "nyc-dc1", "role": "router"} |
| IP address lookup      | ipam/ip-addresses     | {"address": "10.0.0.1/24"}            |
| VLAN lookup            | ipam/vlans            | {"vid": 200}                          |
| Prefix/subnet info     | ipam/prefixes         | {"prefix": "10.10.0.0/24"}            |
| Interface details      | dcim/interfaces       | {"device": "core-rtr-01"}             |

For a full list of available endpoints and filter parameters,
read REFERENCE.md.

---

## Pulling Device Configurations

Only proceed here after confirming the device in NetBox.

Run the config retrieval script with the hostname and device type:

\```bash
python scripts/get_device_config.py --hostname core-rtr-01 --device-type cisco_ios
\```

Supported device types:
- cisco_ios
- cisco_nxos
- cisco_asa
- juniper_junos

If the script returns an authentication or timeout error, do not retry
automatically. Report the exact error message to the user.

---

## Change Runbook

When making or verifying a network change, follow these steps in order.
Do not skip steps.

1. Confirm the device in NetBox (status = active)
2. Pull the current running config and summarize relevant sections
3. State what you are about to change and why
4. Wait for explicit user confirmation before proceeding
5. If the change is destructive, call out the blast radius clearly

---

## What This Skill Cannot Do

- Push configuration changes to devices (read-only)
- Access devices not listed in NetBox
- Make assumptions about credentials, these come from environment variables only

The Scripts

query_netbox.py

#!/usr/bin/env python3
"""
Query the NetBox API and print results as formatted JSON.
Used by the network-automation skill.
"""
import argparse
import json
import os
import sys
import requests
from dotenv import load_dotenv

load_dotenv()

NETBOX_URL = os.getenv("NETBOX_URL")
NETBOX_TOKEN = os.getenv("NETBOX_TOKEN")

def query(endpoint: str, params: dict) -> dict:
    if not NETBOX_URL or not NETBOX_TOKEN:
        return {"error": "NETBOX_URL or NETBOX_TOKEN not set in environment."}

    headers = {
        "Authorization": f"Token {NETBOX_TOKEN}",
        "Content-Type": "application/json",
    }
    url = f"{NETBOX_URL}/api/{endpoint}/"
    try:
        r = requests.get(url, headers=headers, params=params, timeout=10)
        r.raise_for_status()
        return r.json()
    except requests.exceptions.RequestException as e:
        return {"error": str(e)}

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--endpoint", required=True, help="NetBox API endpoint")
    parser.add_argument("--params", default="{}", help="JSON filter params")
    args = parser.parse_args()

    params = json.loads(args.params)
    result = query(args.endpoint, params)
    print(json.dumps(result, indent=2))

get_device_config.py

#!/usr/bin/env python3
"""
Retrieve running configuration from a network device via SSH.
Used by the network-automation skill.
"""
import argparse
import os
import sys
from netmiko import ConnectHandler, NetmikoTimeoutException, NetmikoAuthenticationException
from dotenv import load_dotenv

load_dotenv()

def get_config(hostname: str, device_type: str) -> str:
    device = {
        "device_type": device_type,
        "host": hostname,
        "username": os.getenv("DEVICE_USERNAME"),
        "password": os.getenv("DEVICE_PASSWORD"),
        "timeout": 15,
    }
    try:
        with ConnectHandler(**device) as conn:
            return conn.send_command("show running-config")
    except NetmikoAuthenticationException:
        return f"ERROR: Authentication failed for {hostname}. Check credentials."
    except NetmikoTimeoutException:
        return f"ERROR: Connection to {hostname} timed out. Is the device reachable?"
    except Exception as e:
        return f"ERROR: {str(e)}"

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--hostname", required=True)
    parser.add_argument("--device-type", default="cisco_ios")
    args = parser.parse_args()

    print(get_config(args.hostname, args.device_type))

The key point about both scripts: Claude never sees the source code unless it explicitly reads the file. When it runs python scripts/query_netbox.py, only the JSON output comes back into context. That keeps your token usage lean even when querying NetBox for large device lists.


Installing the Skill

In Claude.ai

  1. Zip up your skill folder: zip -r network-automation-skill.zip network-automation-skill/
  2. Go to Settings → Features → Skills
  3. Upload the zip file
  4. The skill is now available in your conversations automatically

In Claude Code

Drop the folder into your project’s skill directory:

cp -r network-automation-skill/ .claude/skills/

Or into your personal skills directory for use across all projects:

cp -r network-automation-skill/ ~/.claude/skills/

Claude Code discovers and loads it automatically. No restart needed.

Via the API

Upload the skill and reference it by ID in your API calls. Check the Skills API quickstart for the exact upload flow, the key headers you need are code-execution-2025-08-25, skills-2025-10-02, and files-api-2025-04-14.


Watching It Work

Once installed, try a prompt like:

“Look up all active devices at site nyc-dc1 in NetBox and give me a summary table.”

Claude will recognize the task matches the skill’s description, read SKILL.md into context, follow the runbook instructions, and run query_netbox.py with the right parameters. You’ll see the script output come back and Claude format it into a clean table.

The difference from a plain prompt is significant: you didn’t tell Claude which endpoint to use, you didn’t tell it to check NetBox before touching devices, and you didn’t tell it what the right filter parameters are. The skill did all of that, and it will keep doing it consistently, every session, without you repeating yourself.


The Gotchas

The description field is your trigger, write it carefully. If it’s too broad, Claude will activate the skill for unrelated tasks and burn context. If it’s too narrow, it won’t trigger when you need it. Test it by asking Claude questions that should and shouldn’t trigger the skill and see what happens.

Skills don’t sync across surfaces. A skill you upload to Claude.ai is not available via the API. A skill you put in .claude/skills/ in Claude Code is not available in Claude.ai. You manage them separately per surface. This will catch you out eventually.

Credentials still come from the environment. Your scripts run in a code execution sandbox. Environment variables need to be set appropriately for the surface you’re using. On Claude.ai, the sandbox has limited environment access, test this before assuming your .env pattern works everywhere.

Treat third-party skills like installing software. Anthropic’s own warning here is worth repeating: a malicious skill can execute bash commands with whatever access Claude has. Only install skills you built yourself or got from Anthropic. Don’t grab random skills off GitHub without auditing them.


Skills vs. MCP: What’s the Difference?

You might be wondering how this relates to the MCP server we built in the previous post. They’re solving adjacent but different problems:

SkillsMCP
What it isProcedural knowledge + scriptsLive tool endpoints
How Claude uses itReads files, runs scripts via bashCalls remote functions over a protocol
StateStateless filesystem operationsCan maintain connections
Best forRunbooks, workflows, org-specific proceduresReal-time data, persistent connections, shared team tools
Network accessDepends on surfaceRuns on your server, full access

The short version: Skills teach Claude how to do something. MCP gives Claude something to call. They’re complementary, in fact, your skill’s SKILL.md could contain instructions for when and how to call your MCP server.

That comparison deserves its own post, and it’s coming up next.


Questions or hit a gotcha I didn’t cover? Drop a comment below or find me on LinkedIn.

David Henderson | Network Doodles, Decoding Tech, One Doodle at a Time