diff --git a/docs/ar/enterprise/features/agent-control-plane/monitoring.mdx b/docs/ar/enterprise/features/agent-control-plane/monitoring.mdx index 952d5c783..db1b2c712 100644 --- a/docs/ar/enterprise/features/agent-control-plane/monitoring.mdx +++ b/docs/ar/enterprise/features/agent-control-plane/monitoring.mdx @@ -6,6 +6,14 @@ icon: "gauge" mode: "wide" --- + + **تنقل وثائق ACP (إصدار تجريبي)** + + - [نظرة عامة](/ar/enterprise/features/agent-control-plane/overview) + - **المراقبة** *(أنت هنا)* + - [القواعد](/ar/enterprise/features/agent-control-plane/rules) + + ## نظرة عامة تبويب **Automations** هو عرض العمليات للقراءة فقط في [Agent Control Plane](/ar/enterprise/features/agent-control-plane/overview). يجمع بين بطاقتَي مقاييس و sankey تفاعلي وجدولين فرعيين — **Automations** و **Consumption** — يمكنك البحث والتصفية والفرز فيهما. diff --git a/docs/ar/enterprise/features/agent-control-plane/overview.mdx b/docs/ar/enterprise/features/agent-control-plane/overview.mdx index fc290d9d1..6e9ff1925 100644 --- a/docs/ar/enterprise/features/agent-control-plane/overview.mdx +++ b/docs/ar/enterprise/features/agent-control-plane/overview.mdx @@ -5,6 +5,14 @@ sidebarTitle: نظرة عامة icon: "book-open" --- + + **تنقل وثائق ACP (إصدار تجريبي)** + + - **نظرة عامة** *(أنت هنا)* + - [المراقبة](/ar/enterprise/features/agent-control-plane/monitoring) + - [القواعد](/ar/enterprise/features/agent-control-plane/rules) + + ## نظرة عامة **Agent Control Plane** (ACP) هو مركز العمليات لكل ما يعمل لديك على CrewAI AMP. إنها شاشة واحدة — مقسّمة إلى تبويبَي **Automations** و **Rules** — تمنح فريقك القدرة على: diff --git a/docs/ar/enterprise/features/agent-control-plane/rules.mdx b/docs/ar/enterprise/features/agent-control-plane/rules.mdx index 17addc54a..144ec1003 100644 --- a/docs/ar/enterprise/features/agent-control-plane/rules.mdx +++ b/docs/ar/enterprise/features/agent-control-plane/rules.mdx @@ -6,6 +6,14 @@ icon: "shield-check" mode: "wide" --- + + **تنقل وثائق ACP (إصدار تجريبي)** + + - [نظرة عامة](/ar/enterprise/features/agent-control-plane/overview) + - [المراقبة](/ar/enterprise/features/agent-control-plane/monitoring) + - **القواعد** *(أنت هنا)* + + ## نظرة عامة تتيح لك القواعد تطبيق سياسات — اليوم: **PII Redaction** — عبر العديد من الأتمتات دفعة واحدة، بدلاً من ضبط كل deployment على حدة. افتح تبويب **Rules** في [Agent Control Plane](/ar/enterprise/features/agent-control-plane/overview) لإدارتها. diff --git a/docs/en/enterprise/features/agent-control-plane/monitoring.mdx b/docs/en/enterprise/features/agent-control-plane/monitoring.mdx index 05b4e451e..ef94d1895 100644 --- a/docs/en/enterprise/features/agent-control-plane/monitoring.mdx +++ b/docs/en/enterprise/features/agent-control-plane/monitoring.mdx @@ -6,6 +6,14 @@ icon: "gauge" mode: "wide" --- + + **ACP (Beta) Docs Navigation** + + - [Overview](/en/enterprise/features/agent-control-plane/overview) + - **Monitoring** *(you are here)* + - [Rules](/en/enterprise/features/agent-control-plane/rules) + + ## Overview The **Automations** tab is the read-only operations view of the [Agent Control Plane](/en/enterprise/features/agent-control-plane/overview). It combines two metric cards, an interactive sankey, and two sub-tables — **Automations** and **Consumption** — that you can search, filter, and sort. diff --git a/docs/en/enterprise/features/agent-control-plane/overview.mdx b/docs/en/enterprise/features/agent-control-plane/overview.mdx index dc2332764..1e3d4de8b 100644 --- a/docs/en/enterprise/features/agent-control-plane/overview.mdx +++ b/docs/en/enterprise/features/agent-control-plane/overview.mdx @@ -5,6 +5,14 @@ sidebarTitle: Overview icon: "book-open" --- + + **ACP (Beta) Docs Navigation** + + - **Overview** *(you are here)* + - [Monitoring](/en/enterprise/features/agent-control-plane/monitoring) + - [Rules](/en/enterprise/features/agent-control-plane/rules) + + ## Overview The **Agent Control Plane** (ACP) is the operations hub for everything you have running on CrewAI AMP. It is a single screen — split into **Automations** and **Rules** tabs — that lets your team: diff --git a/docs/en/enterprise/features/agent-control-plane/rules.mdx b/docs/en/enterprise/features/agent-control-plane/rules.mdx index 6555ddec0..755afbcc4 100644 --- a/docs/en/enterprise/features/agent-control-plane/rules.mdx +++ b/docs/en/enterprise/features/agent-control-plane/rules.mdx @@ -6,6 +6,14 @@ icon: "shield-check" mode: "wide" --- + + **ACP (Beta) Docs Navigation** + + - [Overview](/en/enterprise/features/agent-control-plane/overview) + - [Monitoring](/en/enterprise/features/agent-control-plane/monitoring) + - **Rules** *(you are here)* + + ## Overview Rules let you apply policies — today: **PII Redaction** — across many automations at once, instead of configuring each deployment individually. Open the **Rules** tab in the [Agent Control Plane](/en/enterprise/features/agent-control-plane/overview) to manage them. diff --git a/docs/ko/enterprise/features/agent-control-plane/monitoring.mdx b/docs/ko/enterprise/features/agent-control-plane/monitoring.mdx index 3766259ea..c8ea48fbb 100644 --- a/docs/ko/enterprise/features/agent-control-plane/monitoring.mdx +++ b/docs/ko/enterprise/features/agent-control-plane/monitoring.mdx @@ -6,6 +6,14 @@ icon: "gauge" mode: "wide" --- + + **ACP (베타) 문서 내비게이션** + + - [개요](/ko/enterprise/features/agent-control-plane/overview) + - **모니터링** *(현재 페이지)* + - [규칙](/ko/enterprise/features/agent-control-plane/rules) + + ## 개요 **Automations** 탭은 [Agent Control Plane](/ko/enterprise/features/agent-control-plane/overview)의 읽기 전용 운영 뷰입니다. 두 개의 메트릭 카드, 인터랙티브 sankey, 그리고 **Automations**와 **Consumption** 두 개의 서브 테이블을 결합해 검색·필터·정렬을 지원합니다. diff --git a/docs/ko/enterprise/features/agent-control-plane/overview.mdx b/docs/ko/enterprise/features/agent-control-plane/overview.mdx index 0ebb3c5fd..9af73ac16 100644 --- a/docs/ko/enterprise/features/agent-control-plane/overview.mdx +++ b/docs/ko/enterprise/features/agent-control-plane/overview.mdx @@ -5,6 +5,14 @@ sidebarTitle: 개요 icon: "book-open" --- + + **ACP (베타) 문서 내비게이션** + + - **개요** *(현재 페이지)* + - [모니터링](/ko/enterprise/features/agent-control-plane/monitoring) + - [규칙](/ko/enterprise/features/agent-control-plane/rules) + + ## 개요 **Agent Control Plane**(ACP)은 CrewAI AMP에서 실행 중인 모든 워크로드를 위한 운영 허브입니다. **Automations**와 **Rules** 두 개의 탭으로 구성된 단일 화면에서 다음 작업을 할 수 있습니다: diff --git a/docs/ko/enterprise/features/agent-control-plane/rules.mdx b/docs/ko/enterprise/features/agent-control-plane/rules.mdx index c8f5d5cd6..b4e9e6b6a 100644 --- a/docs/ko/enterprise/features/agent-control-plane/rules.mdx +++ b/docs/ko/enterprise/features/agent-control-plane/rules.mdx @@ -6,6 +6,14 @@ icon: "shield-check" mode: "wide" --- + + **ACP (베타) 문서 내비게이션** + + - [개요](/ko/enterprise/features/agent-control-plane/overview) + - [모니터링](/ko/enterprise/features/agent-control-plane/monitoring) + - **규칙** *(현재 페이지)* + + ## 개요 규칙(Rules)은 각 deployment를 개별 설정하는 대신, 정책 — 현재: **PII Redaction** — 을 한 번에 여러 자동화에 적용할 수 있게 해줍니다. 관리하려면 [Agent Control Plane](/ko/enterprise/features/agent-control-plane/overview)에서 **Rules** 탭을 엽니다. diff --git a/docs/pt-BR/enterprise/features/agent-control-plane/monitoring.mdx b/docs/pt-BR/enterprise/features/agent-control-plane/monitoring.mdx index 20d70150f..f578423d6 100644 --- a/docs/pt-BR/enterprise/features/agent-control-plane/monitoring.mdx +++ b/docs/pt-BR/enterprise/features/agent-control-plane/monitoring.mdx @@ -6,6 +6,14 @@ icon: "gauge" mode: "wide" --- + + **Navegação da Documentação do ACP (Beta)** + + - [Visão Geral](/pt-BR/enterprise/features/agent-control-plane/overview) + - **Monitoramento** *(você está aqui)* + - [Regras](/pt-BR/enterprise/features/agent-control-plane/rules) + + ## Visão Geral A aba **Automações** é a visão de operações somente leitura do [Agent Control Plane](/pt-BR/enterprise/features/agent-control-plane/overview). Ela combina dois cards de métricas, um sankey interativo e duas sub-tabelas — **Automações** e **Consumo** — nas quais você pode buscar, filtrar e ordenar. diff --git a/docs/pt-BR/enterprise/features/agent-control-plane/overview.mdx b/docs/pt-BR/enterprise/features/agent-control-plane/overview.mdx index dedd5bbf1..750309bac 100644 --- a/docs/pt-BR/enterprise/features/agent-control-plane/overview.mdx +++ b/docs/pt-BR/enterprise/features/agent-control-plane/overview.mdx @@ -5,6 +5,14 @@ sidebarTitle: Visão Geral icon: "book-open" --- + + **Navegação da Documentação do ACP (Beta)** + + - **Visão Geral** *(você está aqui)* + - [Monitoramento](/pt-BR/enterprise/features/agent-control-plane/monitoring) + - [Regras](/pt-BR/enterprise/features/agent-control-plane/rules) + + ## Visão Geral O **Agent Control Plane** (ACP) é o hub de operações para tudo que você tem rodando no CrewAI AMP. É uma tela única — dividida nas abas **Automações** e **Regras** — que permite à sua equipe: diff --git a/docs/pt-BR/enterprise/features/agent-control-plane/rules.mdx b/docs/pt-BR/enterprise/features/agent-control-plane/rules.mdx index b09a5a208..7d00fec86 100644 --- a/docs/pt-BR/enterprise/features/agent-control-plane/rules.mdx +++ b/docs/pt-BR/enterprise/features/agent-control-plane/rules.mdx @@ -6,6 +6,14 @@ icon: "shield-check" mode: "wide" --- + + **Navegação da Documentação do ACP (Beta)** + + - [Visão Geral](/pt-BR/enterprise/features/agent-control-plane/overview) + - [Monitoramento](/pt-BR/enterprise/features/agent-control-plane/monitoring) + - **Regras** *(você está aqui)* + + ## Visão Geral As Regras permitem aplicar políticas — hoje: **PII Redaction** — em muitas automações de uma só vez, em vez de configurar cada deployment individualmente. Abra a aba **Regras** no [Agent Control Plane](/pt-BR/enterprise/features/agent-control-plane/overview) para gerenciá-las. diff --git a/lib/cli/pyproject.toml b/lib/cli/pyproject.toml index b3d20283a..63f28b4ee 100644 --- a/lib/cli/pyproject.toml +++ b/lib/cli/pyproject.toml @@ -8,7 +8,7 @@ authors = [ ] requires-python = ">=3.10, <3.14" dependencies = [ - "crewai-core==1.14.6a2", + "crewai-core==1.14.6", "click~=8.1.7", "pydantic>=2.11.9,<2.13", "pydantic-settings~=2.10.1", diff --git a/lib/cli/src/crewai_cli/__init__.py b/lib/cli/src/crewai_cli/__init__.py index b2f770089..ee738f982 100644 --- a/lib/cli/src/crewai_cli/__init__.py +++ b/lib/cli/src/crewai_cli/__init__.py @@ -1 +1 @@ -__version__ = "1.14.6a2" +__version__ = "1.14.6" diff --git a/lib/cli/src/crewai_cli/cli.py b/lib/cli/src/crewai_cli/cli.py index 6364dfdc3..f1a8b20ba 100644 --- a/lib/cli/src/crewai_cli/cli.py +++ b/lib/cli/src/crewai_cli/cli.py @@ -17,6 +17,7 @@ from crewai_cli.crew_chat import run_chat from crewai_cli.deploy.main import DeployCommand from crewai_cli.enterprise.main import EnterpriseConfigureCommand from crewai_cli.evaluate_crew import evaluate_crew +from crewai_cli.experimental.skills.main import SkillCommand from crewai_cli.install_crew import install_crew from crewai_cli.kickoff_flow import kickoff_flow from crewai_cli.organization.main import OrganizationCommand @@ -26,7 +27,6 @@ from crewai_cli.replay_from_task import replay_task_command from crewai_cli.reset_memories_command import reset_memories_command from crewai_cli.run_crew import run_crew from crewai_cli.settings.main import SettingsCommand -from crewai_cli.skills.main import SkillCommand from crewai_cli.task_outputs import load_task_outputs from crewai_cli.tools.main import ToolCommand from crewai_cli.train_crew import train_crew @@ -544,8 +544,19 @@ def tool_publish(is_public: bool, force: bool) -> None: @crewai.group() +def experimental() -> None: + """Experimental, unstable commands. Subject to change without notice.""" + import os + + if os.environ.get("CREWAI_EXPERIMENTAL") != "1": + raise click.UsageError( + "Experimental commands are gated. Set CREWAI_EXPERIMENTAL=1 to enable." + ) + + +@experimental.group(name="skill") def skill() -> None: - """Skill Repository related commands.""" + """Skill Repository related commands (experimental).""" @skill.command(name="create") diff --git a/lib/cli/src/crewai_cli/skills/__init__.py b/lib/cli/src/crewai_cli/experimental/__init__.py similarity index 100% rename from lib/cli/src/crewai_cli/skills/__init__.py rename to lib/cli/src/crewai_cli/experimental/__init__.py diff --git a/lib/cli/src/crewai_cli/experimental/skills/__init__.py b/lib/cli/src/crewai_cli/experimental/skills/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/cli/src/crewai_cli/skills/main.py b/lib/cli/src/crewai_cli/experimental/skills/main.py similarity index 96% rename from lib/cli/src/crewai_cli/skills/main.py rename to lib/cli/src/crewai_cli/experimental/skills/main.py index 16d705bfb..81f9b3fc5 100644 --- a/lib/cli/src/crewai_cli/skills/main.py +++ b/lib/cli/src/crewai_cli/experimental/skills/main.py @@ -23,9 +23,10 @@ console = Console() _SKILL_MD_TEMPLATE = """\ --- name: {name} -version: 0.1.0 description: | A short description of what this skill does. +metadata: + version: 0.1.0 --- ## Instructions @@ -147,7 +148,7 @@ class SkillCommand(BaseCommand, PlusAPIMixin): ) else: try: - from crewai.skills.cache import SkillCacheManager + from crewai.experimental.skills.cache import SkillCacheManager cache = SkillCacheManager() cache.store(org, name, version, archive_bytes) @@ -191,7 +192,10 @@ class SkillCommand(BaseCommand, PlusAPIMixin): raise SystemExit(1) from exc name = frontmatter.get("name") - version = frontmatter.get("version") + raw_metadata = frontmatter.get("metadata") + version = ( + raw_metadata.get("version") if isinstance(raw_metadata, dict) else None + ) description = frontmatter.get("description") if not name: @@ -362,10 +366,13 @@ class SkillCommand(BaseCommand, PlusAPIMixin): return result def _read_version(self, skill_md: Path) -> str | None: - """Read the version field from a SKILL.md file, or None.""" + """Read the version from a SKILL.md file's metadata, or None.""" try: fm = self._parse_frontmatter(skill_md.read_text()) - return fm.get("version") + raw_metadata = fm.get("metadata") + if isinstance(raw_metadata, dict): + return raw_metadata.get("version") + return None except Exception: return None diff --git a/lib/cli/tests/experimental/__init__.py b/lib/cli/tests/experimental/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/cli/tests/experimental/skills/__init__.py b/lib/cli/tests/experimental/skills/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/cli/tests/skills/test_main.py b/lib/cli/tests/experimental/skills/test_main.py similarity index 94% rename from lib/cli/tests/skills/test_main.py rename to lib/cli/tests/experimental/skills/test_main.py index f66c5b90a..f302754d9 100644 --- a/lib/cli/tests/skills/test_main.py +++ b/lib/cli/tests/experimental/skills/test_main.py @@ -36,7 +36,7 @@ def skill_command(): TokenManager().save_tokens( "test-token", (datetime.now() + timedelta(seconds=36000)).timestamp() ) - from crewai_cli.skills.main import SkillCommand + from crewai_cli.experimental.skills.main import SkillCommand cmd = SkillCommand() yield cmd @@ -142,7 +142,7 @@ class TestSkillPublish: mock_resp.status_code = 200 mock_resp.json.return_value = {} mock_client.publish_skill.return_value = mock_resp - with patch("crewai_cli.skills.main.Settings") as mock_settings_cls: + with patch("crewai_cli.experimental.skills.main.Settings") as mock_settings_cls: mock_settings_cls.return_value.org_name = None mock_settings_cls.return_value.enterprise_base_url = None with pytest.raises(SystemExit): @@ -151,14 +151,14 @@ class TestSkillPublish: def test_publish_calls_api(self, skill_command): with in_temp_dir(): Path("SKILL.md").write_text( - "---\nname: my-skill\nversion: 1.0.0\ndescription: A test skill.\n---\nInstructions." + "---\nname: my-skill\ndescription: A test skill.\nmetadata:\n version: 1.0.0\n---\nInstructions." ) mock_resp = MagicMock() mock_resp.is_success = True mock_resp.status_code = 200 mock_resp.json.return_value = {} skill_command.plus_api_client.publish_skill = MagicMock(return_value=mock_resp) - with patch("crewai_cli.skills.main.Settings") as mock_settings_cls: + with patch("crewai_cli.experimental.skills.main.Settings") as mock_settings_cls: mock_settings_cls.return_value.org_name = "acme" mock_settings_cls.return_value.enterprise_base_url = None diff --git a/lib/crewai-core/src/crewai_core/__init__.py b/lib/crewai-core/src/crewai_core/__init__.py index b2f770089..ee738f982 100644 --- a/lib/crewai-core/src/crewai_core/__init__.py +++ b/lib/crewai-core/src/crewai_core/__init__.py @@ -1 +1 @@ -__version__ = "1.14.6a2" +__version__ = "1.14.6" diff --git a/lib/crewai-files/src/crewai_files/__init__.py b/lib/crewai-files/src/crewai_files/__init__.py index c58f942cf..96f55b568 100644 --- a/lib/crewai-files/src/crewai_files/__init__.py +++ b/lib/crewai-files/src/crewai_files/__init__.py @@ -152,4 +152,4 @@ __all__ = [ "wrap_file_source", ] -__version__ = "1.14.6a2" +__version__ = "1.14.6" diff --git a/lib/crewai-tools/pyproject.toml b/lib/crewai-tools/pyproject.toml index 0bea478a2..6a79c49e4 100644 --- a/lib/crewai-tools/pyproject.toml +++ b/lib/crewai-tools/pyproject.toml @@ -10,7 +10,7 @@ requires-python = ">=3.10, <3.14" dependencies = [ "pytube~=15.0.0", "requests>=2.33.0,<3", - "crewai==1.14.6a2", + "crewai==1.14.6", "tiktoken>=0.8.0,<0.13", "beautifulsoup4~=4.13.4", "python-docx~=1.2.0", diff --git a/lib/crewai-tools/src/crewai_tools/__init__.py b/lib/crewai-tools/src/crewai_tools/__init__.py index 6697aa78a..da788f8e1 100644 --- a/lib/crewai-tools/src/crewai_tools/__init__.py +++ b/lib/crewai-tools/src/crewai_tools/__init__.py @@ -330,4 +330,4 @@ __all__ = [ "ZapierActionTools", ] -__version__ = "1.14.6a2" +__version__ = "1.14.6" diff --git a/lib/crewai/pyproject.toml b/lib/crewai/pyproject.toml index 684a4f24a..fac0b19e1 100644 --- a/lib/crewai/pyproject.toml +++ b/lib/crewai/pyproject.toml @@ -8,8 +8,8 @@ authors = [ ] requires-python = ">=3.10, <3.14" dependencies = [ - "crewai-core==1.14.6a2", - "crewai-cli==1.14.6a2", + "crewai-core==1.14.6", + "crewai-cli==1.14.6", # Core Dependencies "pydantic>=2.11.9,<2.13", "openai>=2.30.0,<3", @@ -54,7 +54,7 @@ Repository = "https://github.com/crewAIInc/crewAI" [project.optional-dependencies] tools = [ - "crewai-tools==1.14.6a2", + "crewai-tools==1.14.6", ] embeddings = [ "tiktoken>=0.8.0,<0.13" diff --git a/lib/crewai/src/crewai/__init__.py b/lib/crewai/src/crewai/__init__.py index 6aa795784..5a65854e3 100644 --- a/lib/crewai/src/crewai/__init__.py +++ b/lib/crewai/src/crewai/__init__.py @@ -48,7 +48,7 @@ def _suppress_pydantic_deprecation_warnings() -> None: _suppress_pydantic_deprecation_warnings() -__version__ = "1.14.6a2" +__version__ = "1.14.6" _LAZY_IMPORTS: dict[str, tuple[str, str]] = { "Memory": ("crewai.memory.unified_memory", "Memory"), diff --git a/lib/crewai/src/crewai/agent/core.py b/lib/crewai/src/crewai/agent/core.py index 0295a2a6c..73020f115 100644 --- a/lib/crewai/src/crewai/agent/core.py +++ b/lib/crewai/src/crewai/agent/core.py @@ -472,7 +472,7 @@ class Agent(BaseAgent): for item in items: if isinstance(item, str): - from crewai.skills.registry import ( + from crewai.experimental.skills.registry import ( is_registry_ref, parse_registry_ref, resolve_registry_ref, diff --git a/lib/crewai/src/crewai/events/types/skill_events.py b/lib/crewai/src/crewai/events/types/skill_events.py index 322c053e3..aab625dda 100644 --- a/lib/crewai/src/crewai/events/types/skill_events.py +++ b/lib/crewai/src/crewai/events/types/skill_events.py @@ -60,20 +60,3 @@ class SkillLoadFailedEvent(SkillEvent): type: Literal["skill_load_failed"] = "skill_load_failed" error: str - - -class SkillDownloadStartedEvent(SkillEvent): - """Event emitted when a registry skill download begins.""" - - type: Literal["skill_download_started"] = "skill_download_started" - registry_ref: str - version: str | None = None - - -class SkillDownloadCompletedEvent(SkillEvent): - """Event emitted when a registry skill download completes.""" - - type: Literal["skill_download_completed"] = "skill_download_completed" - registry_ref: str - version: str | None = None - cache_path: Path | None = None diff --git a/lib/crewai/src/crewai/experimental/skills/__init__.py b/lib/crewai/src/crewai/experimental/skills/__init__.py new file mode 100644 index 000000000..aef3d4ed5 --- /dev/null +++ b/lib/crewai/src/crewai/experimental/skills/__init__.py @@ -0,0 +1,23 @@ +"""Experimental Skills Repository — registry refs, global cache, downloads. + +This package contains the registry-backed pieces of the skills feature +(`@org/name` refs, `~/.crewai/skills/` cache, download events). The stable +filesystem-based skill loader still lives in `crewai.skills`. +""" + +from crewai.experimental.skills.cache import SkillCacheManager +from crewai.experimental.skills.registry import ( + SkillNotCachedError, + is_registry_ref, + parse_registry_ref, + resolve_registry_ref, +) + + +__all__ = [ + "SkillCacheManager", + "SkillNotCachedError", + "is_registry_ref", + "parse_registry_ref", + "resolve_registry_ref", +] diff --git a/lib/crewai/src/crewai/experimental/skills/_flag.py b/lib/crewai/src/crewai/experimental/skills/_flag.py new file mode 100644 index 000000000..beca1d270 --- /dev/null +++ b/lib/crewai/src/crewai/experimental/skills/_flag.py @@ -0,0 +1,24 @@ +"""Experimental feature gate for the Skills Repository.""" + +from __future__ import annotations + +import os + + +ENV_VAR = "CREWAI_EXPERIMENTAL" + + +class ExperimentalFeatureDisabledError(RuntimeError): + """Raised when an experimental feature is used without the flag set.""" + + +def is_enabled() -> bool: + return os.environ.get(ENV_VAR) == "1" + + +def require_experimental_skills() -> None: + if not is_enabled(): + raise ExperimentalFeatureDisabledError( + "The Skills Repository (registry refs, cache, downloads) is " + f"experimental. Set {ENV_VAR}=1 to enable it." + ) diff --git a/lib/crewai/src/crewai/skills/cache.py b/lib/crewai/src/crewai/experimental/skills/cache.py similarity index 100% rename from lib/crewai/src/crewai/skills/cache.py rename to lib/crewai/src/crewai/experimental/skills/cache.py diff --git a/lib/crewai/src/crewai/experimental/skills/events.py b/lib/crewai/src/crewai/experimental/skills/events.py new file mode 100644 index 000000000..c97632281 --- /dev/null +++ b/lib/crewai/src/crewai/experimental/skills/events.py @@ -0,0 +1,30 @@ +"""Download lifecycle events for registry-backed skills. + +These events are emitted only by the experimental Skills Repository +(`@org/name` resolution + global cache). Local-file skill events still +live in `crewai.events.types.skill_events`. +""" + +from __future__ import annotations + +from pathlib import Path +from typing import Literal + +from crewai.events.types.skill_events import SkillEvent + + +class SkillDownloadStartedEvent(SkillEvent): + """Event emitted when a registry skill download begins.""" + + type: Literal["skill_download_started"] = "skill_download_started" + registry_ref: str + version: str | None = None + + +class SkillDownloadCompletedEvent(SkillEvent): + """Event emitted when a registry skill download completes.""" + + type: Literal["skill_download_completed"] = "skill_download_completed" + registry_ref: str + version: str | None = None + cache_path: Path | None = None diff --git a/lib/crewai/src/crewai/skills/registry.py b/lib/crewai/src/crewai/experimental/skills/registry.py similarity index 96% rename from lib/crewai/src/crewai/skills/registry.py rename to lib/crewai/src/crewai/experimental/skills/registry.py index 991fbcde2..fa502c4f0 100644 --- a/lib/crewai/src/crewai/skills/registry.py +++ b/lib/crewai/src/crewai/experimental/skills/registry.py @@ -11,7 +11,7 @@ from pathlib import Path import sys from typing import Any -from crewai.skills.cache import SkillCacheManager +from crewai.experimental.skills.cache import SkillCacheManager _logger = logging.getLogger(__name__) @@ -100,9 +100,11 @@ def resolve_registry_ref( Raises: SkillNotCachedError: When not cached and running in non-interactive mode. """ + from crewai.experimental.skills._flag import require_experimental_skills from crewai.skills.loader import activate_skill from crewai.skills.parser import load_skill_metadata + require_experimental_skills() org, name = parse_registry_ref(ref) local_path = Path.cwd() / "skills" / name @@ -152,7 +154,7 @@ def download_skill( try: from crewai.events.event_bus import crewai_event_bus - from crewai.events.types.skill_events import ( + from crewai.experimental.skills.events import ( SkillDownloadCompletedEvent, SkillDownloadStartedEvent, ) diff --git a/lib/crewai/src/crewai/skills/__init__.py b/lib/crewai/src/crewai/skills/__init__.py index a045581d2..e33e98570 100644 --- a/lib/crewai/src/crewai/skills/__init__.py +++ b/lib/crewai/src/crewai/skills/__init__.py @@ -3,20 +3,15 @@ Provides filesystem-based skill packaging with progressive disclosure. """ -from crewai.skills.cache import SkillCacheManager from crewai.skills.loader import activate_skill, discover_skills from crewai.skills.models import Skill, SkillFrontmatter from crewai.skills.parser import SkillParseError -from crewai.skills.registry import is_registry_ref, resolve_registry_ref __all__ = [ "Skill", - "SkillCacheManager", "SkillFrontmatter", "SkillParseError", "activate_skill", "discover_skills", - "is_registry_ref", - "resolve_registry_ref", ] diff --git a/lib/crewai/src/crewai/skills/models.py b/lib/crewai/src/crewai/skills/models.py index 2cf6beb78..8c23773bd 100644 --- a/lib/crewai/src/crewai/skills/models.py +++ b/lib/crewai/src/crewai/skills/models.py @@ -49,6 +49,7 @@ class SkillFrontmatter(BaseModel): license: Optional license name or reference. compatibility: Optional compatibility information (max 500 chars). metadata: Optional additional metadata as string key-value pairs. + Conventional keys include 'version' (skill semantic version). allowed_tools: Optional space-delimited list of pre-approved tools. """ @@ -71,17 +72,14 @@ class SkillFrontmatter(BaseModel): ) metadata: dict[str, str] | None = Field( default=None, - description="Arbitrary string key-value pairs for custom skill metadata.", + description="Arbitrary string key-value pairs for custom skill metadata. " + "Conventional keys include 'version' for the skill's semantic version.", ) allowed_tools: list[str] | None = Field( default=None, alias="allowed-tools", description="Pre-approved tool names the skill may use, parsed from a space-delimited string in frontmatter.", ) - version: str | None = Field( - default=None, - description="Semantic version of the skill, e.g. '1.0.0'. Optional for local skills.", - ) @model_validator(mode="before") @classmethod diff --git a/lib/crewai/tests/experimental/skills/__init__.py b/lib/crewai/tests/experimental/skills/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/crewai/tests/experimental/skills/conftest.py b/lib/crewai/tests/experimental/skills/conftest.py new file mode 100644 index 000000000..40baddaad --- /dev/null +++ b/lib/crewai/tests/experimental/skills/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture(autouse=True) +def _enable_experimental_skills(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("CREWAI_EXPERIMENTAL", "1") \ No newline at end of file diff --git a/lib/crewai/tests/skills/test_cache.py b/lib/crewai/tests/experimental/skills/test_cache.py similarity index 98% rename from lib/crewai/tests/skills/test_cache.py rename to lib/crewai/tests/experimental/skills/test_cache.py index 4316cb9b9..8b458bb3e 100644 --- a/lib/crewai/tests/skills/test_cache.py +++ b/lib/crewai/tests/experimental/skills/test_cache.py @@ -8,7 +8,7 @@ import json import tarfile from pathlib import Path -from crewai.skills.cache import SkillCacheManager +from crewai.experimental.skills.cache import SkillCacheManager def _make_tar_gz(files: dict[str, str]) -> bytes: diff --git a/lib/crewai/tests/experimental/skills/test_flag.py b/lib/crewai/tests/experimental/skills/test_flag.py new file mode 100644 index 000000000..9295de678 --- /dev/null +++ b/lib/crewai/tests/experimental/skills/test_flag.py @@ -0,0 +1,30 @@ +"""Tests for the CREWAI_EXPERIMENTAL gate on Skills Repository.""" + +from __future__ import annotations + +import pytest + +from crewai.experimental.skills._flag import ( + ExperimentalFeatureDisabledError, + require_experimental_skills, +) +from crewai.experimental.skills.registry import resolve_registry_ref + + +def test_require_raises_without_flag(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("CREWAI_EXPERIMENTAL", raising=False) + with pytest.raises(ExperimentalFeatureDisabledError): + require_experimental_skills() + + +def test_resolve_registry_ref_raises_without_flag( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.delenv("CREWAI_EXPERIMENTAL", raising=False) + with pytest.raises(ExperimentalFeatureDisabledError): + resolve_registry_ref("@acme/my-skill") + + +def test_require_passes_with_flag(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("CREWAI_EXPERIMENTAL", "1") + require_experimental_skills() \ No newline at end of file diff --git a/lib/crewai/tests/experimental/skills/test_models_version.py b/lib/crewai/tests/experimental/skills/test_models_version.py new file mode 100644 index 000000000..e3bf7048a --- /dev/null +++ b/lib/crewai/tests/experimental/skills/test_models_version.py @@ -0,0 +1,32 @@ +"""Tests for the 'version' metadata key on SkillFrontmatter. + +Per the agentskills.io spec, `version` lives under `metadata`, not as a +top-level frontmatter field. +""" + +from __future__ import annotations + +from crewai.skills.models import SkillFrontmatter + + +class TestSkillFrontmatterVersion: + def test_no_metadata_by_default(self) -> None: + fm = SkillFrontmatter(name="my-skill", description="A skill.") + assert fm.metadata is None + + def test_version_via_metadata(self) -> None: + fm = SkillFrontmatter( + name="my-skill", + description="A skill.", + metadata={"version": "1.2.3"}, + ) + assert fm.metadata is not None + assert fm.metadata["version"] == "1.2.3" + + def test_metadata_accepts_other_keys(self) -> None: + fm = SkillFrontmatter( + name="my-skill", + description="A skill.", + metadata={"version": "1.0.0", "author": "acme"}, + ) + assert fm.metadata == {"version": "1.0.0", "author": "acme"} diff --git a/lib/crewai/tests/skills/test_registry.py b/lib/crewai/tests/experimental/skills/test_registry.py similarity index 81% rename from lib/crewai/tests/skills/test_registry.py rename to lib/crewai/tests/experimental/skills/test_registry.py index 5f588ccf7..84067dc73 100644 --- a/lib/crewai/tests/skills/test_registry.py +++ b/lib/crewai/tests/experimental/skills/test_registry.py @@ -5,7 +5,7 @@ from __future__ import annotations from pathlib import Path from unittest.mock import MagicMock, patch -from crewai.skills.registry import ( +from crewai.experimental.skills.registry import ( SkillNotCachedError, is_registry_ref, parse_registry_ref, @@ -75,11 +75,11 @@ class TestResolveRegistryRef: mock_cache.get_cached_path.return_value = None with ( - patch("crewai.skills.registry._is_noninteractive", return_value=False), + patch("crewai.experimental.skills.registry._is_noninteractive", return_value=False), patch.object(Path, "cwd", return_value=tmp_path), - patch("crewai.skills.registry.SkillCacheManager", return_value=mock_cache), + patch("crewai.experimental.skills.registry.SkillCacheManager", return_value=mock_cache), ): - from crewai.skills.registry import resolve_registry_ref + from crewai.experimental.skills.registry import resolve_registry_ref skill = resolve_registry_ref("@acme/my-skill") assert skill.name == "my-skill" @@ -90,11 +90,11 @@ class TestResolveRegistryRef: mock_cache.get_cached_path.return_value = None with ( - patch("crewai.skills.registry._is_noninteractive", return_value=True), + patch("crewai.experimental.skills.registry._is_noninteractive", return_value=True), patch.object(Path, "cwd", return_value=tmp_path), - patch("crewai.skills.registry.SkillCacheManager", return_value=mock_cache), + patch("crewai.experimental.skills.registry.SkillCacheManager", return_value=mock_cache), ): - from crewai.skills.registry import resolve_registry_ref + from crewai.experimental.skills.registry import resolve_registry_ref with pytest.raises(SkillNotCachedError) as exc_info: resolve_registry_ref("@acme/ghost-skill") assert "@acme/ghost-skill" in str(exc_info.value) @@ -112,11 +112,11 @@ class TestResolveRegistryRef: # tmp_path has no ./skills/ directory with ( - patch("crewai.skills.registry._is_noninteractive", return_value=False), + patch("crewai.experimental.skills.registry._is_noninteractive", return_value=False), patch.object(Path, "cwd", return_value=tmp_path), - patch("crewai.skills.registry.SkillCacheManager", return_value=mock_cache), + patch("crewai.experimental.skills.registry.SkillCacheManager", return_value=mock_cache), ): - from crewai.skills.registry import resolve_registry_ref + from crewai.experimental.skills.registry import resolve_registry_ref skill = resolve_registry_ref("@acme/cached-skill") assert skill.name == "cached-skill" diff --git a/lib/crewai/tests/skills/test_models_version.py b/lib/crewai/tests/skills/test_models_version.py deleted file mode 100644 index 262ca2a82..000000000 --- a/lib/crewai/tests/skills/test_models_version.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Tests for the version field added to SkillFrontmatter.""" - -from __future__ import annotations - -import pytest -from pydantic import ValidationError - -from crewai.skills.models import SkillFrontmatter - - -class TestSkillFrontmatterVersion: - def test_version_defaults_to_none(self) -> None: - fm = SkillFrontmatter(name="my-skill", description="A skill.") - assert fm.version is None - - def test_version_can_be_set(self) -> None: - fm = SkillFrontmatter(name="my-skill", description="A skill.", version="1.2.3") - assert fm.version == "1.2.3" - - def test_existing_frontmatter_without_version_still_valid(self) -> None: - """Backward compat: existing SKILL.md files without version must still parse.""" - fm = SkillFrontmatter(name="old-skill", description="Old skill without version.") - assert fm.version is None - - def test_version_is_optional_string(self) -> None: - fm = SkillFrontmatter(name="my-skill", description="Desc.", version=None) - assert fm.version is None - - def test_frontmatter_is_frozen(self) -> None: - fm = SkillFrontmatter(name="my-skill", description="A skill.", version="1.0.0") - with pytest.raises(ValidationError): - fm.version = "2.0.0" # type: ignore[misc] diff --git a/lib/devtools/src/crewai_devtools/__init__.py b/lib/devtools/src/crewai_devtools/__init__.py index 67b5daefd..9a799dbe8 100644 --- a/lib/devtools/src/crewai_devtools/__init__.py +++ b/lib/devtools/src/crewai_devtools/__init__.py @@ -1,3 +1,3 @@ """CrewAI development tools.""" -__version__ = "1.14.6a2" +__version__ = "1.14.6"