From 01c1bf4bccb66ea7de654a3b7fdc449614831c07 Mon Sep 17 00:00:00 2001 From: Greyson Lalonde Date: Fri, 6 Mar 2026 00:07:23 -0500 Subject: [PATCH] fix: improve skills typing, path validation, and event tracing --- lib/crewai/src/crewai/agent/core.py | 2 ++ lib/crewai/src/crewai/skills/__init__.py | 8 +++++++- lib/crewai/src/crewai/skills/loader.py | 15 ++++++++++++--- lib/crewai/src/crewai/skills/models.py | 7 ++++--- lib/crewai/src/crewai/skills/parser.py | 14 ++++++++++---- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index c7633c5cb..f21a5838d 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -268,6 +268,8 @@ class Agent(BaseAgent): if self.allow_code_execution: self._validate_docker_installation() + self.set_skills() + return self def _setup_agent_executor(self) -> None: diff --git a/lib/crewai/src/crewai/skills/__init__.py b/lib/crewai/src/crewai/skills/__init__.py index 538fb4348..0332d41e4 100644 --- a/lib/crewai/src/crewai/skills/__init__.py +++ b/lib/crewai/src/crewai/skills/__init__.py @@ -9,12 +9,18 @@ from crewai.skills.loader import ( format_skill_context, load_resources, ) -from crewai.skills.models import DisclosureLevel, Skill, SkillFrontmatter +from crewai.skills.models import ( + DisclosureLevel, + ResourceDirName, + Skill, + SkillFrontmatter, +) from crewai.skills.parser import SkillParseError, parse_skill_md __all__ = [ "DisclosureLevel", + "ResourceDirName", "Skill", "SkillFrontmatter", "SkillParseError", diff --git a/lib/crewai/src/crewai/skills/loader.py b/lib/crewai/src/crewai/skills/loader.py index bf89abbe5..736bf6306 100644 --- a/lib/crewai/src/crewai/skills/loader.py +++ b/lib/crewai/src/crewai/skills/loader.py @@ -7,7 +7,11 @@ for agent use, and format skill context for prompt injection. from __future__ import annotations from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from crewai.agents.agent_builder.base_agent import BaseAgent from crewai.events.event_bus import crewai_event_bus from crewai.events.types.skill_events import ( @@ -28,7 +32,7 @@ from crewai.skills.parser import ( def discover_skills( search_path: Path, - source: Any | None = None, + source: BaseAgent | None = None, ) -> list[Skill]: """Scan a directory for skill directories containing SKILL.md. @@ -51,6 +55,7 @@ def discover_skills( crewai_event_bus.emit( source, event=SkillDiscoveryStartedEvent( + from_agent=source, search_path=search_path, ), ) @@ -68,6 +73,7 @@ def discover_skills( crewai_event_bus.emit( source, event=SkillLoadedEvent( + from_agent=source, skill_name=skill.name, skill_path=skill.path, disclosure_level=skill.disclosure_level.value, @@ -78,6 +84,7 @@ def discover_skills( crewai_event_bus.emit( source, event=SkillLoadFailedEvent( + from_agent=source, skill_name=child.name, skill_path=child, error=str(e), @@ -88,6 +95,7 @@ def discover_skills( crewai_event_bus.emit( source, event=SkillDiscoveryCompletedEvent( + from_agent=source, search_path=search_path, skills_found=len(skills), skill_names=[s.name for s in skills], @@ -99,7 +107,7 @@ def discover_skills( def activate_skill( skill: Skill, - source: Any | None = None, + source: BaseAgent | None = None, ) -> Skill: """Promote a skill to INSTRUCTIONS disclosure level. @@ -121,6 +129,7 @@ def activate_skill( crewai_event_bus.emit( source, event=SkillActivatedEvent( + from_agent=source, skill_name=activated.name, skill_path=activated.path, disclosure_level=activated.disclosure_level.value, diff --git a/lib/crewai/src/crewai/skills/models.py b/lib/crewai/src/crewai/skills/models.py index 098015bbc..3bcb56668 100644 --- a/lib/crewai/src/crewai/skills/models.py +++ b/lib/crewai/src/crewai/skills/models.py @@ -8,7 +8,7 @@ from __future__ import annotations from enum import IntEnum from pathlib import Path -from typing import Any +from typing import Any, Literal from pydantic import BaseModel, Field, model_validator @@ -20,6 +20,7 @@ from crewai.skills.validation import ( MAX_DESCRIPTION_LENGTH: int = 1024 +ResourceDirName = Literal["scripts", "references", "assets"] class DisclosureLevel(IntEnum): @@ -88,7 +89,7 @@ class Skill(BaseModel): instructions: str | None = None path: Path disclosure_level: DisclosureLevel = Field(default=DisclosureLevel.METADATA) - resource_files: dict[str, list[str]] | None = None + resource_files: dict[ResourceDirName, list[str]] | None = None @property def name(self) -> str: @@ -119,7 +120,7 @@ class Skill(BaseModel): self, level: DisclosureLevel, instructions: str | None = None, - resource_files: dict[str, list[str]] | None = None, + resource_files: dict[ResourceDirName, list[str]] | None = None, ) -> Skill: """Create a new Skill at a different disclosure level. diff --git a/lib/crewai/src/crewai/skills/parser.py b/lib/crewai/src/crewai/skills/parser.py index 32dc037c8..9d8896df9 100644 --- a/lib/crewai/src/crewai/skills/parser.py +++ b/lib/crewai/src/crewai/skills/parser.py @@ -12,7 +12,12 @@ from typing import Any import yaml -from crewai.skills.models import DisclosureLevel, Skill, SkillFrontmatter +from crewai.skills.models import ( + DisclosureLevel, + ResourceDirName, + Skill, + SkillFrontmatter, +) from crewai.skills.validation import validate_directory_name @@ -145,12 +150,13 @@ def load_skill_resources(skill: Skill) -> Skill: if skill.disclosure_level < DisclosureLevel.INSTRUCTIONS: skill = load_skill_instructions(skill) - resource_files: dict[str, list[str]] = {} - for dir_name, resource_dir in ( + resource_dirs: list[tuple[ResourceDirName, Path]] = [ ("scripts", skill.scripts_dir), ("references", skill.references_dir), ("assets", skill.assets_dir), - ): + ] + resource_files: dict[ResourceDirName, list[str]] = {} + for dir_name, resource_dir in resource_dirs: if resource_dir.is_dir(): resource_files[dir_name] = sorted( str(f.relative_to(resource_dir))