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"