diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index 7a01f12d0..e468a0828 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -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() diff --git a/lib/crewai/src/crewai/agents/agent_builder/base_agent.py b/lib/crewai/src/crewai/agents/agent_builder/base_agent.py index 24bcfa263..9c4099979 100644 --- a/lib/crewai/src/crewai/agents/agent_builder/base_agent.py +++ b/lib/crewai/src/crewai/agents/agent_builder/base_agent.py @@ -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 diff --git a/lib/crewai/src/crewai/crew.py b/lib/crewai/src/crewai/crew.py index 8608050c4..ac3523cab 100644 --- a/lib/crewai/src/crewai/crew.py +++ b/lib/crewai/src/crewai/crew.py @@ -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, diff --git a/lib/crewai/src/crewai/skills/__init__.py b/lib/crewai/src/crewai/skills/__init__.py index 0332d41e4..5fa337fa3 100644 --- a/lib/crewai/src/crewai/skills/__init__.py +++ b/lib/crewai/src/crewai/skills/__init__.py @@ -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", diff --git a/lib/crewai/src/crewai/skills/validation.py b/lib/crewai/src/crewai/skills/validation.py index 9c8ac07a0..af1472a75 100644 --- a/lib/crewai/src/crewai/skills/validation.py +++ b/lib/crewai/src/crewai/skills/validation.py @@ -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.