Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 60 additions & 15 deletions docs/Tutorial/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,21 +256,19 @@ agent = Agent(
"You have access to specialized skill modes that give you "
"deeper expertise in specific domains.\n"
"When the user's request clearly matches a skill, activate "
"it immediately by calling set_skill() before doing anything else.\n"
"When the task is done, call clear_skill() to return to general mode."
"it immediately by calling set_skill() before doing anything else."
),
}
],
skills=SkillConfig(
skills_dir=SKILLS_DIR,
inject_trigger_table=True, # Auto-appends skill table to system prompt
hot_reload=True, # Re-reads SKILL.md on every call (great for dev)
auto_deactivate=True, # Activating new skill clears the old one
),
trim_context=True, # Safe with skills - they survive trimming!
)

# Get the tool node - already includes set_skill and clear_skill
# Get the tool node - includes set_skill tool
tool_node = agent.get_tool_node()


Expand Down Expand Up @@ -402,28 +400,75 @@ You: What's the average of these numbers: 10, 20, 30

## How It Works

1. **Startup**: The agent discovers skills from `./skills/` and registers `set_skill` and `clear_skill` tools.
1. **Startup**: The agent discovers skills from `./skills/` and registers the `set_skill` tool.

2. **System Prompt**: Each LLM call includes:
- Your base system prompt
- A trigger table listing all skills
- Active skill content (if a skill is active)
- A trigger table listing all skills with their triggers and resources

3. **Skill Activation**: When the LLM calls `set_skill("code-review")`:
- The tool returns `"SKILL_ACTIVATED:code-review"`
- The framework stores this in `state.execution_meta.internal_data`
- On the next LLM call, the skill's instructions are injected
- The tool returns the full skill content in format: `## SKILL: CODE-REVIEW\n\n{skill instructions}`

4. **Context Trimming**: Skills survive `trim_context=True` because:
- The active skill name is stored in `internal_data`, not message history
- Skill content is re-injected fresh on every call
4. **Resource Loading**: To load a specific resource file:
- Call `set_skill("code-review", "style-guide.md")`
- The tool returns: `## Resource: style-guide.md\n\n{file content}`

5. **Context Trimming**: Skills survive `trim_context=True` because:
- The skill content is returned as a tool result
- The LLM uses this content directly in its response
- Content is re-loaded fresh on each call when needed

## Resources Feature

Skills can include additional reference files (resources) that can be loaded on-demand:

### Adding Resources to a Skill

In your SKILL.md frontmatter, add a `resources` list:

```markdown
---
name: code-review
description: Perform thorough code reviews
metadata:
triggers:
- review my code
- check this code
resources:
- style-guide.md
- security-checklist.md
---
```

### Loading Resources

The LLM can load specific resources during a conversation:

```python
# Call set_skill with resource name to load that specific file
# The tool returns the resource content directly
set_skill("code-review", "style-guide.md")
# Returns: ## Resource: style-guide.md\n\n{content of style-guide.md}
```

The trigger table shows available resources, and the LLM knows to call `set_skill(name, resource)` to load them.

### Example: Code Review with Resources

Here's how resources work in practice:

1. User asks: "Review my Python code"
2. LLM calls `set_skill("code-review")`
3. Tool returns the skill content
4. If the user then asks about style conventions, the LLM calls `set_skill("code-review", "style-guide.md")`
5. Tool returns the style guide content, which the LLM uses to answer

## Next Steps

- Add a `style-guide.md` resource to the code-review skill
- Add resource files to your skills for reference documentation
- Create custom skills for your domain
- Explore `max_active=2` to allow multiple skills at once
- Use tags to organize and filter skills
- Explore the hot_reload option for development

## Full Example Code

Expand Down
96 changes: 37 additions & 59 deletions docs/reference/library/skills/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ from agentflow.skills import SkillConfig

config = SkillConfig(
skills_dir="./skills/",
max_active=1,
auto_deactivate=True,
inject_trigger_table=True,
hot_reload=True,
)
Expand All @@ -28,19 +26,15 @@ agent = Agent(
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `skills_dir` | `str \| None` | `None` | Path to folder containing skill subdirectories. If `None`, no skills are discovered from disk. |
| `max_active` | `int` | `1` | Maximum number of simultaneously active skills. |
| `auto_deactivate` | `bool` | `True` | When `True`, activating a new skill automatically clears the previous one. |
| `inject_trigger_table` | `bool` | `True` | Appends a markdown table of all skills to the system prompt so the LLM knows when to use each skill. |
| `hot_reload` | `bool` | `True` | Re-reads SKILL.md files when their modification time changes. Safe for production (mtime check is cheap). |

### Example: Multiple Active Skills
### Example

```python
# Allow up to 2 skills active at once
# Configure skills
config = SkillConfig(
skills_dir="./skills/",
max_active=2,
auto_deactivate=False, # Don't clear previous skills
)
```

Expand Down Expand Up @@ -163,84 +157,68 @@ table = registry.build_trigger_table()
# Returns:
# ## Available Skills
#
# | Skill | When to use |
# |-------|-------------|
# | `code-review` | review my code; find bugs; code review |
# ### How to Use Skills
# When the user's request matches a skill:
# 1. Call `set_skill(skill_name)` to load the skill instructions
# 2. Read the loaded content — it may reference additional resources
# 3. If you need a specific resource mentioned in the skill, call `set_skill(skill_name, resource_name)`
# 4. Then provide your answer using the loaded content
# ### Skills & Resources
#
# **`code-review`** — triggers: "review my code", "find bugs", "code review"
# ...
```

---

## Tools: set_skill and clear_skill
## Tool: set_skill

When skills are enabled, the Agent's ToolNode automatically includes the `set_skill` tool.

When skills are enabled, the Agent's ToolNode automatically includes two tools:
### set_skill(skill_name: str, resource: str | None = None) -> str

### set_skill(skill_name: str) -> str
Activates a skill or loads a specific resource. The LLM calls this when a user request matches a skill domain.

Activates a skill. The LLM calls this when a user request matches a skill domain.
**Arguments:**
- `skill_name` (required): Name of the skill to load
- `resource` (optional): If provided, loads this specific resource file instead of the skill instructions

**Returns:**
- `"SKILL_ACTIVATED:<skill_name>"` on success
- `"## SKILL: {NAME}\n\n{skill content}"` on success
- `"## Resource: {filename}\n\n{resource content}"` when loading a resource
- `"ERROR: Unknown skill '<name>'. Available: ..."` if skill not found

**Example LLM interaction:**
```
User: Can you review my Python code?
Assistant: [calls set_skill("code-review")]
Tool Result: SKILL_ACTIVATED:code-review
Tool Result: ## SKILL: CODE-REVIEW

You are now in **CODE REVIEW** mode...
Assistant: [responds using code-review skill instructions]
```

### clear_skill() -> str

Deactivates all active skills, returning to general mode.

**Returns:** `"SKILL_DEACTIVATED"`

---

## SkillInjector

Internal class that builds system prompt messages from active skills. You typically don't use this directly.

```python
from agentflow.skills.injection import SkillInjector

injector = SkillInjector(registry, config)

# Build prompts for active skills
prompts = injector.build_skill_prompts(["code-review"])
# Returns: [{"role": "system", "content": "## ACTIVE SKILL: CODE REVIEW\n..."}]
**Loading a resource:**
```
Assistant: [calls set_skill("code-review", "style-guide.md")]
Tool Result: ## Resource: style-guide.md

### Methods

#### `build_skill_prompts(active_skills: list[str]) -> list[dict[str, str]]`

Builds system prompt dicts for each active skill. Each dict contains:
- Skill body content
- Appended resource files
- Routing note (tells LLM to check other skills on next turn)
# Style Guide
...
```

---

## State Storage

Active skills are stored in `state.execution_meta.internal_data["active_skills"]` as a list of strings.
## How Skills Work

```python
# Check active skills
active = state.execution_meta.internal_data.get("active_skills", [])

# Manually set (not recommended - use set_skill tool instead)
state.execution_meta.internal_data["active_skills"] = ["code-review"]
```
The skill system works differently from the old "active skill injection" model:

**Why this location?**
1. **Trigger Table**: The LLM sees all available skills in the system prompt
2. **Skill Activation**: When the LLM calls `set_skill("skill-name")`, the tool returns the full skill content
3. **Resource Loading**: `set_skill("skill-name", "resource.md")` loads specific resource files
4. **No State Storage**: Skills don't store state in `execution_meta` - instead, content is returned as tool results and the LLM uses it directly

- `state.context` (message history) can be trimmed by `MessageContextManager`
- `execution_meta.internal_data` is never trimmed
- Skills survive context trimming automatically
This design is simpler and works better with context trimming - skill content is re-loaded fresh when needed.

---

Expand Down
37 changes: 19 additions & 18 deletions docs/reference/library/skills/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ agent = Agent(
),
)

# The tool_node already includes set_skill and clear_skill
# The tool_node includes the set_skill tool
tool_node = agent.get_tool_node()

def should_use_tools(state):
Expand Down Expand Up @@ -132,30 +132,31 @@ The agent will:
│ Agent.execute() │
├─────────────────────────────────────────────────────────────────┤
│ 1. Base system prompt │
│ 2. Trigger table (all available skills) │
│ 3. Active skill content (if skill is active) │
│ 4. User messages from state.context │
│ 2. Trigger table (all available skills with triggers/resources)│
│ 3. User messages from state.context │
└─────────────────────────────────────────────────────────────────┘
LLM Response
┌────────────────────┴────────────────────┐
│ │
Text response Tool call: set_skill()
│ │
▼ ▼
END ToolNode
"SKILL_ACTIVATED:code-review"
State updated with active skill
Back to MAIN
Skill content now injected
┌────────────────────┴────────────────────┐
│ │
Text response Tool call: set_skill()
│ │
▼ ▼
END ToolNode
"## SKILL: CODE-REVIEW\n\n{content}"
Skill content returned as tool result
Back to MAIN
LLM uses skill content in response
```

The `set_skill()` tool returns the full skill content directly, which the LLM uses to formulate its response. Resources can be loaded by calling `set_skill("skill-name", "resource.md")`.

## Next Steps

- [SKILL.md Format Reference](./skill-format.md) - Complete syntax guide
Expand Down
23 changes: 11 additions & 12 deletions docs/reference/library/skills/skill-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,13 @@ metadata:
- is this code good
```

Only the first 3 triggers are shown in the table. Choose the most distinctive ones first.
Only the first 4 triggers are shown in the table. Choose the most distinctive ones first.

**Tip:** Make triggers natural and varied. The LLM uses semantic matching, so you don't need to cover every possible phrase.

## Resources

Resources are additional files that get injected alongside the skill content. They're useful for:
Resources are additional files that can be loaded on-demand during a conversation. They're useful for:

- Style guides
- Reference documentation
Expand All @@ -127,19 +127,18 @@ code-review/
└── other.md ✗ Won't be found (relative path doesn't traverse dirs)
```

Resources are appended to the skill content with a clear header:
### Loading Resources

```
## ACTIVE SKILL: CODE REVIEW

[SKILL.md body content]

---
### Reference: style-guide.md
Resources are loaded via the `set_skill` tool by passing the resource filename as the second argument:

[style-guide.md content]
```python
# Load a specific resource file
set_skill("code-review", "style-guide.md")
# Returns: ## Resource: style-guide.md\n\n{content of the file}
```

The trigger table shows available resources, and the LLM knows to call `set_skill("skill-name", "resource-name")` to load them.

## Tags

Tags are metadata for filtering skills programmatically. They're not used by the LLM.
Expand Down Expand Up @@ -173,7 +172,7 @@ metadata:

## Skill Body (Instructions)

Everything after the closing `---` is the skill body. This content is injected as a system message when the skill is active.
Everything after the closing `---` is the skill body. This content is returned as a tool result when the LLM calls `set_skill("skill-name")`.

### Best Practices

Expand Down