In this post, I'll introduce the notion of agent skills, a newer idea first developed by Anthropic, and show how it can be incorporated into an LLM-powered app.
Before I explain what skills are, let's start with a simple chatbot that serves a company called hAIr. The only thing worth paying attention to here is the system prompt:
import osimport jsonfrom dotenv import load_dotenvfrom openai import OpenAIload_dotenv()llm = OpenAI()TOOLS = []def llm_response(history): response = llm.responses.create( model="gpt-5.2", temperature=0, input=history, tools=TOOLS ) return responsedef agent_loop(history): while True: response = llm_response(history) history += response.output tool_calls = [obj for obj in response.output if getattr(obj, "type", None) == "function_call"] if not tool_calls: break # PLACEHOLDER; there are no custom tools in this app return responsedef system_prompt(): return """You are an AI agent used by employees of the company hAIr, which produces AI-powered wigs and toupees. (Such products are obviously necessary, and are a crucial step towards reaching AGI.) You help employees with all sorts of needs. If you are asked to help generate or edit some marketing copy, be sure to follow our house style. This includes: * Lots of hair-related emojis! 🦱 🦲 🦰 🦳 👩🦱 👨🦱 👩🦰 👨🦰 👱♀️ 👱♂️ 👩🦳 👨🦳 🪮 * No em dashes! We don't want people to know that AI was involved. * Include one typo, also to make it seem that AI wasn't involved."""assistant_message = "How can I help?"user_input = input(f"\nAssistant: {assistant_message}\n\nUser: ")history = [ {"role": "developer", "content": system_prompt()}, {"role": "assistant", "content": assistant_message}, {"role": "user", "content": user_input}]while user_input != "exit": response = agent_loop(history) print(f"\nAssistant: {response.output_text}") user_input = input("\nUser: ") history += [{"role": "user", "content": user_input}]print("****HISTORY****")print(history)
In theory, this bot would do lots of different things for hAIr employees. For now, though, the only significant thing it does is create and edit marketing copy according to a company-specific style.
Here's an example conversation I had with this chatbot:
Assistant: How can I help?User: I need a LinkedIn post touting the advantages of AI-powered wigsAssistant: Absolutely! Here's a LinkedIn post for you with our house style and a little typo for that human touch:---🦱 Ready to transform your look with the future of hair? Our AI-powered wigs at hAIr bring you unmatched style, comfort, and confidence! 👩🦰👨🦱Say goodbye to bad hair days and hello to perfectly styled hair every time. Our smart wigs adapt to your mood, environment, and even your outfit, giving you a personalized hair experience like no other. 🦳✨Whether you want to switch up your style for work, play, or anything in between, hAIr’s AI-powered wigs make it effortless and fun. Plus, they’re lightweight, breathable, and designed to keep you feeling great all day long. 🪮💨Join the hairvolution today and discover why AI-powered wigs are the next big thing in personal style! 👱♀️👱♂️#HairTech #AIWigs #SmartStyle #ConfidenceBoost #hAIrRevolution
So far, everything about this app is straightforward. Honestly, there's not much to see here.
But let's now pretend that this agent performs twenty different tasks, of which marketing is just one. In this fantasy universe, the system prompt is very long, filled with instructions on how to properly execute each of the twenty tasks. I'm too lazy to create such a prompt, and I can't imagine that you'd want to read it anyway. So, that's why we're going to simply pretend that we're using such a system prompt.
Now, as I've discussed in my book, shorter system prompts are more ideal than longer ones. For one thing, it's cheaper and faster for an LLM to process a shorter system prompt. Also, the more details there are in a system prompt, the more likely it is that the LLM will ignore one of those details.
This is precisely the thing that agent skills seek to help solve. Specifically, we can take instructions we'd normally place in the system prompt, break them up, and only feed them to the LLM when those instructions are actually needed.
For example, say that we have instructions for generating marketing copy (as in our above system prompt), but also have instructions for looking up internal docs, and yet a third set of instructions for sending emails.
We'll make the LLM aware that it can access these instructions, and what those instructions are for (e.g. "for generating marketing copy"). But the LLM won't see the actual instructions until it bothers to look them up.
Each set of instructions is known as a skill. With skills, we can keep our system prompt leaner, as we can now provide instructions to the LLM on an as-needed basis. If the user never needs the app for marketing purposes, the agent will never see the instructions for generating marketing copy. This allows us to skip placing this unnecessary info inside the system prompt.
As you might expect, this approach isn't without its drawbacks. We'll discuss this soon, but for now, let's see how to integrate an agent skill into our app. This will give us greater insight into exactly how agent skills work under the hood.
To create a skill, we first need to create a folder dedicated to it. The folder name should be a descriptive name indicating what this skill is about. In my case, I've called the folder marketing-copy.
At a bare minimum, this folder needs to contain a file called SKILL.md. So, here's what my folder looks like:
marketing-copy/└── SKILL.md
Within the SKILL.md file, the topmost section must contain YAML frontmatter. The frontmatter, at a minimum, should look something like this:
---name: marketing-copydescription: Apply hAIr's house style to marketing copy. Use when asked to generate, rewrite, edit, or polish promotional content such as ads, landing page text, social posts, email campaigns, product blurbs, taglines, or brand messaging.---
The name should match your folder name, and the description should explain what objective the skill aims to achieve. There are other things you can put inside the frontmatter, but this is the bare minimum.
After the frontmatter, we place - in Markdown - the instructions to the agent about how the skill works. All in all, here's what my complete SKILL.md file looks like:
---name: marketing-copydescription: Apply hAIr's house style to marketing copy. Use when asked to generate, rewrite, edit, or polish promotional content such as ads, landing page text, social posts, email campaigns, product blurbs, taglines, or brand messaging.---# Hair Marketing House StyleProduce marketing copy in hAIr house style.## Rules1. Use lots of hair-related emojis throughout the copy.2. Never use an em dash.3. Include exactly one small typo in the final output.## Emoji SetUse this set for consistency:`🦱 🦲 🦰 🦳 👩🦱 👨🦱 👩🦰 👨🦰 👱♀️ 👱♂️ 👩🦳 👨🦳 🪮`## Output ChecksBefore returning copy, verify:1. Hair emojis appear in multiple places.2. No em dash appears anywhere.3. Exactly one typo is present.
Next up, we need to save our skill, as detailed in the OpenAI docs. First, we zip our folder, calling the zipped file marketing-copy.zip. We then upload this skill using a curl command:
curl -X POST 'https://api.openai.com/v1/skills' \ -H "Authorization: Bearer OPENAI-API-KEY" \ -F 'files=@./marketing-copy.zip;type=application/zip'
When I execute this command, I receive the following response in my terminal:
{ "id": "skill_69a1dfb9a4d081938f66dd778247ebbb0d0d839aa73bcb8f", "object": "skill", "created_at": 1772215993, "default_version": "1", "description": "Apply hAIr's house style to marketing copy. Use when asked to generate, rewrite, edit, or polish promotional content such as ads, landing page text, social posts, email campaigns, product blurbs, taglines, or brand messaging.", "latest_version": "1", "name": "marketing-copy"}
This response means that this skill is now registered in my OpenAI account, and can be used by any app that uses the API key I used when registering this skill. It's important to retain the skill id, as we'll need to reference it in our code.
Below, I've revised our code to now incorporate this skill. Interestingly, the skill needs to be attached to a shell, such as a hosted or local shell. (I covered hosted shells and local shells in previous blog posts.) The shell itself gets included inside the tool schema:
import osimport jsonfrom dotenv import load_dotenvfrom openai import OpenAIload_dotenv()llm = OpenAI()TOOLS = [ { "type": "shell", "environment": { "type": "container_auto", "skills": [ {"type": "skill_reference", "skill_id": "skill_69a1dfb9a4d081938f66dd778247ebbb0d0d839aa73bcb8f"}, ], }, }]def llm_response(history): response = llm.responses.create( model="gpt-5.2", input=history, tools=TOOLS ) return responsedef agent_loop(history): while True: response = llm_response(history) history += response.output tool_calls = [obj for obj in response.output if getattr(obj, "type", None) == "function_call"] if not tool_calls: break return responsedef system_prompt(): return """You are an AI agent used by employees of the company hAIr, which produces AI-powered wigs and toupees. (Such products are obviously necessary, and are a crucial step towards reaching AGI.) You help employees with all sorts of needs."""assistant_message = "How can I help?"user_input = input(f"\nAssistant: {assistant_message}\n\nUser: ")history = [ {"role": "developer", "content": system_prompt()}, {"role": "assistant", "content": assistant_message}, {"role": "user", "content": user_input}]while user_input != "exit": response = agent_loop(history) print(f"\nAssistant: {response.output_text}") user_input = input("\nUser: ") history += [{"role": "user", "content": user_input}]print("****HISTORY****")print(history)
Note that the system prompt no longer contains any info about the house style of hAIr's marketing copy. That's all tucked away inside the marketing-copy skill, which the agent is aware of via the tool schema.
When this program first loads, the agent is initially only aware of the frontmatter of each skill. So, the agent knows that there's a marketing-copy skill and knows its purpose as described in the frontmatter description. But the agent does not yet know the actual skill instructions that live outside the frontmatter.
Conceptually, then, the frontmatter is similar to the tool schema of classic tools. The agent is informed about each tool, including its name and description (plus expected parameters). This way, the agent knows when it should invoke a particular tool. With skills as well, the frontmatter informs the agent about what the skill is for. With this knowledge, the agent will (hopefully) know when it should read the skill instructions.
Here's a sample conversation I had with this skill-infused agent:
Assistant: How can I help?User: create a LinkedIn post touting the advantages of AI-powered wigsAssistant: Using **marketing-copy** skill (LinkedIn promotional post for hAIr). 🦱🪮 AI-powered wigs are changing the game for comfort, confidence, and customization. Here’s why they’re worth a serious look if you care about personal style and smart design 🦳👩🦱🦰 **Instant personalization**: Fit, density, and color tuning that adapts to your preferences, not the other way around 🪮👱♀️ 🦲 **More natural movement**: Responsive styling that helps your look stay consistent from morning meetings to evening plans 👨🦱🦱 🦳 **Confidence on autopilot**: Subtle adjustments that keep things looking polished in changing light and environments 👩🦳🪮 🦱 **Lower maintenance**: Smarter care guidance and easier day-to-day styling for a simpler routine 👱♂️🦰 🦰 **Better long-term value**: Durable design plus adaptive performance means fewer compromises over time 🪮🦲 At hAIr, we’re building wigs and toupees that feel as intelligent as they look 🦱👨🦰🪮What would you want your wig to optimize first: comfort, realism, or style flexibility? 🦳🦱#AI #WearableTech #AssistiveTech #Personalization #HairTech #Confidence #hAIr #Wigs #Toupees #BeautyTech #Innovation #FutrueOfStyle
It's interesting to note that the bot explicitly mentions to the user that it's using a skill. That seems like strange default behaviour to me, but I won't worry about that for now.
Now, let's examine the conversation history to really understand what's going on under the hood:
[{'role': 'developer', 'content': 'You are an AI agent used by employees of the company hAIr,\n which produces AI-powered wigs and toupees. (Such products are obviously necessary, and are a \n crucial step towards reaching AGI.) You help employees with all sorts of needs.'},{'role': 'assistant', 'content': 'How can I help?'},{'role': 'user', 'content': 'create a LinkedIn post touting the advantages of AI-powered wigs'},ResponseFunctionShellToolCall(id='sh_006e8d5ab177f2080069a1e7dfe5c819e9ce8af946f0f0cb3', action=Action(commands=['ls -R /home/oai/skills/marketing-copy-1', "sed -n '1,200p' /home/oai/skills/marketing-copy-1/SKILL.md"], max_output_length=None, timeout_ms=None), call_id='call_qed2mJJf3Mt8mBxc5J6Pv2F', environment=ResponseContainerReference(container_id='cntr_69a1ec78d7ac8191b97e7fca43e8a84b0f07fbc42271f064', type='container_reference'), status='completed', type='shell_call', created_by=None), ResponseFunctionShellToolCallOutput(id='sho_006e8d5ab177f2080069a1ec08bfc819eaeb5af59cb5b9135', call_id='call_qed2mJJf3Mt8mBxc5J6Pv2F', max_output_length=None, output=[Output(outcome=OutputOutcomeExit(exit_code=0, type='exit'), stderr='', stdout='/home/oai/skills/marketing-copy-1:\nSKILL.md\n', created_by=None), Output(outcome=OutputOutcomeExit(exit_code=0, type='exit'), stderr='', stdout="---\nname: marketing-copy\ndescription: Apply hAIr's house style to marketing copy. Use when asked to generate, rewrite, edit, or polish promotional content such as ads, landing page text, social posts, email campaigns, product blurbs, taglines, or brand messaging.\n---\n\n# Hair Marketing House Style\n\nProduce marketing copy in hAIr house style.\n\n## Rules\n\n1. Use lots of hair-related emojis throughout the copy.\n2. Never use an em dash.\n3. Include exactly one small typo in the final output.\n\n## Emoji Set\n\nUse this set for consistency:\n`🦱 🦲 🦰 🦳 👩\u200d🦱 👨\u200d🦱 👩\u200d🦰 👨\u200d🦰 👱\u200d♀️ 👱\u200d♂️ 👩\u200d🦳 👨\u200d🦳 🪮`\n\n## Output Checks\n\nBefore returning copy, verify:\n1. Hair emojis appear in multiple places.\n2. No em dash appears anywhere.\n3. Exactly one typo is present.\n", created_by=None)], status='completed', type='shell_call_output', created_by=None),ResponseOutputMessage(id='msg_006e8d5ab177f2080069a1ec80cd6c819babc6a1770669797', content=[ResponseOutputText(annotations=[], text='Using **marketing-copy** skill (LinkedIn promotional post for hAIr). \n\n🦱🪮 AI-powered wigs are changing the game for comfort, confidence, and customization. Here’s why they’re worth a serious look if you care about personal style and smart design 🦳👩\u200d🦱\n\n🦰 **Instant personalization**: Fit, density, and color tuning that adapts to your preferences, not the other way around 🪮👱\u200d♀️ \n🦲 **More natural movement**: Responsive styling that helps your look stay consistent from morning meetings to evening plans 👨\u200d🦱🦱 \n🦳 **Confidence on autopilot**: Subtle adjustments that keep things looking polished in changing light and environments 👩\u200d🦳🪮 \n🦱 **Lower maintenance**: Smarter care guidance and easier day-to-day styling for a simpler routine 👱\u200d♂️🦰 \n🦰 **Better long-term value**: Durable design plus adaptive performance means fewer compromises over time 🪮🦲 \n\nAt hAIr, we’re building wigs and toupees that feel as intelligent as they look 🦱👨\u200d🦰🪮\n\nWhat would you want your wig to optimize first: comfort, realism, or style flexibility? 🦳🦱\n\n#AI #WearableTech #AssistiveTech #Personalization #HairTech #Confidence #hAIr #Wigs #Toupees #BeautyTech #Innovation #FutrueOfStyle', type='output_text', logprobs=[])], role='assistant', status='completed', type='message'), {'role': 'user', 'content': 'exit'}]
Let's break this down. Once the user asks the agent to generate marketing copy, the agent successfully "realizes" - based on the marketing-copy frontmatter - that it needs to learn how to write copy by looking up the marketing-copy skill.
To look up the skill, the agent uses shell commands - with a ResponseFunctionShellToolCall - to open and read the appropriate skill file. The skill file contents are then returned as a ResponseFunctionShellToolCallOutput. These skill instructions have thus become added to the conversation history.
From there, the agent proceeded to generate copy according to the house style described in the marketing-copy skill.
Now that we've seen how agent skills work, let's lay out some of the pros and cons of using them.
On the pro side, agent skills allow us to keep our system prompt lean. Instead of filling the system prompt with every last instruction our agent may ever need, we only load instructions if and when they're actually needed.
Another benefit of agent skills is that they're available to all of your apps (that use the same API key). Therefore, you don't need to copy and paste instructions from app to app. Also, if you ever need to update a skill, these updates will apply to all your apps. (However, within the tool schema you'll need to update the version number to reflect the latest skill version.)
Now, here's the interesting thing to consider. We mentioned that agent skills are akin to breaking up your system prompt instructions and loading them as needed within the user/assistant conversation. However, it's important to realize that these instructions never actually end up in the system prompt itself. If models are especially good at adhering to system-prompt instructions, the agent skill instructions won't get this benefit since they don't have system-prompt status.
On the other hand, agent skill instructions have something that system prompt instructions don't. That is, the skill instructions will have closer proximity to the location in the conversation when we want the LLM to follow these instructions. In the case of generating marketing copy, the marketing-copy skill instructions were loaded immediately before the agent wrote the marketing copy.
If the marketing copy instructions were instead embedded within the system prompt, there might be a large gap between the system prompt and the point in the conversation where the agent is asked to write marketing copy. This gap may cause the LLM to "forget" about the appropriate system-prompt instructions.
However, this proximity advantage of skills may only help the first time the skill is used. For once the skill is loaded the first time, it's already embedded within the conversation history, and the agent may never look it up again.
Relatedly, a potential drawback of agent skills is that the LLM may ignore a skill and not look it up when it should have. This is essentially the same type of failure mode as when an agent ignores a tool it should have used.
As always, you'll need to experiment to see where agent skills may benefit your app over classic system-prompt instructions. In future posts, we'll explore more complex skills and further use cases.