fix: deduplicate coerce_skill_paths and activate pre-loaded METADATA skills

This commit is contained in:
Greyson Lalonde
2026-03-12 01:11:01 -04:00
parent 10c65272f2
commit 6a2e5b5906
5 changed files with 34 additions and 13 deletions

View File

@@ -66,7 +66,7 @@ from crewai.mcp.tool_resolver import MCPToolResolver
from crewai.rag.embeddings.types import EmbedderConfig
from crewai.security.fingerprint import Fingerprint
from crewai.skills.loader import activate_skill, discover_skills
from crewai.skills.models import Skill as SkillModel
from crewai.skills.models import DisclosureLevel, Skill as SkillModel
from crewai.tools.agent_tools.agent_tools import AgentTools
from crewai.utilities.agent_utils import (
get_tool_names,
@@ -296,11 +296,10 @@ class Agent(BaseAgent):
raise ValueError(f"Invalid Knowledge Configuration: {e!s}") from e
def set_skills(self) -> None:
"""Resolve skill paths into loaded Skill objects.
"""Resolve skill paths and activate skills to INSTRUCTIONS level.
Path entries trigger discovery and activation. Skill entries pass through.
Crew-level skill paths are merged in. Skips work when all items are
already resolved and there are no crew-level paths to merge.
Path entries trigger discovery and activation. Pre-loaded Skill objects
below INSTRUCTIONS level are activated. Crew-level skills are merged in.
"""
from crewai.crew import Crew
@@ -313,8 +312,15 @@ class Agent(BaseAgent):
if not self.skills and not crew_skills:
return
has_unresolved = self.skills and any(isinstance(s, Path) for s in self.skills)
if not has_unresolved and not crew_skills:
needs_work = self.skills and any(
isinstance(s, Path)
or (
isinstance(s, SkillModel)
and s.disclosure_level < DisclosureLevel.INSTRUCTIONS
)
for s in self.skills
)
if not needs_work and not crew_skills:
return
seen: set[str] = set()

View File

@@ -31,6 +31,7 @@ from crewai.mcp.config import MCPServerConfig
from crewai.rag.embeddings.types import EmbedderConfig
from crewai.security.security_config import SecurityConfig
from crewai.skills.models import Skill
from crewai.skills.validation import coerce_skill_paths as _coerce_skill_paths
from crewai.tools.base_tool import BaseTool, Tool
from crewai.utilities.config import process_config
from crewai.utilities.i18n import I18N, get_i18n
@@ -225,9 +226,7 @@ class BaseAgent(BaseModel, ABC, metaclass=AgentMeta):
@classmethod
def coerce_skill_paths(cls, v: list[Any] | None) -> list[Path | Skill] | None:
"""Coerce string entries to Path objects."""
if not v:
return v
return [Path(item) if isinstance(item, str) else item for item in v]
return _coerce_skill_paths(v)
@model_validator(mode="before")
@classmethod

View File

@@ -90,6 +90,7 @@ from crewai.rag.types import SearchResult
from crewai.security.fingerprint import Fingerprint
from crewai.security.security_config import SecurityConfig
from crewai.skills.models import Skill
from crewai.skills.validation import coerce_skill_paths as _coerce_skill_paths
from crewai.task import Task
from crewai.tasks.conditional_task import ConditionalTask
from crewai.tasks.task_output import TaskOutput
@@ -302,9 +303,7 @@ class Crew(FlowTrackable, BaseModel):
@classmethod
def coerce_skill_paths(cls, v: list[Any] | None) -> list[Path | Skill] | None:
"""Coerce string entries to Path objects, pass through Skill instances."""
if not v:
return v
return [Path(item) if isinstance(item, str) else item for item in v]
return _coerce_skill_paths(v)
security_config: SecurityConfig = Field(
default_factory=SecurityConfig,

View File

@@ -16,6 +16,7 @@ from crewai.skills.models import (
SkillFrontmatter,
)
from crewai.skills.parser import SkillParseError, parse_skill_md
from crewai.skills.validation import coerce_skill_paths
__all__ = [
@@ -25,6 +26,7 @@ __all__ = [
"SkillFrontmatter",
"SkillParseError",
"activate_skill",
"coerce_skill_paths",
"discover_skills",
"format_skill_context",
"load_resources",

View File

@@ -6,6 +6,7 @@ Validates skill names and directory structures per the Agent Skills standard.
from __future__ import annotations
from pathlib import Path
from typing import Any
MAX_SKILL_NAME_LENGTH: int = 64
@@ -13,6 +14,20 @@ MIN_SKILL_NAME_LENGTH: int = 1
SKILL_NAME_PATTERN: str = r"^[a-z0-9]+(?:-[a-z0-9]+)*$"
def coerce_skill_paths(v: list[Any] | None) -> list[Any] | None:
"""Coerce string entries to Path objects, pass through other types.
Args:
v: List of skill paths or Skill objects, or None.
Returns:
The list with string entries converted to Path objects, or None.
"""
if not v:
return v
return [Path(item) if isinstance(item, str) else item for item in v]
def validate_directory_name(skill_dir: Path, skill_name: str) -> None:
"""Validate that a directory name matches the skill name.