CodeDocs Vault

Agent Profiles and Skills System

Two major systems added in v2.0: Agent Profiles for configurable behavior presets and Skills for injectable domain knowledge.

Part 1: Agent Profiles

Overview

Directory: vibe/core/agents/

vibe/core/agents/
├── models.py    # AgentProfile, AgentSafety, AgentType, BuiltinAgentName
└── manager.py   # AgentManager discovery & switching

AgentSafety Enum

File: vibe/core/agents/models.py:23

class AgentSafety(StrEnum):
    SAFE = auto()         # Read-only, no modifications
    NEUTRAL = auto()      # Default, requires approval
    DESTRUCTIVE = auto()  # Auto-approves some write operations
    YOLO = auto()         # Auto-approves everything

AgentType Enum

File: vibe/core/agents/models.py:30

class AgentType(StrEnum):
    AGENT = auto()      # Primary agent (user-switchable)
    SUBAGENT = auto()   # Subagent (used by Task tool only)

BuiltinAgentName Enum

File: vibe/core/agents/models.py:35

class BuiltinAgentName(StrEnum):
    DEFAULT = "default"
    PLAN = "plan"
    ACCEPT_EDITS = "accept-edits"
    AUTO_APPROVE = "auto-approve"
    EXPLORE = "explore"

AgentProfile Dataclass

File: vibe/core/agents/models.py:43

@dataclass(frozen=True)
class AgentProfile:
    name: str
    display_name: str
    description: str
    safety: AgentSafety
    agent_type: AgentType = AgentType.AGENT
    overrides: dict[str, Any] = field(default_factory=dict)

Key methods:

Method Line Purpose
apply_to_config() 52 Deep-merge overrides into base VibeConfig
from_toml() 58 Load custom agent from TOML file

Built-in Agent Profiles

Defined at: models.py:74-122

Agent Safety Type Overrides
default NEUTRAL AGENT None (requires approval)
plan SAFE AGENT auto_approve=True, enabled_tools=["grep", "read_file", "todo", "ask_user_question", "task"]
accept-edits DESTRUCTIVE AGENT tools.write_file.permission="always", tools.search_replace.permission="always"
auto-approve YOLO AGENT auto_approve=True
explore SAFE SUBAGENT enabled_tools=["grep", "read_file"]

The explore agent is a SUBAGENT, meaning it can only be used via the Task tool (not directly switchable by the user).

AgentManager

File: vibe/core/agents/manager.py:24

class AgentManager:
    def __init__(
        self,
        config_getter: Callable[[], VibeConfig],
        initial_agent: str = BuiltinAgentName.DEFAULT,
    ) -> None:

Initialization

AgentManager.__init__() [manager.py:25]
    │
    ├─1─► Compute search paths [manager.py:31]
    │     _compute_search_paths(config)
    │
    ├─2─► Discover agents [manager.py:32]
    │     _discover_agents() → dict[str, AgentProfile]
    │
    ├─3─► Log custom agent count [manager.py:34-43]
    │
    └─4─► Set active profile [manager.py:45-47]
          Falls back to DEFAULT if initial_agent not found

Search Path Resolution

Method: _compute_search_paths() at manager.py:83

@staticmethod
def _compute_search_paths(config: VibeConfig) -> list[Path]:
    paths: list[Path] = []
 
    # 1. Config-specified paths
    for path in config.agent_paths:
        if path.is_dir():
            paths.append(path)
 
    # 2. Project-local .vibe/agents/ (trust-aware)
    if (agents_dir := resolve_local_agents_dir(Path.cwd())) is not None:
        paths.append(agents_dir)
 
    # 3. Global ~/.vibe/agents/
    if GLOBAL_AGENTS_DIR.path.is_dir():
        paths.append(GLOBAL_AGENTS_DIR.path)
 
    return unique_paths

Agent Discovery

Method: _discover_agents() at manager.py:100

_discover_agents() [manager.py:100-123]
    │
    ├─1─► Start with all BUILTIN_AGENTS [manager.py:101]
    │
    └─2─► For each search path:
          │
          ├─► Find all .toml files [manager.py:106]
          │
          ├─► Load via AgentProfile.from_toml() [manager.py:109]
          │
          ├─► If name matches builtin: override with warning [manager.py:110-113]
          │
          └─► If duplicate: skip [manager.py:114-120]

Key Properties and Methods

Property/Method Line Purpose
active_profile 45 Currently active AgentProfile
available_agents 55 Filtered dict respecting enabled/disabled config
config 71 VibeConfig with active profile overrides applied
switch_profile() 76 Switch to different agent, invalidate cached config
invalidate_config() 80 Force config recalculation
get_agent() 134 Look up agent by name
get_subagents() 139 List agents with type SUBAGENT
get_agent_order() 146 Ordered list of primary agents for cycling
next_agent() 162 Get next agent in cycle order

Agent Cycling (Shift+Tab)

The get_agent_order() method returns agents in this order:

  1. Built-in agents: default → plan → accept-edits → auto-approve
  2. Custom agents (sorted alphabetically)

Subagents are excluded from the cycle.

Creating Custom Agents

Create a .toml file in any agent search path:

# ~/.vibe/agents/my-agent.toml
display_name = "My Custom Agent"
description = "A custom agent with specific settings"
safety = "neutral"
agent_type = "agent"
 
# Config overrides (any valid VibeConfig fields)
auto_approve = false
active_model = "devstral-small"
 
[tools.bash]
permission = "always"
timeout = 120

The file stem becomes the agent name (e.g., my-agent).

Part 2: Skills System

Overview

Directory: vibe/core/skills/

vibe/core/skills/
├── models.py    # SkillMetadata, SkillInfo
├── manager.py   # SkillManager discovery
└── parser.py    # YAML frontmatter parser

SKILL.md Format

Skills are directories containing a SKILL.md file with YAML frontmatter:

---
name: my-skill
description: "What this skill does and when to use it"
license: MIT
compatibility: "Requires Node.js 18+"
user-invocable: true
allowed-tools: "bash read_file write_file"
metadata:
  author: "Your Name"
  version: "1.0"
---
 
# Skill Instructions
 
This content is injected into the system prompt when the skill is active.
The LLM will see these instructions and follow them.

SkillMetadata Model

File: vibe/core/skills/models.py:9

class SkillMetadata(BaseModel):
    name: str              # Lowercase, hyphens only (pattern: ^[a-z0-9]+(-[a-z0-9]+)*$)
    description: str       # What this skill does (max 1024 chars)
    license: str | None    # License name
    compatibility: str | None  # Environment requirements
    metadata: dict[str, str]   # Arbitrary key-value pairs
    allowed_tools: list[str]   # Pre-approved tools (space-delimited string or list)
    user_invocable: bool = True  # Whether it appears in slash command menu

SkillInfo Model

File: vibe/core/skills/models.py:65

class SkillInfo(BaseModel):
    name: str
    description: str
    license: str | None = None
    compatibility: str | None = None
    metadata: dict[str, str]
    allowed_tools: list[str]
    user_invocable: bool = True
    skill_path: Path           # Absolute path to SKILL.md
 
    @property
    def skill_dir(self) -> Path:
        return self.skill_path.parent.resolve()

SkillManager

File: vibe/core/skills/manager.py:20

class SkillManager:
    def __init__(self, config_getter: Callable[[], VibeConfig]) -> None:

Search Path Resolution

Method: _compute_search_paths() at manager.py:53

@staticmethod
def _compute_search_paths(config: VibeConfig) -> list[Path]:
    paths: list[Path] = []
 
    # 1. Config-specified paths
    for path in config.skill_paths:
        if path.is_dir():
            paths.append(path)
 
    # 2. Project-local .vibe/skills/ (trust-aware)
    if (skills_dir := resolve_local_skills_dir(Path.cwd())) is not None:
        paths.append(skills_dir)
 
    # 3. Global ~/.vibe/skills/
    if GLOBAL_SKILLS_DIR.path.is_dir():
        paths.append(GLOBAL_SKILLS_DIR.path)
 
    return unique_paths

Skill Discovery

Method: _discover_skills() at manager.py:75

_discover_skills() [manager.py:75-90]
    │
    for each search path:
    │
    ├─► List subdirectories [manager.py:94]
    │
    ├─► Look for SKILL.md in each subdir [manager.py:97-98]
    │
    ├─► Parse frontmatter + validate [manager.py:112-119]
    │   parse_frontmatter(content) → (yaml_dict, markdown_body)
    │   SkillMetadata.model_validate(yaml_dict)
    │
    ├─► Warn if skill name != directory name [manager.py:122-128]
    │
    └─► First occurrence wins (skip duplicates) [manager.py:81-89]

Filtering

The available_skills property respects enabled_skills / disabled_skills config:

@property
def available_skills(self) -> dict[str, SkillInfo]:
    if self._config.enabled_skills:
        return {name: info for name, info in self._available.items()
                if name_matches(name, self._config.enabled_skills)}
    if self._config.disabled_skills:
        return {name: info for name, info in self._available.items()
                if not name_matches(name, self._config.disabled_skills)}
    return dict(self._available)

Frontmatter Parser

File: vibe/core/skills/parser.py

def parse_frontmatter(content: str) -> tuple[dict[str, Any], str]:
    """Parse YAML frontmatter delimited by --- markers."""
    # Splits on --- boundaries
    # Returns (yaml_dict, markdown_body)

Raises SkillParseError if frontmatter is missing, invalid YAML, or not a dictionary.

Skill Placement

Skills can be placed in:

~/.vibe/skills/          # Global skills
    my-skill/
        SKILL.md

./.vibe/skills/          # Project-local (requires trusted folder)
    project-skill/
        SKILL.md

/custom/path/skills/     # Config-specified via skill_paths
    custom-skill/
        SKILL.md

Integration with System Prompt

Skills are injected into the system prompt via get_universal_system_prompt():

get_universal_system_prompt(tool_manager, config, skill_manager, agent_manager)
    │
    ├── Include tool descriptions
    ├── Include agent info
    └── Include skill content from all available skills
        Markdown body of each SKILL.md is appended to the prompt

Source File References

File Key Lines Description
agents/models.py:13-20 _deep_merge() Deep merge utility for config overrides
agents/models.py:23-27 AgentSafety Safety level enum
agents/models.py:30-32 AgentType Agent vs subagent enum
agents/models.py:35-40 BuiltinAgentName Built-in agent name constants
agents/models.py:43-69 AgentProfile Profile dataclass with apply_to_config, from_toml
agents/models.py:72 PLAN_AGENT_TOOLS Allowed tools for plan agent
agents/models.py:74-122 Built-in agents DEFAULT, PLAN, ACCEPT_EDITS, AUTO_APPROVE, EXPLORE
agents/manager.py:24-166 AgentManager Discovery, switching, config merging
agents/manager.py:83-98 _compute_search_paths() Search path resolution
agents/manager.py:100-123 _discover_agents() Agent file discovery
agents/manager.py:146-160 get_agent_order() Cycling order for Shift+Tab
skills/models.py:9-63 SkillMetadata YAML frontmatter model
skills/models.py:65-92 SkillInfo Runtime skill representation
skills/manager.py:20-134 SkillManager Discovery, filtering
skills/manager.py:53-73 _compute_search_paths() Search path resolution
skills/parser.py:9-12 SkillParseError Parse error exception
skills/parser.py:18-39 parse_frontmatter() YAML frontmatter parser