Agent skills & context
This guide shows how to implement skills in the SDK. For conceptual overview, see Skills Overview.
Faheem Code supports an extended version of the AgentSkills standard with optional keyword triggers.
Skill injection behavior
Understanding where skill content appears in the prompt is critical. The behavior differs based on skill format and trigger configuration:
| Skill Format | Trigger | Where Content Appears | Model Mediated? |
|---|---|---|---|
AgentSkills (SKILL.md) | Any | <available_skills> (description only) | ✅ Yes — agent calls invoke_skill() |
AgentSkills (SKILL.md) | Has triggers | <available_skills> + auto-inject on match | ✅ Yes |
Legacy (inline/*.md) | None | <REPO_CONTEXT> (full content in the initial system prompt; included in LLM context for each turn) | ❌ No |
Legacy (inline/*.md) | Has triggers | <available_skills> + auto-inject on match | ✅ Yes |
Rule (inline/*.md) | PathTrigger (paths: globs) | Injected into the tool result (<EXTRA_INFO>) when a matching file is touched; never in <available_skills> or <REPO_CONTEXT> | ❌ No — deterministic on file-touch |
Prompt structure
Skills appear in different parts of the system prompt:
<!-- System Prompt Structure -->
<REPO_CONTEXT>
<!-- Legacy trigger=None skills: FULL content in the initial system prompt;
included in LLM context for each turn while retained in history -->
[BEGIN context from [agents]]
... AGENTS.md content ...
[END Context]
</REPO_CONTEXT>
<SKILLS>
<available_skills>
<!-- AgentSkills + legacy with triggers: description only -->
<skill>
<name>github</name>
<description>Interact with GitHub...</description>
</skill>
</available_skills>
</SKILLS>
When a trigger matches, content is injected into the user message:
<EXTRA_INFO>
The following information has been included based on a keyword match for "github".
Skill location: /path/to/skill
... skill content ...
</EXTRA_INFO>
Context loading methods
| Method | When Content Loads | Use Case |
|---|---|---|
| Always-loaded | At conversation start | Repository rules, coding standards |
| Trigger-loaded | When keywords match | Specialized tasks, domain knowledge |
| Path-triggered | When the agent touches a matching file | File-scoped rules (e.g. API validation, migration conventions) |
| Progressive disclosure | Agent reads on demand | Large reference docs (AgentSkills) |
Always-loaded context
Content that's always in the system prompt.
Option 1: AGENTS.md (auto-loaded)
Place AGENTS.md at your repo root - it's loaded automatically. See Permanent Context.
from faheemcode.sdk.skills import load_project_skills
# Automatically finds AGENTS.md, CLAUDE.md, GEMINI.md at workspace root
skills = load_project_skills(workspace_dir="/path/to/repo")
agent_context = AgentContext(skills=skills)
Option 2: inline skill (code-defined)
from faheemcode.sdk import AgentContext
from faheemcode.sdk.context import Skill
agent_context = AgentContext(
skills=[
Skill(
name="code-style",
content="Always use type hints in Python.",
trigger=None, # No trigger = always loaded
),
]
)
Trigger-loaded context
Content injected when keywords appear in user messages. See Keyword-Triggered Skills.
from faheemcode.sdk.context import Skill, KeywordTrigger
Skill(
name="encryption-helper",
content="Use the encrypt.sh script to encrypt messages.",
trigger=KeywordTrigger(keywords=["encrypt", "decrypt"]),
)
When user says "encrypt this", the content is injected into the message:
<EXTRA_INFO>
The following information has been included based on a keyword match for "encrypt".
Skill location: /path/to/encryption-helper
Use the encrypt.sh script to encrypt messages.
</EXTRA_INFO>
Path-triggered rules
A rule is a skill with a PathTrigger (paths: glob frontmatter). Its content is injected
deterministically when the agent reads, edits, or creates a file whose workspace-relative path
matches one of the globs — no reliance on the model choosing a skill. See Path-Triggered Rules
for the conceptual overview.
Rules add zero baseline cost: they are excluded from <available_skills> and <REPO_CONTEXT>
and are never model-invocable (disable_model_invocation is forced on). Nothing is loaded until a
matching file is touched, and each rule is injected only once per conversation.
from faheemcode.sdk.skills import PathTrigger, Skill
Skill(
name="api-validation",
content="API RULE: validate all request inputs with zod before using them.",
trigger=PathTrigger(paths=["src/api/**/*.ts", "**/*.route.ts"]),
)
As a file-based skill, this is just a *.md file with paths: frontmatter in a skills directory
(e.g. .agents/skills/api-validation.md):
---
paths:
- "src/api/**/*.ts"
- "**/*.route.ts"
---
API RULE: validate all request inputs with zod before using them.
When the agent creates or edits src/api/users.ts, the rule content is appended to that tool
result (not the user message) inside an <EXTRA_INFO> block, so the agent reads it on its next step:
<EXTRA_INFO>
The following rule applies because a file you touched matches "src/api/**/*.ts". Follow it when working with matching files.
Rule location: /repo/.agents/skills/api-validation.md
API RULE: validate all request inputs with zod before using them.
</EXTRA_INFO>
Glob semantics
Patterns use gitignore-style matching against the workspace-relative POSIX path (case-sensitive):
| Pattern | Matches |
|---|---|
** | Any number of path segments, including zero (crosses /). |
* | Any run of characters within a single path segment. |
? | A single non-separator character. |
*.ts (no slash) | The basename at any depth — equivalent to **/*.ts. |
Progressive disclosure (AgentSkills standard)
For the agent to trigger skills, use the AgentSkills standard SKILL.md format. The agent sees a summary and reads full content on demand.
from faheemcode.sdk.skills import load_skills_from_dir
# Load SKILL.md files from a directory
_, _, agent_skills = load_skills_from_dir("/path/to/skills")
agent_context = AgentContext(skills=list(agent_skills.values()))
Skills are listed in the system prompt:
<available_skills>
<skill>
<name>code-style</name>
<description>Project coding standards.</description>
<location>/path/to/code-style/SKILL.md</location>
</skill>
</available_skills>
Managing installed skills
You can install AgentSkills into a persistent directory and manage them through
faheemcode.sdk.skills. Skills are stored under
~/.faheem-code/skills/installed/ with a .installed.json metadata file that
records an enabled flag. list_installed_skills() returns all installed
skills, while load_installed_skills() returns only those with
enabled=true.
The public lifecycle API includes install_skill(), update_skill(),
enable_skill(), disable_skill(), and uninstall_skill(), which gives the
CLI a clean SDK surface for /skill install, /skill enable,
/skill disable, and /skill uninstall.
Installed skill lifecycle example
This example mirrors the installed-plugin lifecycle example, but for
AgentSkills. It installs sample skills, lists them, toggles the
persistent enabled flag, and uninstalls one skill while leaving the
other available.
"""Example: Installing and Managing Skills
This example demonstrates installed skill lifecycle operations in the SDK:
1. Install skills from local paths into persistent storage
2. List tracked skills and load only the enabled ones
3. Inspect the `.installed.json` metadata file and `enabled` flag
4. Disable and re-enable a skill without reinstalling it
5. Uninstall a skill while leaving other installed skills available
For marketplace installation flows, see:
`examples/01_standalone_sdk/43_mixed_marketplace_skills/`.
"""
import json
import tempfile
from pathlib import Path
from faheemcode.sdk.skills import (
disable_skill,
enable_skill,
install_skill,
list_installed_skills,
load_installed_skills,
uninstall_skill,
)
script_dir = Path(__file__).resolve().parent
example_skills_dir = script_dir.parent / "01_loading_agentskills" / "example_skills"
def print_state(label: str, installed_dir: Path) -> None:
"""Print tracked, loaded, and persisted skill state."""
print(f"\n{label}")
print("-" * len(label))
installed = list_installed_skills(installed_dir=installed_dir)
print("Tracked skills:")
for info in installed:
print(f" - {info.name} (enabled={info.enabled}, source={info.source})")
loaded = load_installed_skills(installed_dir=installed_dir)
print(f"Loaded skills: {[skill.name for skill in loaded]}")
metadata = json.loads((installed_dir / ".installed.json").read_text())
print("Metadata file:")
print(json.dumps(metadata, indent=2))
def demo_install_skills(installed_dir: Path) -> list[str]:
"""Install the sample skills into the isolated installed directory."""
print("\n" + "=" * 60)
print("DEMO 1: Installing local skills")
print("=" * 60)
installed_names: list[str] = []
for skill_dir in sorted(example_skills_dir.iterdir()):
if not skill_dir.is_dir():
continue
info = install_skill(source=str(skill_dir), installed_dir=installed_dir)
installed_names.append(info.name)
print(f"✓ Installed: {info.name}")
print(f" Source: {info.source}")
print(f" Path: {info.install_path}")
return installed_names
def demo_list_and_load_skills(installed_dir: Path) -> None:
"""List tracked skills and load them as runtime Skill objects."""
print("\n" + "=" * 60)
print("DEMO 2: Listing and loading installed skills")
print("=" * 60)
installed = list_installed_skills(installed_dir=installed_dir)
print("Tracked skills:")
for info in installed:
desc = (info.description or "No description")[:60]
print(f" - {info.name} (enabled={info.enabled})")
print(f" Description: {desc}...")
loaded = load_installed_skills(installed_dir=installed_dir)
print(f"\nLoaded {len(loaded)} skill(s):")
for skill in loaded:
desc = (skill.description or "No description")[:60]
print(f" - {skill.name}: {desc}...")
def demo_enable_disable_skill(installed_dir: Path, skill_name: str) -> None:
"""Disable then re-enable a skill and show the persisted metadata."""
print("\n" + "=" * 60)
print("DEMO 3: Disabling and re-enabling a skill")
print("=" * 60)
print_state("Before disable", installed_dir)
assert disable_skill(skill_name, installed_dir=installed_dir) is True
print_state("After disable", installed_dir)
assert skill_name not in [
skill.name for skill in load_installed_skills(installed_dir=installed_dir)
]
metadata = json.loads((installed_dir / ".installed.json").read_text())
assert metadata["skills"][skill_name]["enabled"] is False
assert enable_skill(skill_name, installed_dir=installed_dir) is True
print_state("After re-enable", installed_dir)
metadata = json.loads((installed_dir / ".installed.json").read_text())
assert metadata["skills"][skill_name]["enabled"] is True
assert skill_name in [
skill.name for skill in load_installed_skills(installed_dir=installed_dir)
]
def demo_uninstall_skill(
installed_dir: Path, skill_name: str, remaining_skill_name: str
) -> None:
"""Uninstall one skill and confirm the other skill remains available."""
print("\n" + "=" * 60)
print("DEMO 4: Uninstalling a skill")
print("=" * 60)
assert uninstall_skill(skill_name, installed_dir=installed_dir) is True
print_state("After uninstall", installed_dir)
assert not (installed_dir / skill_name).exists()
metadata = json.loads((installed_dir / ".installed.json").read_text())
assert skill_name not in metadata["skills"]
assert remaining_skill_name in metadata["skills"]
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as tmpdir:
installed_dir = Path(tmpdir) / "installed-skills"
installed_dir.mkdir(parents=True)
installed_names = demo_install_skills(installed_dir)
demo_list_and_load_skills(installed_dir)
demo_enable_disable_skill(installed_dir, skill_name="rot13-encryption")
demo_uninstall_skill(
installed_dir,
skill_name="rot13-encryption",
remaining_skill_name="code-style-guide",
)
remaining_names = [
info.name for info in list_installed_skills(installed_dir=installed_dir)
]
assert remaining_names == ["code-style-guide"]
assert sorted(installed_names) == ["code-style-guide", "rot13-encryption"]
print("\nEXAMPLE_COST: 0")
You can run the example code as-is.
export LLM_API_KEY="your-api-key"
export LLM_MODEL="anthropic/claude-sonnet-4-5-20250929" # or openai/gpt-4o, etc.
cd software-agent-sdk
uv run python examples/05_skills_and_plugins/03_managing_installed_skills/main.py
# https://app.faheemcode.ai/settings/api-keys
export LLM_API_KEY="example-user-api-key"
export LLM_MODEL="faheemcode/claude-sonnet-4-5-20250929"
cd software-agent-sdk
uv run python examples/05_skills_and_plugins/03_managing_installed_skills/main.py
Installing skills from a marketplace
Use a marketplace when you want to install a curated mix of local and remote AgentSkills in one step. The example below shows how to define a marketplace, install all listed skills, and inspect the installed metadata.
"""Example: Mixed Marketplace with Local and Remote Skills
This example demonstrates how to create a marketplace that includes both:
1. Local skills hosted in your project directory
2. Remote skills from GitHub (SMART-National-Solution/faheem-code-extensions repository)
The marketplace.json schema supports source paths in these formats:
- Local paths: ./path, ../path, /absolute/path, ~/path, file:///path
- GitHub URLs: https://github.com/{owner}/{repo}/blob/{branch}/{path}
This pattern is useful for teams that want to:
- Maintain their own custom skills locally
- Reference specific skills from remote repositories
- Create a curated skill set for their specific workflows
Directory Structure:
43_mixed_marketplace_skills/
├── .plugin/
│ └── marketplace.json # Marketplace with local and remote skills
├── skills/
│ └── greeting-helper/
│ └── SKILL.md # Local skill content
├── main.py # This file
└── README.md # Documentation
Usage:
# Install all skills from marketplace to ~/.faheem-code/skills/installed/
python main.py --install
# Force reinstall (overwrite existing)
python main.py --install --force
# Show installed skills
python main.py --list
"""
import sys
from pathlib import Path
from faheemcode.sdk.plugin import Marketplace
from faheemcode.sdk.skills import (
install_skills_from_marketplace,
list_installed_skills,
)
def main():
script_dir = Path(__file__).parent
if "--list" in sys.argv:
# List installed skills
print("=" * 80)
print("Installed Skills")
print("=" * 80)
installed = list_installed_skills()
if not installed:
print("\nNo skills installed.")
print("Run with --install to install skills from the marketplace.")
else:
for info in installed:
desc = (info.description or "No description")[:60]
print(f"\n {info.name}")
print(f" Description: {desc}...")
print(f" Source: {info.source}")
return
if "--install" in sys.argv:
# Install skills from marketplace
print("=" * 80)
print("Installing Skills from Marketplace")
print("=" * 80)
print(f"\nMarketplace directory: {script_dir}")
force = "--force" in sys.argv
installed = install_skills_from_marketplace(script_dir, force=force)
print(f"\n\nInstalled {len(installed)} skills:")
for info in installed:
print(f" - {info.name}")
# Show all installed skills
print("\n" + "=" * 80)
print("All Installed Skills")
print("=" * 80)
all_installed = list_installed_skills()
for info in all_installed:
desc = (info.description or "No description")[:50]
print(f" - {info.name}: {desc}...")
return
# Default: show marketplace info
print("=" * 80)
print("Marketplace Information")
print("=" * 80)
print(f"\nMarketplace directory: {script_dir}")
marketplace = Marketplace.load(script_dir)
print(f"Name: {marketplace.name}")
print(f"Description: {marketplace.description}")
print(f"Skills defined: {len(marketplace.skills)}")
print("\nSkills:")
for entry in marketplace.skills:
source_type = "remote" if entry.source.startswith("http") else "local"
print(f" - {entry.name} ({source_type})")
print(f" Source: {entry.source}")
if entry.description:
print(f" Description: {entry.description}")
print("\n" + "-" * 80)
print("Usage:")
print(" python main.py --install # Install all skills")
print(" python main.py --install --force # Force reinstall")
print(" python main.py --list # List installed skills")
if __name__ == "__main__":
main()
You can run the example code as-is.
export LLM_API_KEY="your-api-key"
export LLM_MODEL="anthropic/claude-sonnet-4-5-20250929" # or openai/gpt-4o, etc.
cd software-agent-sdk
uv run python examples/01_standalone_sdk/43_mixed_marketplace_skills/main.py
# https://app.faheemcode.ai/settings/api-keys
export LLM_API_KEY="example-user-api-key"
export LLM_MODEL="faheemcode/claude-sonnet-4-5-20250929"
cd software-agent-sdk
uv run python examples/01_standalone_sdk/43_mixed_marketplace_skills/main.py
Full example
import os
from pydantic import SecretStr
from faheemcode.sdk import (
LLM,
Agent,
AgentContext,
Conversation,
Event,
LLMConvertibleEvent,
get_logger,
)
from faheemcode.sdk.context import (
KeywordTrigger,
Skill,
)
from faheemcode.sdk.tool import Tool
from faheemcode.tools.file_editor import FileEditorTool
from faheemcode.tools.terminal import TerminalTool
logger = get_logger(__name__)
# Configure LLM
api_key = os.getenv("LLM_API_KEY")
assert api_key is not None, "LLM_API_KEY environment variable is not set."
model = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929")
base_url = os.getenv("LLM_BASE_URL")
llm = LLM(
usage_id="agent",
model=model,
base_url=base_url,
api_key=SecretStr(api_key),
)
# Tools
cwd = os.getcwd()
tools = [
Tool(
name=TerminalTool.name,
),
Tool(name=FileEditorTool.name),
]
# AgentContext provides flexible ways to customize prompts:
# 1. Skills: Inject instructions (always-active or keyword-triggered)
# 2. system_message_suffix: Append text to the system prompt
# 3. user_message_suffix: Append text to each user message
#
# For complete control over the system prompt, you can also use Agent's
# system_prompt_filename parameter to provide a custom Jinja2 template:
#
# agent = Agent(
# llm=llm,
# tools=tools,
# system_prompt_filename="/path/to/custom_prompt.j2",
# system_prompt_kwargs={"cli_mode": True, "repo": "my-project"},
# )
#
# See: https://docs.faheemcode.ai/sdk/guides/skill#customizing-system-prompts
agent_context = AgentContext(
skills=[
Skill(
name="repo.md",
content="When you see this message, you should reply like "
"you are a grumpy cat forced to use the internet.",
# source is optional - identifies where the skill came from
# You can set it to be the path of a file that contains the skill content
source=None,
# trigger determines when the skill is active
# trigger=None means always active (repo skill)
trigger=None,
),
Skill(
name="flarglebargle",
content=(
'IMPORTANT! The user has said the magic word "flarglebargle". '
"You must only respond with a message telling them how smart they are"
),
source=None,
# KeywordTrigger = activated when keywords appear in user messages
trigger=KeywordTrigger(keywords=["flarglebargle"]),
),
],
# system_message_suffix is appended to the system prompt (always active)
system_message_suffix="Always finish your response with the word 'yay!'",
# user_message_suffix is appended to each user message
user_message_suffix="The first character of your response should be 'I'",
# You can also enable automatic load skills from
# public registry at https://github.com/SMART-National-Solution/faheem-code-extensions
load_public_skills=True,
)
# Agent
agent = Agent(llm=llm, tools=tools, agent_context=agent_context)
llm_messages = [] # collect raw LLM messages
def conversation_callback(event: Event):
if isinstance(event, LLMConvertibleEvent):
llm_messages.append(event.to_llm_message())
conversation = Conversation(
agent=agent, callbacks=[conversation_callback], workspace=cwd
)
print("=" * 100)
print("Checking if the repo skill is activated.")
conversation.send_message("Hey are you a grumpy cat?")
conversation.run()
print("=" * 100)
print("Now sending flarglebargle to trigger the knowledge skill!")
conversation.send_message("flarglebargle!")
conversation.run()
print("=" * 100)
print("Now triggering public skill 'github'")
conversation.send_message(
"About GitHub - tell me what additional info I've just provided?"
)
conversation.run()
print("=" * 100)
print("Conversation finished. Got the following LLM messages:")
for i, message in enumerate(llm_messages):
print(f"Message {i}: {str(message)[:200]}")
# Report cost
cost = llm.metrics.accumulated_cost
print(f"EXAMPLE_COST: {cost}")
You can run the example code as-is.
export LLM_API_KEY="your-api-key"
export LLM_MODEL="anthropic/claude-sonnet-4-5-20250929" # or openai/gpt-4o, etc.
cd software-agent-sdk
uv run python examples/01_standalone_sdk/03_activate_skill.py
# https://app.faheemcode.ai/settings/api-keys
export LLM_API_KEY="example-user-api-key"
export LLM_MODEL="faheemcode/claude-sonnet-4-5-20250929"
cd software-agent-sdk
uv run python examples/01_standalone_sdk/03_activate_skill.py
Creating skills
Skills are defined with a name, content (the instructions), and an optional trigger:
agent_context = AgentContext(
skills=[
Skill(
name="AGENTS.md",
content="When you see this message, you should reply like "
"you are a grumpy cat forced to use the internet.",
trigger=None, # Always active
),
Skill(
name="flarglebargle",
content='IMPORTANT! The user has said the magic word "flarglebargle". '
"You must only respond with a message telling them how smart they are",
trigger=KeywordTrigger(keywords=["flarglebargle"]),
),
]
)
Keyword triggers
Use KeywordTrigger to activate skills only when specific words appear:
Skill(
name="magic-word",
content="Special instructions when magic word is detected",
trigger=KeywordTrigger(keywords=["flarglebargle", "sesame"]),
)
File-based skills (SKILL.md)
For reusable skills, use the AgentSkills standard directory format.
Directory structure
Each skill is a directory containing:
- my-skill/
- SKILL.md
- scripts/
- helper.sh
- references/
- examples.md
- assets/
- config.json
where
| Component | Required | Description |
|---|---|---|
SKILL.md | Yes | Skill definition with frontmatter |
scripts/ | No | Executable scripts |
references/ | No | Reference documentation |
assets/ | No | Static assets |
SKILL.md format
The SKILL.md file defines the skill with YAML frontmatter:
---
name: my-skill # Required (standard)
description: > # Required (standard)
A brief description of what this skill does and when to use it.
license: MIT # Optional (standard)
compatibility: Requires bash # Optional (standard)
metadata: # Optional (standard)
author: your-name
version: "1.0"
triggers: # Optional (Faheem Code extension)
- keyword1
- keyword2
---
# Skill Content
Instructions and documentation for the agent...
Frontmatter fields
| Field | Required | Description |
|---|---|---|
name | Yes | Skill identifier (lowercase + hyphens) |
description | Yes | What the skill does (shown to agent) |
triggers | No | Keywords that auto-activate this skill (Faheem Code extension) |
license | No | License name |
compatibility | No | Environment requirements |
metadata | No | Custom key-value pairs |
Loading skills
Use load_skills_from_dir() to load all skills from a directory:
"""Example: Loading Skills from Disk (AgentSkills Standard)
This example demonstrates how to load skills following the AgentSkills standard
from a directory on disk.
Skills are modular, self-contained packages that extend an agent's capabilities
by providing specialized knowledge, workflows, and tools. They follow the
AgentSkills standard which includes:
- SKILL.md file with frontmatter metadata (name, description, triggers)
- Optional resource directories: scripts/, references/, assets/
The example_skills/ directory contains two skills:
- rot13-encryption: Has triggers (encrypt, decrypt) - listed in <available_skills>
AND content auto-injected when triggered
- code-style-guide: No triggers - listed in <available_skills> for on-demand access
All SKILL.md files follow the AgentSkills progressive disclosure model:
they are listed in <available_skills> with name, description, and location.
Skills with triggers get the best of both worlds: automatic content injection
when triggered, plus the agent can proactively read them anytime.
"""
import os
import sys
from pathlib import Path
from pydantic import SecretStr
from faheemcode.sdk import LLM, Agent, AgentContext, Conversation
from faheemcode.sdk.skills import (
discover_skill_resources,
load_skills_from_dir,
)
from faheemcode.sdk.tool import Tool
from faheemcode.tools.file_editor import FileEditorTool
from faheemcode.tools.terminal import TerminalTool
# Get the directory containing this script
script_dir = Path(__file__).parent
example_skills_dir = script_dir / "example_skills"
# =========================================================================
# Part 1: Loading Skills from a Directory
# =========================================================================
print("=" * 80)
print("Part 1: Loading Skills from a Directory")
print("=" * 80)
print(f"Loading skills from: {example_skills_dir}")
# Discover resources in the skill directory
skill_subdir = example_skills_dir / "rot13-encryption"
resources = discover_skill_resources(skill_subdir)
print("\nDiscovered resources in rot13-encryption/:")
print(f" - scripts: {resources.scripts}")
print(f" - references: {resources.references}")
print(f" - assets: {resources.assets}")
# Load skills from the directory
repo_skills, knowledge_skills, agent_skills = load_skills_from_dir(example_skills_dir)
print("\nLoaded skills from directory:")
print(f" - Repo skills: {list(repo_skills.keys())}")
print(f" - Knowledge skills: {list(knowledge_skills.keys())}")
print(f" - Agent skills (SKILL.md): {list(agent_skills.keys())}")
# Access the loaded skill and show all AgentSkills standard fields
if agent_skills:
skill_name = next(iter(agent_skills))
loaded_skill = agent_skills[skill_name]
print(f"\nDetails for '{skill_name}' (AgentSkills standard fields):")
print(f" - Name: {loaded_skill.name}")
desc = loaded_skill.description or ""
print(f" - Description: {desc[:70]}...")
print(f" - License: {loaded_skill.license}")
print(f" - Compatibility: {loaded_skill.compatibility}")
print(f" - Metadata: {loaded_skill.metadata}")
if loaded_skill.resources:
print(" - Resources:")
print(f" - Scripts: {loaded_skill.resources.scripts}")
print(f" - References: {loaded_skill.resources.references}")
print(f" - Assets: {loaded_skill.resources.assets}")
print(f" - Skill root: {loaded_skill.resources.skill_root}")
# =========================================================================
# Part 2: Using Skills with an Agent
# =========================================================================
print("\n" + "=" * 80)
print("Part 2: Using Skills with an Agent")
print("=" * 80)
# Check for API key
api_key = os.getenv("LLM_API_KEY")
if not api_key:
print("Skipping agent demo (LLM_API_KEY not set)")
print("\nTo run the full demo, set the LLM_API_KEY environment variable:")
print(" export LLM_API_KEY=your-api-key")
sys.exit(0)
# Configure LLM
model = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929")
llm = LLM(
usage_id="skills-demo",
model=model,
api_key=SecretStr(api_key),
base_url=os.getenv("LLM_BASE_URL"),
)
# Create agent context with loaded skills
agent_context = AgentContext(
skills=list(agent_skills.values()),
# Disable public skills for this demo to keep output focused
load_public_skills=False,
)
# Create agent with tools so it can read skill resources
tools = [
Tool(name=TerminalTool.name),
Tool(name=FileEditorTool.name),
]
agent = Agent(llm=llm, tools=tools, agent_context=agent_context)
# Create conversation
conversation = Conversation(agent=agent, workspace=os.getcwd())
# Test the skill (triggered by "encrypt" keyword)
# The skill provides instructions and a script for ROT13 encryption
print("\nSending message with 'encrypt' keyword to trigger skill...")
conversation.send_message("Encrypt the message 'hello world'.")
conversation.run()
print(f"\nTotal cost: ${llm.metrics.accumulated_cost:.4f}")
print(f"EXAMPLE_COST: {llm.metrics.accumulated_cost:.4f}")
You can run the example code as-is.
export LLM_API_KEY="your-api-key"
export LLM_MODEL="anthropic/claude-sonnet-4-5-20250929" # or openai/gpt-4o, etc.
cd software-agent-sdk
uv run python examples/05_skills_and_plugins/01_loading_agentskills/main.py
# https://app.faheemcode.ai/settings/api-keys
export LLM_API_KEY="example-user-api-key"
export LLM_MODEL="faheemcode/claude-sonnet-4-5-20250929"
cd software-agent-sdk
uv run python examples/05_skills_and_plugins/01_loading_agentskills/main.py
Key functions
load_skills_from_dir()
Loads all skills from a directory, returning three dictionaries:
from faheemcode.sdk.skills import load_skills_from_dir
repo_skills, knowledge_skills, agent_skills = load_skills_from_dir(skills_dir)
| Return Value | Source Files | Injection Behavior |
|---|---|---|
| repo_skills | repo.md, AGENTS.md, .cursorrules | Full content in <REPO_CONTEXT> in the initial system prompt; included in LLM context for each turn |
| knowledge_skills | knowledge/ subdirectories, *.md with triggers | Listed in <available_skills>, auto-inject on trigger |
| agent_skills | SKILL.md files (AgentSkills standard) | Listed in <available_skills>, agent calls invoke_skill() |
discover_skill_resources()
Discovers resource files in a skill directory:
from faheemcode.sdk.skills import discover_skill_resources
resources = discover_skill_resources(skill_dir)
print(resources.scripts) # List of script files
print(resources.references) # List of reference files
print(resources.assets) # List of asset files
print(resources.skill_root) # Path to skill directory
Skill location in prompts
The <location> element in <available_skills> follows the AgentSkills standard, allowing agents to read the full skill content on demand. When a triggered skill is activated, the content is injected with the location path:
<EXTRA_INFO>
The following information has been included based on a keyword match for "encrypt".
Skill location: /path/to/rot13-encryption
(Use this path to resolve relative file references in the skill content below)
[skill content from SKILL.md]
</EXTRA_INFO>
This enables skills to reference their own scripts and resources using relative paths like ./scripts/encrypt.sh.
Example skill: ROT13 encryption
Here's a skill with triggers (Faheem Code extension):
SKILL.md:
---
name: rot13-encryption
description: >
This skill helps encrypt and decrypt messages using ROT13 cipher.
triggers:
- encrypt
- decrypt
- cipher
---
# ROT13 Encryption Skill
Run the [encrypt.sh](scripts/encrypt.sh) script with your message:
\`\`\`bash
./scripts/encrypt.sh "your message"
\`\`\`
scripts/encrypt.sh:
#!/bin/bash
echo "$1" | tr 'A-Za-z' 'N-ZA-Mn-za-m'
When the user says "encrypt", the skill is triggered and the agent can use the provided script.
Loading public skills
Faheem Code maintains a public skills repository with community-contributed skills. You can automatically load these skills without waiting for SDK updates.
Automatic loading via AgentContext
Enable public skills loading in your AgentContext:
agent_context = AgentContext(
load_public_skills=True, # Auto-load from public registry
skills=[
# Your custom skills here
]
)
When enabled, the SDK will:
- Clone or update the public skills repository to
~/.faheem-code/cache/skills/on first run - Load all available skills from the repository
- Merge them with your explicitly defined skills
Skill naming and triggers
Skill Precedence by Name: If a skill name conflicts, your explicitly defined skills take precedence over public skills. For example, if you define a skill named code-review, the public code-review skill will be skipped entirely.
Multiple Skills with Same Trigger: Skills with different names but the same trigger can coexist and will ALL be activated when the trigger matches. To add project-specific guidelines alongside public skills, use a unique name (e.g., custom-codereview-guide instead of code-review). Both skills will be triggered together.
# Both skills will be triggered by "/codereview"
agent_context = AgentContext(
load_public_skills=True, # Loads public "code-review" skill
skills=[
Skill(
name="custom-codereview-guide", # Different name = coexists
content="Project-specific guidelines...",
trigger=KeywordTrigger(keywords=["/codereview"]),
),
]
)
Programmatic loading
You can also load public skills manually and have more control:
from faheemcode.sdk.skills import load_public_skills
# Load all public skills
public_skills = load_public_skills()
# Use with AgentContext
agent_context = AgentContext(skills=public_skills)
# Or combine with custom skills
my_skills = [
Skill(name="custom", content="Custom instructions", trigger=None)
]
agent_context = AgentContext(skills=my_skills + public_skills)
Custom skills repository
You can load skills from your own repository:
from faheemcode.sdk.skills import load_public_skills
# Load from a custom repository
custom_skills = load_public_skills(
repo_url="https://github.com/my-org/my-skills",
branch="main"
)
How it works
The load_public_skills() function uses git-based caching for efficiency:
- First run: Clones the skills repository to
~/.faheem-code/cache/skills/public-skills/ - Subsequent runs: Pulls the latest changes to keep skills up-to-date
- Offline mode: Uses the cached version if network is unavailable
This approach is more efficient than fetching individual skill files via HTTP and ensures you always have access to the latest community skills.
Customizing agent context
Message suffixes
Append custom instructions to the system prompt or user messages via AgentContext:
agent_context = AgentContext(
system_message_suffix="""
<REPOSITORY_INFO>
Repository: my-project
Branch: feature/new-api
</REPOSITORY_INFO>
""".strip(),
user_message_suffix="Remember to explain your reasoning."
)
system_message_suffix: Appended to system prompt (always active, combined with repo skills)user_message_suffix: Appended to each user message
Replacing the entire system prompt
For complete control, provide a custom Jinja2 template via the Agent class:
from faheemcode.sdk import Agent
agent = Agent(
llm=llm,
tools=tools,
system_prompt_filename="/path/to/custom_system_prompt.j2", # Absolute path
system_prompt_kwargs={"cli_mode": True, "repo_name": "my-project"}
)
Custom template example (custom_system_prompt.j2):
You are a helpful coding assistant for {{ repo_name }}.
{% if cli_mode %}
You are running in CLI mode. Keep responses concise.
{% endif %}
Follow these guidelines:
- Write clean, well-documented code
- Consider edge cases and error handling
- Suggest tests when appropriate
Key points:
- Use relative filenames (e.g.,
"system_prompt.j2") to load from the agent's prompts directory - Use absolute paths (e.g.,
"/path/to/prompt.j2") to load from any location - Pass variables to the template via
system_prompt_kwargs - The
system_message_suffixfromAgentContextis automatically appended after your custom prompt
Dynamic command execution
Skills support inline shell command execution for injecting dynamic context at render time. This is useful for including repository state, environment information, or computed values in skill content.
Basic syntax
Use !`command` to execute a shell command and replace it with stdout:
---
name: repo-context
description: Injects current repository state
triggers:
- git
- commit
---
# Repository Context
Current branch: !`git branch --show-current`
Last commit: !`git log -1 --oneline`
When triggered, the skill content becomes:
# Repository Context
Current branch: main
Last commit: a1b2c3d Fix authentication bug
Safety rules
Code blocks are never executed. Both fenced and inline code blocks are preserved:
# Safe Examples
Regular inline code: `git status` → preserved as-is
Fenced block: → preserved as-is
```bash
!`echo "not executed"`
```
Dynamic command: !`echo "executed"` → replaced with "executed"
Unclosed fenced blocks protect trailing content. If a fenced block isn't closed (odd number of ``` delimiters), everything after it is treated as inside the fence:
```bash
!`echo "inside fence - not executed"`
```
!`echo "between fences - executed"`
```bash
!`echo "unclosed fence - not executed"`
Escape syntax
Use \!`cmd` to output the literal text !`cmd` without execution:
# Documenting the Syntax
To execute a command, use \!`command` syntax.
For example: \!`git status` shows the current git state.
Output:
# Documenting the Syntax
To execute a command, use !`command` syntax.
For example: !`git status` shows the current git state.
Error handling
Failed commands return inline error markers:
| Scenario | Output |
|---|---|
| Command fails | [Error: Command xyz exited with code 1: error message] |
| Command times out | [Error: Command xyz timed out after 10s] |
| Large output (>50KB) | Output truncated with ... [output truncated] |
Programmatic rendering
When using skills programmatically, call render_content() to execute commands:
from faheemcode.sdk.context import Skill
skill = Skill.load("/path/to/skill/SKILL.md")
# Render with command execution
rendered = skill.render_content(working_dir="/path/to/repo")
print(rendered) # Commands replaced with output
The working_dir parameter sets the current directory for command execution, enabling workspace-relative commands like git status.
Migrating from legacy to AgentSkills format
If you have legacy inline skills consuming many tokens, convert them to AgentSkills format for progressive disclosure:
Before (legacy format)
# Legacy: Full content in <REPO_CONTEXT> in the initial system prompt
Skill(
name="api-guidelines",
content="""
# API Guidelines
... 2000 lines of detailed documentation ...
""",
trigger=None, # Always-on context - affects token usage on each turn!
)
After (AgentSkills format)
Create a directory api-guidelines/SKILL.md:
---
name: api-guidelines
description: Comprehensive API design guidelines for the project. Invoke when designing or reviewing API endpoints.
---
# API Guidelines
... 2000 lines of detailed documentation ...
Then load it:
from faheemcode.sdk.skills import load_skills_from_dir
# AgentSkills: Only description in prompt, agent reads full content on demand
_, _, skills = load_skills_from_dir("/path/to/skills")
agent_context = AgentContext(skills=list(skills.values()))
Benefits
| Aspect | Legacy trigger=None | AgentSkills SKILL.md |
|---|---|---|
| Token usage | Full content in system prompt; included in LLM context for each turn | Description only (~100 chars) |
| Model control | None — always present | Agent decides when to read |
| Scalability | Limited by context window | Many skills without token bloat |
Next steps
- Custom Tools - Create specialized tools
- MCP Integration - Connect external tool servers
- Confirmation Mode - Add execution approval