This is an automated email from the ASF dual-hosted git repository.

kaxil pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 86d8b47c886 Add Agent Skills support to the Common AI provider (#67786)
86d8b47c886 is described below

commit 86d8b47c88661c1e981369817af49202e64776b3
Author: Kaxil Naik <[email protected]>
AuthorDate: Sun May 31 02:07:17 2026 +0100

    Add Agent Skills support to the Common AI provider (#67786)
    
    Add AgentSkillsToolset, a pydantic-ai toolset that loads agentskills.io 
SKILL.md bundles from a local directory or a Git repository. Git credentials 
come from an Airflow git connection (HTTPS token or SSH key) resolved through 
the Git provider's GitHook: cleartext http and credential-bearing URLs are 
rejected, interactive credential prompts are disabled, and the token is 
stripped from the clone's .git/config. Sources are resolved on the worker when 
the agent enters the toolset, so a t [...]
---
 docs/spelling_wordlist.txt                         |   1 +
 providers/common/ai/docs/index.rst                 |   1 +
 providers/common/ai/docs/operators/agent.rst       |   2 +-
 providers/common/ai/docs/toolsets.rst              |  95 +++++++++
 providers/common/ai/pyproject.toml                 |  14 ++
 .../common/ai/example_dags/example_agent_skills.py | 106 ++++++++++
 .../ai/example_dags/skills/sql-reporting/SKILL.md  |  41 ++++
 .../ai/src/airflow/providers/common/ai/skills.py   | 224 +++++++++++++++++++++
 .../airflow/providers/common/ai/toolsets/skills.py | 140 +++++++++++++
 .../common/ai/tests/unit/common/ai/test_skills.py  | 166 +++++++++++++++
 .../tests/unit/common/ai/toolsets/test_skills.py   | 140 +++++++++++++
 .../check_providers_subpackages_all_have_init.py   |   3 +
 uv.lock                                            |  30 ++-
 13 files changed, 961 insertions(+), 2 deletions(-)

diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index fe93cebe41a..d8837585eed 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -837,6 +837,7 @@ instanceTemplates
 InstanceType
 instanceType
 instantiation
+InstructionPart
 integrations
 ints
 intvl
diff --git a/providers/common/ai/docs/index.rst 
b/providers/common/ai/docs/index.rst
index adde6dbe9cc..590a770d481 100644
--- a/providers/common/ai/docs/index.rst
+++ b/providers/common/ai/docs/index.rst
@@ -129,6 +129,7 @@ Dependent package
 
==================================================================================================================
  =================
 `apache-airflow-providers-common-compat 
<https://airflow.apache.org/docs/apache-airflow-providers-common-compat>`_  
``common.compat``
 `apache-airflow-providers-common-sql 
<https://airflow.apache.org/docs/apache-airflow-providers-common-sql>`_        
``common.sql``
+`apache-airflow-providers-git 
<https://airflow.apache.org/docs/apache-airflow-providers-git>`_                
      ``git``
 `apache-airflow-providers-standard 
<https://airflow.apache.org/docs/apache-airflow-providers-standard>`_           
 ``standard``
 
==================================================================================================================
  =================
 
diff --git a/providers/common/ai/docs/operators/agent.rst 
b/providers/common/ai/docs/operators/agent.rst
index f1c13fb4490..51e575d139e 100644
--- a/providers/common/ai/docs/operators/agent.rst
+++ b/providers/common/ai/docs/operators/agent.rst
@@ -304,7 +304,7 @@ Parameters
 - ``output_type``: Expected output type (default: ``str``). Set to a Pydantic
   ``BaseModel`` for structured output.
 - ``toolsets``: List of pydantic-ai toolsets (``SQLToolset``, ``HookToolset``,
-  etc.).
+  ``AgentSkillsToolset`` for :ref:`agent-skills`, etc.).
 - ``enable_tool_logging``: Wrap each toolset in
   :class:`~airflow.providers.common.ai.toolsets.logging.LoggingToolset` so that
   every tool call is logged in real time. Default ``True``.
diff --git a/providers/common/ai/docs/toolsets.rst 
b/providers/common/ai/docs/toolsets.rst
index ec3927946f9..33fc04f0e18 100644
--- a/providers/common/ai/docs/toolsets.rst
+++ b/providers/common/ai/docs/toolsets.rst
@@ -313,6 +313,101 @@ This works because PydanticAI's MCP server classes 
implement
 code instead of being managed through Airflow connections and secret backends.
 
 
+.. _agent-skills:
+
+``AgentSkillsToolset``
+----------------------
+
+:class:`~airflow.providers.common.ai.toolsets.skills.AgentSkillsToolset` loads
+`Agent Skills <https://agentskills.io>`__ -- ``SKILL.md`` bundles 
(instructions,
+and optionally scripts and resources) that the model discovers and loads *on
+demand*. Only a compact catalog of skill names and descriptions sits in the
+prompt until the model decides it needs one, so a large skill library costs few
+tokens until used (progressive disclosure).
+
+It is backed by the community `pydantic-ai-skills
+<https://github.com/DougTrajano/pydantic-ai-skills>`__ package (MIT); native
+progressive disclosure is in flight upstream in `pydantic/pydantic-ai#5230
+<https://github.com/pydantic/pydantic-ai/pull/5230>`__. Install the optional
+extra to use it:
+
+.. code-block:: bash
+
+    pip install "apache-airflow-providers-common-ai[skills]"
+
+Each source is a local directory or a connection-resolved
+:class:`~airflow.providers.common.ai.skills.GitSkills`. Sources are resolved 
when
+the agent enters the toolset, on the worker -- never while the DAG processor
+parses the file -- so a Git token is never baked into the serialized DAG, and
+cloned repositories are removed when the run ends.
+
+A local directory of ``SKILL.md`` bundles:
+
+.. exampleinclude:: 
/../../ai/src/airflow/providers/common/ai/example_dags/example_agent_skills.py
+    :language: python
+    :start-after: [START howto_operator_agent_skills_local]
+    :end-before: [END howto_operator_agent_skills_local]
+
+A Git repository, with credentials from an Airflow connection:
+
+.. exampleinclude:: 
/../../ai/src/airflow/providers/common/ai/example_dags/example_agent_skills.py
+    :language: python
+    :start-after: [START howto_operator_agent_skills_git]
+    :end-before: [END howto_operator_agent_skills_git]
+
+For a private repository, point ``conn_id`` at a
+:doc:`git connection <apache-airflow-providers-git:connections/git>`; 
credentials
+are resolved through the Git provider's ``GitHook`` (an HTTPS token in the
+connection password, or an SSH key in the connection's extra). A plain 
``http://``
+URL with ``conn_id`` is rejected so a credential is never sent in cleartext, 
and a
+``repo_url`` that embeds a username/password is rejected (use ``conn_id``). 
After
+cloning, the credential is stripped from the checkout's ``.git/config``. As 
with
+any ``git clone``, the worker's own git configuration (credential helpers, SSH
+agent) may still apply, so run workers without ambient git credentials if you
+need strict isolation.
+
+.. warning::
+
+    Skill bundles can contain scripts that the agent may run on the worker via
+    the ``run_skill_script`` tool. For a remote source, anyone who can modify 
the
+    repository can introduce code that executes on your worker, outside DAG
+    review and versioning. Point ``GitSkills`` at a trusted repository, pin
+    ``branch`` to a trusted ref, and treat skill contents as code that runs in
+    your environment.
+
+Parameters
+^^^^^^^^^^
+
+- ``sources``: List of skill sources -- local directory paths and/or
+  :class:`~airflow.providers.common.ai.skills.GitSkills`.
+- ``exclude_tools``: Optional set of skill tool names to hide from the agent
+  (e.g. ``{"run_skill_script"}`` to disable on-worker script execution).
+
+Using Agent Skills with other frameworks
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+``AgentSkillsToolset`` is a standard pydantic-ai toolset, so it also works 
with a
+plain ``pydantic_ai.Agent`` you build yourself, not just ``AgentOperator``.
+
+Because Agent Skills is a cross-framework format, the connection handling is 
also
+reusable through :func:`~airflow.providers.common.ai.skills.resolve_skills`, 
which
+resolves sources to local ``SKILL.md`` directories that any loader accepts:
+
+.. code-block:: python
+
+    from airflow.providers.common.ai.skills import GitSkills, resolve_skills
+
+    sources = ["./skills", GitSkills(repo_url="https://github.com/org/skills";, 
conn_id="github_skills")]
+    with resolve_skills(sources) as dirs:
+        # LangChain DeepAgents
+        agent = create_deep_agent(model="openai:gpt-5.4", skills=dirs)
+        # ...or Strands
+        agent = Agent(plugins=[AgentSkills(skills=dirs)])
+
+``resolve_skills`` needs the Git provider (for ``GitSkills``) but not 
pydantic-ai,
+and removes any cloned directories when the ``with`` block exits.
+
+
 Security
 --------
 
diff --git a/providers/common/ai/pyproject.toml 
b/providers/common/ai/pyproject.toml
index 0f684c26941..4f370d17c35 100644
--- a/providers/common/ai/pyproject.toml
+++ b/providers/common/ai/pyproject.toml
@@ -80,6 +80,15 @@ dependencies = [
 "google" = ["pydantic-ai-slim[google]"]
 "openai" = ["pydantic-ai-slim[openai]"]
 "mcp" = ["pydantic-ai-slim[mcp]"]
+# Agent Skills (agentskills.io) support. pydantic-ai-skills provides the 
toolset
+# (pulls in pydantic-ai-slim>=1.74 transitively; the provider base floor stays
+# 1.71); the git provider supplies GitHook + GitPython for cloning GitSkills 
with
+# credentials from a `git` connection. Native progressive disclosure is tracked
+# upstream in pydantic/pydantic-ai#5230; revisit this extra once that lands.
+"skills" = [
+    "apache-airflow-providers-git>=0.4.0",
+    "pydantic-ai-skills>=0.11.0",
+]
 "avro" = [
     'fastavro>=1.10.0; python_version < "3.14"',
     'fastavro>=1.12.1; python_version >= "3.14"',
@@ -105,6 +114,9 @@ dependencies = [
 ]
 "pdf" = ["pypdf>=4.0.0"]
 "docx" = ["python-docx>=1.0.0"]
+"git" = [
+    "apache-airflow-providers-git"
+]
 
 [dependency-groups]
 dev = [
@@ -113,10 +125,12 @@ dev = [
     "apache-airflow-devel-common",
     "apache-airflow-providers-common-compat",
     "apache-airflow-providers-common-sql",
+    "apache-airflow-providers-git",
     "apache-airflow-providers-standard",
     # Additional devel dependencies (do not remove this line and add extra 
development dependencies)
     "sqlglot>=30.0.0",
     "pydantic-ai-slim[mcp]",
+    "pydantic-ai-skills>=0.11.0",
     "apache-airflow-providers-common-sql[datafusion]",
     "langchain>=1.0.0",
     "llama-index-core>=0.13.0",
diff --git 
a/providers/common/ai/src/airflow/providers/common/ai/example_dags/example_agent_skills.py
 
b/providers/common/ai/src/airflow/providers/common/ai/example_dags/example_agent_skills.py
new file mode 100644
index 00000000000..4608446cf10
--- /dev/null
+++ 
b/providers/common/ai/src/airflow/providers/common/ai/example_dags/example_agent_skills.py
@@ -0,0 +1,106 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Example DAGs demonstrating Agent Skills with ``AgentOperator``.
+
+`Agent Skills <https://agentskills.io>`__ are ``SKILL.md`` bundles the model
+discovers and loads on demand (progressive disclosure). They are passed to the
+agent as an ``AgentSkillsToolset`` in the operator's ``toolsets=`` list. Skill
+sources are resolved when the task runs, on the worker (not while the DAG
+processor parses the file), so a Git token resolved from an Airflow connection
+is never baked into the serialized DAG.
+
+These DAGs need the optional ``skills`` extra::
+
+    pip install "apache-airflow-providers-common-ai[skills]"
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+from airflow.providers.common.ai.operators.agent import AgentOperator
+from airflow.providers.common.ai.skills import GitSkills
+from airflow.providers.common.ai.toolsets.skills import AgentSkillsToolset
+from airflow.providers.common.ai.toolsets.sql import SQLToolset
+from airflow.providers.common.compat.sdk import dag
+
+# Skills ship next to this DAG file; resolve relative to __file__ so the path
+# holds regardless of the dag-processor's working directory.
+SKILLS_DIR = Path(__file__).parent / "skills"
+
+
+# ---------------------------------------------------------------------------
+# 1. Local filesystem skills (a directory of SKILL.md bundles)
+# ---------------------------------------------------------------------------
+
+
+# [START howto_operator_agent_skills_local]
+@dag(tags=["example"])
+def example_agent_skills_local():
+    AgentOperator(
+        task_id="reporter",
+        prompt="How many orders did our top 5 customers place last month?",
+        llm_conn_id="pydanticai_default",
+        system_prompt="You are a data analyst. Consult your skills before 
writing SQL.",
+        toolsets=[
+            AgentSkillsToolset(sources=[str(SKILLS_DIR)]),
+            SQLToolset(
+                db_conn_id="postgres_default",
+                allowed_tables=["customers", "orders"],
+                max_rows=50,
+            ),
+        ],
+    )
+
+
+# [END howto_operator_agent_skills_local]
+
+example_agent_skills_local()
+
+
+# ---------------------------------------------------------------------------
+# 2. Remote skills from a Git repo, credentials from an Airflow connection
+# ---------------------------------------------------------------------------
+# ``github_skills`` is a git connection (HTTPS token in the password, or an SSH
+# key in the extra). The DAG only references it by id; no credential is 
inlined.
+
+
+# [START howto_operator_agent_skills_git]
+@dag(tags=["example"])
+def example_agent_skills_git():
+    AgentOperator(
+        task_id="support_agent",
+        prompt="Summarize our refund policy and apply it to order 12345.",
+        llm_conn_id="pydanticai_default",
+        system_prompt="You are a support agent. Load the relevant skill before 
answering.",
+        toolsets=[
+            AgentSkillsToolset(
+                sources=[
+                    GitSkills(
+                        repo_url="https://github.com/my-org/agent-skills";,
+                        conn_id="github_skills",
+                        path="skills",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+
+# [END howto_operator_agent_skills_git]
+
+example_agent_skills_git()
diff --git 
a/providers/common/ai/src/airflow/providers/common/ai/example_dags/skills/sql-reporting/SKILL.md
 
b/providers/common/ai/src/airflow/providers/common/ai/example_dags/skills/sql-reporting/SKILL.md
new file mode 100644
index 00000000000..b3bef6b2348
--- /dev/null
+++ 
b/providers/common/ai/src/airflow/providers/common/ai/example_dags/skills/sql-reporting/SKILL.md
@@ -0,0 +1,41 @@
+---
+name: sql-reporting
+description: Conventions and review steps for writing analytics SQL against 
the warehouse. Use whenever the task involves querying tables, building a 
report, or aggregating metrics.
+license: Apache-2.0
+---
+<!-- SPDX-License-Identifier: Apache-2.0
+     https://www.apache.org/licenses/LICENSE-2.0 -->
+
+# SQL Reporting Skill
+
+Apply this skill before writing or running any analytics SQL so reports stay
+consistent and safe.
+
+## When to Use This Skill
+
+Use this skill when the task involves:
+
+- Querying warehouse tables for a metric, report, or dashboard figure
+- Aggregating rows (counts, sums, rolling windows)
+- Cross-referencing two or more tables
+
+## Conventions
+
+1. Always `SELECT` explicit column names, never `SELECT *`.
+2. Filter on a partition/date column first to bound the scan.
+3. Alias aggregates with snake_case names (`order_count`, not `count(*)`).
+4. Cap exploratory queries with `LIMIT` unless an aggregate already collapses
+   the result set.
+5. Prefer `COUNT(DISTINCT ...)` over a sub-query when de-duplicating.
+
+## Review Checklist (run before returning an answer)
+
+- [ ] No `SELECT *`.
+- [ ] A date or partition predicate is present.
+- [ ] Every aggregate has an explicit alias.
+- [ ] The query reads only from tables the task actually needs.
+
+## Output Format
+
+Return the final SQL in a fenced ```sql block, then one sentence describing
+what the query returns.
diff --git a/providers/common/ai/src/airflow/providers/common/ai/skills.py 
b/providers/common/ai/src/airflow/providers/common/ai/skills.py
new file mode 100644
index 00000000000..20238ec8511
--- /dev/null
+++ b/providers/common/ai/src/airflow/providers/common/ai/skills.py
@@ -0,0 +1,224 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+Framework-agnostic `Agent Skills <https://agentskills.io>`__ sources for 
Airflow.
+
+This module resolves skill *sources* -- a local directory or a ``GitSkills``
+descriptor -- into local ``SKILL.md`` directories, cloning Git repositories 
with
+a token taken from an Airflow connection. The output is a list of directory
+paths, the interchange format every Agent Skills implementation consumes
+(pydantic-ai-skills, LangChain DeepAgents, Strands), so the same Airflow
+credential handling works across frameworks.
+
+For pydantic-ai use the 
:class:`~airflow.providers.common.ai.toolsets.skills.AgentSkillsToolset`
+binding. For other frameworks, resolve the directories yourself::
+
+    from airflow.providers.common.ai.skills import GitSkills, resolve_skills
+
+    with resolve_skills(["./skills", GitSkills(repo_url="https://...";, 
conn_id="github")]) as dirs:
+        # LangChain DeepAgents
+        agent = create_deep_agent(model=..., skills=dirs)
+        # ...or Strands
+        agent = Agent(plugins=[AgentSkills(skills=dirs)])
+
+Resolution (connection lookup, clone) happens when ``resolve_skills`` is 
entered,
+so run it inside the task, not at module import / DAG-parse time. The context
+manager removes any cloned directories on exit.
+"""
+
+from __future__ import annotations
+
+import os
+import shutil
+import tempfile
+from collections.abc import Callable, Iterator
+from contextlib import contextmanager
+from dataclasses import dataclass
+from typing import Any, Union
+from urllib.parse import urlsplit
+
+__all__ = ["GitSkills", "SkillSource", "resolve_skills"]
+
+
+@dataclass
+class GitSkills:
+    """
+    Agent Skills cloned from a Git repository when resolved.
+
+    :param repo_url: HTTPS or SSH URL of the repository to clone.
+    :param conn_id: Airflow ``git`` connection used for credentials, resolved
+        through the Git provider's ``GitHook`` (HTTPS token in the connection
+        password, or an SSH key in the connection's extra). **Set this for 
private
+        repositories.** Plain ``http://`` is rejected when ``conn_id`` is set 
so a
+        credential is never sent in cleartext, and a ``repo_url`` with embedded
+        credentials is rejected (use ``conn_id`` instead). When ``conn_id`` is
+        ``None`` the clone is unauthenticated; as with any ``git clone``, the
+        worker's own git configuration (credential helpers, SSH agent) may 
still
+        apply, so run workers without ambient git credentials if you need 
strict
+        isolation.
+    :param path: Sub-path inside the repository that holds the skill 
directories
+        (e.g. ``"skills"``). Defaults to the repository root.
+    :param branch: Branch, tag, or ref to check out. Defaults to the
+        repository's default branch.
+
+    .. warning::
+
+        Skill bundles can contain scripts an agent may run on the worker. 
Because
+        the repository is fetched at run time, anyone who can modify it can
+        introduce code that runs in your environment, outside DAG review. Point
+        ``repo_url`` at a trusted repository and pin ``branch`` to a trusted 
ref.
+    """
+
+    repo_url: str
+    conn_id: str | None = None
+    path: str = ""
+    branch: str | None = None
+
+
+SkillSource = Union[str, "os.PathLike[str]", GitSkills]
+
+
+def _clone_git(source: GitSkills) -> tuple[str, str]:
+    """
+    Clone *source* into a fresh temp dir; return (skills_dir, 
temp_dir_to_remove).
+
+    When ``conn_id`` is set, credentials come from the Airflow ``git`` 
connection
+    via ``GitHook`` (HTTPS token or SSH key). The token is stripped from the
+    clone's ``.git/config`` afterwards so a skill script in the checkout cannot
+    read it back, interactive credential prompts are disabled, and the temp 
dir is
+    removed if the clone fails. As with any ``git clone``, the worker's own git
+    configuration (credential helpers, SSH agent) may still apply.
+    """
+    try:
+        from git import Repo
+    except ImportError as e:
+        raise ValueError(
+            "GitSkills requires GitPython. Install the 'skills' extra: "
+            "pip install 'apache-airflow-providers-common-ai[skills]'."
+        ) from e
+
+    # Reject credentials embedded directly in the URL: they would be stored in
+    # the serialized DAG, written back into .git/config by the scrub below, and
+    # leak into error messages. Credentials must come from ``conn_id`` instead.
+    split = urlsplit(source.repo_url)
+    if split.username or split.password:
+        raise ValueError(
+            "GitSkills repo_url must not embed a username or password; pass 
credentials "
+            "through a git connection via conn_id instead."
+        )
+    if source.conn_id and source.repo_url.startswith("http://";):
+        raise ValueError(
+            f"GitSkills refuses to send credentials from conn_id 
{source.conn_id!r} over plain "
+            f"http://; use an https:// URL (token) or an ssh URL with a key on 
the connection."
+        )
+
+    # ``path`` is joined onto the clone dir; an absolute or upward-traversing
+    # value would point outside the checkout. Require a relative sub-path so a
+    # misconfigured value fails fast instead of silently reading elsewhere.
+    if source.path:
+        normalized = os.path.normpath(source.path)
+        if os.path.isabs(source.path) or normalized == ".." or 
normalized.startswith(".." + os.sep):
+            raise ValueError(
+                f"GitSkills path must be a relative sub-path inside the 
repository; got {source.path!r}."
+            )
+
+    clone_kwargs: dict[str, Any] = {"depth": 1}
+    if source.branch:
+        clone_kwargs["branch"] = source.branch
+    # Never drop into an interactive credential prompt on the worker.
+    base_env = {"GIT_TERMINAL_PROMPT": "0"}
+
+    temp_dir = tempfile.mkdtemp(prefix="airflow_skills_")
+    hook = None
+    try:
+        if source.conn_id:
+            from airflow.providers.git.hooks.git import GitHook
+
+            hook = GitHook(git_conn_id=source.conn_id, 
repo_url=source.repo_url)
+            with hook.configure_hook_env():
+                clone_url = hook.repo_url or source.repo_url
+                repo = Repo.clone_from(clone_url, temp_dir, env={**base_env, 
**hook.env}, **clone_kwargs)
+        else:
+            repo = Repo.clone_from(source.repo_url, temp_dir, env=base_env, 
**clone_kwargs)
+        # Strip any embedded credential from .git/config so a skill script in 
the
+        # checkout cannot read it back out of the temporary clone.
+        repo.remote("origin").set_url(source.repo_url)
+    except Exception as exc:
+        shutil.rmtree(temp_dir, ignore_errors=True)
+        # GitPython errors embed the failing command, which may contain the
+        # token-bearing URL GitHook built -- scrub it before surfacing to logs.
+        message = str(exc)
+        if hook is not None and hook.repo_url and hook.repo_url != 
source.repo_url:
+            message = message.replace(hook.repo_url, source.repo_url)
+        raise RuntimeError(f"Failed to clone {source.repo_url}: {message}") 
from None
+    except BaseException:
+        shutil.rmtree(temp_dir, ignore_errors=True)
+        raise
+
+    skills_dir = os.path.join(temp_dir, source.path) if source.path else 
temp_dir
+    return skills_dir, temp_dir
+
+
+def _materialize_skills(sources: list[SkillSource]) -> tuple[list[str], 
Callable[[], None]]:
+    """
+    Resolve *sources* to local directories; return (directories, cleanup).
+
+    ``cleanup`` removes any directories cloned for ``GitSkills`` sources; local
+    directory sources are returned untouched and are not removed.
+    """
+    directories: list[str] = []
+    temp_dirs: list[str] = []
+    try:
+        for source in sources:
+            if isinstance(source, GitSkills):
+                skills_dir, temp_dir = _clone_git(source)
+                directories.append(skills_dir)
+                temp_dirs.append(temp_dir)
+            elif isinstance(source, (str, os.PathLike)):
+                directories.append(os.fspath(source))
+            else:
+                raise TypeError(
+                    f"Unsupported skill source {type(source).__name__!r}; 
expected a path or GitSkills."
+                )
+    except BaseException:
+        # Don't leak partially cloned directories if a later source fails.
+        for temp_dir in temp_dirs:
+            shutil.rmtree(temp_dir, ignore_errors=True)
+        raise
+
+    def cleanup() -> None:
+        for temp_dir in temp_dirs:
+            shutil.rmtree(temp_dir, ignore_errors=True)
+
+    return directories, cleanup
+
+
+@contextmanager
+def resolve_skills(sources: list[SkillSource]) -> Iterator[list[str]]:
+    """
+    Resolve skill *sources* to local ``SKILL.md`` directories.
+
+    Yields a list of directory paths suitable for any Agent Skills loader
+    (pydantic-ai-skills, LangChain DeepAgents ``skills=``, Strands
+    ``AgentSkills(skills=...)``). Cloned repositories are removed on exit, so 
use
+    the returned directories inside the ``with`` block.
+    """
+    directories, cleanup = _materialize_skills(sources)
+    try:
+        yield directories
+    finally:
+        cleanup()
diff --git 
a/providers/common/ai/src/airflow/providers/common/ai/toolsets/skills.py 
b/providers/common/ai/src/airflow/providers/common/ai/toolsets/skills.py
new file mode 100644
index 00000000000..6df834e2dc2
--- /dev/null
+++ b/providers/common/ai/src/airflow/providers/common/ai/toolsets/skills.py
@@ -0,0 +1,140 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+A pydantic-ai toolset that loads `Agent Skills <https://agentskills.io>`__.
+
+``AgentSkillsToolset`` is a normal pydantic-ai ``AbstractToolset``: it can be
+passed to :class:`~airflow.providers.common.ai.operators.agent.AgentOperator`
+via ``toolsets=`` or used directly with a ``pydantic_ai.Agent`` anywhere the
+Airflow connection backend is reachable (i.e. inside a worker/task runtime).
+
+Skill sources are resolved lazily when the agent enters the toolset (run time,
+on the worker), never at DAG-parse time, so a Git token resolved from an 
Airflow
+connection is never baked into the serialized DAG. Cloned repositories are
+removed when the toolset context exits.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from airflow.providers.common.ai.skills import SkillSource, _materialize_skills
+
+try:
+    from pydantic_ai.toolsets.abstract import AbstractToolset
+except ImportError:  # pragma: no cover - pydantic-ai is a provider dependency
+    AbstractToolset = object  # type: ignore[assignment,misc]
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Sequence
+
+    from pydantic_ai._run_context import RunContext
+    from pydantic_ai.messages import InstructionPart
+    from pydantic_ai.toolsets.abstract import ToolsetTool
+
+
+class AgentSkillsToolset(AbstractToolset):
+    """
+    A pydantic-ai toolset that loads Agent Skills, with Git credentials from 
Airflow connections.
+
+    Sources are local directory paths and/or
+    :class:`~airflow.providers.common.ai.skills.GitSkills`.
+
+    :param sources: Skill sources -- local directory paths and/or 
``GitSkills``.
+    :param exclude_tools: Optional set of skill tool names to hide from the 
agent
+        (e.g. ``{"run_skill_script"}`` to disable on-worker script execution).
+
+    Requires the ``skills`` extra: ``pip install 
"apache-airflow-providers-common-ai[skills]"``.
+    """
+
+    def __init__(
+        self,
+        sources: list[SkillSource],
+        *,
+        exclude_tools: set[str] | None = None,
+    ) -> None:
+        self._sources = list(sources)
+        self._exclude_tools = exclude_tools
+        self._inner: Any = None
+        self._cleanup: Callable[[], None] | None = None
+
+    @property
+    def id(self) -> str | None:
+        return None
+
+    async def for_run(self, ctx: RunContext) -> AbstractToolset:
+        # Per-run isolation: pydantic-ai shares one toolset instance across 
runs,
+        # but we hold per-run clone/cleanup state on __aenter__/__aexit__. Hand
+        # each run its own instance so concurrent runs never clobber each 
other.
+        return AgentSkillsToolset(self._sources, 
exclude_tools=self._exclude_tools)
+
+    async def __aenter__(self) -> AgentSkillsToolset:
+        # Resolve + clone at run time, on the worker -- not at DAG-parse time.
+        try:
+            from pydantic_ai_skills import SkillsToolset
+        except ImportError as e:
+            raise ValueError(
+                "AgentSkillsToolset requires the optional 'skills' extra: "
+                "pip install 'apache-airflow-providers-common-ai[skills]'."
+            ) from e
+
+        directories, cleanup = _materialize_skills(self._sources)
+        self._cleanup = cleanup
+        try:
+            kwargs: dict[str, Any] = {"directories": directories}
+            if self._exclude_tools:
+                kwargs["exclude_tools"] = self._exclude_tools
+            self._inner = SkillsToolset(**kwargs)
+            await self._inner.__aenter__()
+        except BaseException:
+            cleanup()
+            self._inner = None
+            self._cleanup = None
+            raise
+        return self
+
+    async def __aexit__(self, *args: Any) -> bool | None:
+        try:
+            if self._inner is not None:
+                return await self._inner.__aexit__(*args)
+            return None
+        finally:
+            if self._cleanup is not None:
+                self._cleanup()
+            self._inner = None
+            self._cleanup = None
+
+    def _require_inner(self) -> Any:
+        if self._inner is None:
+            raise RuntimeError(
+                "AgentSkillsToolset must be entered via 'async with' (the 
agent does this "
+                "during a run) before its tools are used."
+            )
+        return self._inner
+
+    async def get_tools(self, ctx: RunContext) -> dict[str, ToolsetTool]:
+        return await self._require_inner().get_tools(ctx)
+
+    async def call_tool(
+        self, name: str, tool_args: dict[str, Any], ctx: RunContext, tool: 
ToolsetTool
+    ) -> Any:
+        return await self._require_inner().call_tool(name, tool_args, ctx, 
tool)
+
+    async def get_instructions(
+        self, ctx: RunContext
+    ) -> str | InstructionPart | Sequence[str | InstructionPart] | None:
+        return await self._require_inner().get_instructions(ctx)
diff --git a/providers/common/ai/tests/unit/common/ai/test_skills.py 
b/providers/common/ai/tests/unit/common/ai/test_skills.py
new file mode 100644
index 00000000000..cbaddb2787e
--- /dev/null
+++ b/providers/common/ai/tests/unit/common/ai/test_skills.py
@@ -0,0 +1,166 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Tests for the framework-agnostic skill source resolver."""
+
+from __future__ import annotations
+
+import os
+from unittest.mock import patch
+
+import pytest
+
+from airflow.providers.common.ai.skills import GitSkills, resolve_skills
+
+pytest.importorskip("git")
+pytest.importorskip("airflow.providers.git.hooks.git")
+
+
+class TestGitSkills:
+    def test_defaults(self):
+        source = GitSkills(repo_url="https://github.com/org/repo";)
+        assert source.repo_url == "https://github.com/org/repo";
+        assert source.conn_id is None
+        assert source.path == ""
+        assert source.branch is None
+
+    def test_holds_no_secret(self):
+        source = GitSkills(repo_url="u", conn_id="github_skills")
+        assert "github_skills" in repr(source)
+        assert not hasattr(source, "token")
+        assert not hasattr(source, "password")
+
+
+class TestResolveLocal:
+    def test_local_dirs_pass_through_untouched(self, tmp_path):
+        d1, d2 = str(tmp_path / "a"), str(tmp_path / "b")
+        with resolve_skills([d1, d2]) as dirs:
+            assert dirs == [d1, d2]
+
+    def test_unsupported_source_raises_typeerror(self):
+        with pytest.raises(TypeError, match="Unsupported skill source"):
+            with resolve_skills([123]):
+                pass
+
+
+class TestResolveGitWithHook:
+    """Credentials come from the git connection via GitHook; never the 
environment."""
+
+    @patch("airflow.providers.git.hooks.git.GitHook")
+    @patch("git.Repo")
+    def test_conn_id_uses_githook_and_scrubs_config(self, mock_repo, 
mock_githook):
+        mock_githook.return_value.repo_url = 
"https://user:[email protected]/org/repo";
+        mock_githook.return_value.env = {}
+        src = GitSkills(repo_url="https://github.com/org/repo";, 
conn_id="git_default", path="skills")
+
+        with resolve_skills([src]) as dirs:
+            assert dirs[0].endswith(os.sep + "skills")
+
+        mock_githook.assert_called_once_with(
+            git_conn_id="git_default", repo_url="https://github.com/org/repo";
+        )
+        # Cloned via the hook's token-bearing URL...
+        assert mock_repo.clone_from.call_args.args[0] == 
"https://user:[email protected]/org/repo";
+        # ...with interactive credential prompts disabled...
+        assert 
mock_repo.clone_from.call_args.kwargs["env"]["GIT_TERMINAL_PROMPT"] == "0"
+        # ...then .git/config is scrubbed back to the credential-free URL.
+        
mock_repo.clone_from.return_value.remote.return_value.set_url.assert_called_once_with(
+            "https://github.com/org/repo";
+        )
+
+    @pytest.mark.parametrize(
+        "repo_url",
+        ["https://user:[email protected]/org/repo";, 
"https://[email protected]/org/repo";],
+    )
+    @patch("airflow.providers.git.hooks.git.GitHook")
+    @patch("git.Repo")
+    def test_repo_url_with_embedded_credentials_rejected(self, mock_repo, 
mock_githook, repo_url):
+        # Credentials in the URL would land in the serialized DAG and be 
written
+        # back to .git/config by the scrub -- reject them outright.
+        with pytest.raises(ValueError, match="must not embed"):
+            with resolve_skills([GitSkills(repo_url=repo_url, 
conn_id="git_default")]):
+                pass
+        mock_githook.assert_not_called()
+        mock_repo.clone_from.assert_not_called()
+
+    @pytest.mark.parametrize("bad_path", ["/etc", "../outside", "a/../../b"])
+    @patch("airflow.providers.git.hooks.git.GitHook")
+    @patch("git.Repo")
+    def test_absolute_or_traversing_path_rejected(self, mock_repo, 
mock_githook, bad_path):
+        with pytest.raises(ValueError, match="relative sub-path"):
+            with resolve_skills(
+                [GitSkills(repo_url="https://github.com/org/repo";, 
conn_id="git_default", path=bad_path)]
+            ):
+                pass
+        mock_repo.clone_from.assert_not_called()
+
+    @patch("airflow.providers.git.hooks.git.GitHook")
+    @patch("git.Repo")
+    def test_http_with_conn_id_is_rejected(self, mock_repo, mock_githook):
+        with pytest.raises(ValueError, match="http://";):
+            with 
resolve_skills([GitSkills(repo_url="http://github.com/org/repo";, 
conn_id="git_default")]):
+                pass
+        mock_githook.assert_not_called()
+        mock_repo.clone_from.assert_not_called()
+
+    @patch("airflow.providers.git.hooks.git.GitHook")
+    @patch("git.Repo")
+    def test_no_conn_id_clones_anonymously_without_hook(self, mock_repo, 
mock_githook):
+        with 
resolve_skills([GitSkills(repo_url="https://github.com/org/repo";)]):
+            pass
+        mock_githook.assert_not_called()
+        assert mock_repo.clone_from.call_args.args[0] == 
"https://github.com/org/repo";
+
+    @patch("airflow.providers.git.hooks.git.GitHook")
+    @patch("git.Repo")
+    def test_failed_clone_removes_temp_dir(self, mock_repo, mock_githook, 
tmp_path):
+        mock_githook.return_value.repo_url = "https://github.com/org/repo";
+        mock_githook.return_value.env = {}
+        mock_repo.clone_from.side_effect = RuntimeError("boom")
+        created = str(tmp_path / "clone")
+        os.makedirs(created)
+        with patch("airflow.providers.common.ai.skills.tempfile.mkdtemp", 
return_value=created):
+            with pytest.raises(RuntimeError):
+                with resolve_skills(
+                    [GitSkills(repo_url="https://github.com/org/repo";, 
conn_id="git_default")]
+                ):
+                    pass
+        assert not os.path.exists(created)
+
+    @patch("airflow.providers.git.hooks.git.GitHook")
+    @patch("git.Repo")
+    def test_clone_error_scrubs_token_from_message(self, mock_repo, 
mock_githook):
+        mock_githook.return_value.repo_url = 
"https://user:[email protected]/org/repo";
+        mock_githook.return_value.env = {}
+        mock_repo.clone_from.side_effect = Exception(
+            "fatal: unable to access 
https://user:[email protected]/org/repo";
+        )
+        with pytest.raises(RuntimeError) as exc_info:
+            with 
resolve_skills([GitSkills(repo_url="https://github.com/org/repo";, 
conn_id="git_default")]):
+                pass
+        assert "ghp_secret" not in str(exc_info.value)
+
+
+class TestResolveGitCleanup:
+    @patch("airflow.providers.git.hooks.git.GitHook")
+    @patch("git.Repo")
+    def test_clone_dir_removed_on_exit(self, mock_repo, mock_githook):
+        mock_githook.return_value.repo_url = "https://github.com/org/repo";
+        mock_githook.return_value.env = {}
+        with resolve_skills([GitSkills(repo_url="https://github.com/org/repo";, 
conn_id="git_default")]):
+            dest = mock_repo.clone_from.call_args.args[1]
+            assert os.path.isdir(dest)
+        assert not os.path.exists(dest)
diff --git a/providers/common/ai/tests/unit/common/ai/toolsets/test_skills.py 
b/providers/common/ai/tests/unit/common/ai/toolsets/test_skills.py
new file mode 100644
index 00000000000..a3bf781f803
--- /dev/null
+++ b/providers/common/ai/tests/unit/common/ai/toolsets/test_skills.py
@@ -0,0 +1,140 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Tests for the pydantic-ai AgentSkillsToolset binding.
+
+Async methods are driven with ``asyncio.run`` to avoid depending on a 
particular
+pytest-asyncio mode.
+"""
+
+from __future__ import annotations
+
+import asyncio
+import sys
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from airflow.providers.common.ai.skills import GitSkills
+from airflow.providers.common.ai.toolsets.skills import AgentSkillsToolset
+
+pytest.importorskip("pydantic_ai_skills")
+
+
+class _FakeInner:
+    """Minimal async-context-manager stand-in for 
pydantic_ai_skills.SkillsToolset."""
+
+    def __init__(self, **kwargs):
+        self.kwargs = kwargs
+        self.entered = False
+        self.exited = False
+
+    async def __aenter__(self):
+        self.entered = True
+        return self
+
+    async def __aexit__(self, *args):
+        self.exited = True
+        return None
+
+
+def _write_skill(directory):
+    skill_dir = directory / "demo-skill"
+    skill_dir.mkdir(parents=True)
+    (skill_dir / "SKILL.md").write_text(
+        "---\nname: demo-skill\ndescription: A demo skill for tests.\n---\n\n# 
Demo\n"
+    )
+
+
+class TestConstruction:
+    def test_is_abstract_toolset_and_lazy(self):
+        from pydantic_ai.toolsets.abstract import AbstractToolset
+
+        toolset = AgentSkillsToolset(sources=["./skills", 
GitSkills(repo_url="https://x/y";, conn_id="c")])
+        assert isinstance(toolset, AbstractToolset)
+        # Nothing resolved or cloned at construction.
+        assert toolset._inner is None
+
+    def test_get_tools_before_enter_raises(self):
+        toolset = AgentSkillsToolset(sources=["./skills"])
+        with pytest.raises(RuntimeError, match="must be entered"):
+            asyncio.run(toolset.get_tools(MagicMock()))
+
+
+class TestLifecycle:
+    def test_enter_builds_inner_and_exit_tears_down(self, tmp_path):
+        from pydantic_ai_skills import SkillsToolset
+
+        _write_skill(tmp_path)
+        toolset = AgentSkillsToolset(sources=[str(tmp_path)])
+
+        async def run():
+            async with toolset:
+                assert isinstance(toolset._inner, SkillsToolset)
+                assert "demo-skill" in toolset._inner._skills
+            assert toolset._inner is None
+
+        asyncio.run(run())
+
+    def test_exclude_tools_passed_to_inner(self):
+        captured: dict = {}
+
+        def fake_skillstoolset(**kwargs):
+            captured.update(kwargs)
+            return _FakeInner(**kwargs)
+
+        toolset = AgentSkillsToolset(sources=["/x"], 
exclude_tools={"run_skill_script"})
+        with patch(
+            "airflow.providers.common.ai.toolsets.skills._materialize_skills",
+            return_value=(["/x"], lambda: None),
+        ):
+            with patch("pydantic_ai_skills.SkillsToolset", fake_skillstoolset):
+                asyncio.run(_enter_exit(toolset))
+
+        assert captured["exclude_tools"] == {"run_skill_script"}
+        assert captured["directories"] == ["/x"]
+
+
+class TestCleanup:
+    def test_cleanup_runs_if_inner_construction_fails(self):
+        cleanup = MagicMock()
+
+        def boom(**kwargs):
+            raise RuntimeError("inner build failed")
+
+        toolset = AgentSkillsToolset(sources=["/x"])
+        with patch(
+            "airflow.providers.common.ai.toolsets.skills._materialize_skills",
+            return_value=(["/x"], cleanup),
+        ):
+            with patch("pydantic_ai_skills.SkillsToolset", boom):
+                with pytest.raises(RuntimeError, match="inner build failed"):
+                    asyncio.run(toolset.__aenter__())
+
+        cleanup.assert_called_once()
+
+
+class TestMissingExtra:
+    def test_helpful_error_when_package_missing(self):
+        toolset = AgentSkillsToolset(sources=["./skills"])
+        with patch.dict(sys.modules, {"pydantic_ai_skills": None}):
+            with pytest.raises(ValueError, match=r"\[skills\]"):
+                asyncio.run(toolset.__aenter__())
+
+
+async def _enter_exit(toolset):
+    await toolset.__aenter__()
+    await toolset.__aexit__(None, None, None)
diff --git a/scripts/ci/prek/check_providers_subpackages_all_have_init.py 
b/scripts/ci/prek/check_providers_subpackages_all_have_init.py
index c1511f21319..a67f914e982 100755
--- a/scripts/ci/prek/check_providers_subpackages_all_have_init.py
+++ b/scripts/ci/prek/check_providers_subpackages_all_have_init.py
@@ -45,6 +45,9 @@ ACCEPTED_NON_INIT_DIRS = [
     ".pnpm-store",
     "node_modules",
     "non_python_src",
+    # Agent Skills bundles (agentskills.io): a "skills" dir holds SKILL.md 
files
+    # and their assets, not a Python package.
+    "skills",
 ]
 
 IGNORE_DIR_PATTERNS = ["airflow/providers/edge3/plugins", 
"airflow/providers/common/ai/plugins"]
diff --git a/uv.lock b/uv.lock
index 8ebb1fb9659..eaf84b6de2c 100644
--- a/uv.lock
+++ b/uv.lock
@@ -4288,6 +4288,9 @@ common-sql = [
 docx = [
     { name = "python-docx" },
 ]
+git = [
+    { name = "apache-airflow-providers-git" },
+]
 google = [
     { name = "pydantic-ai-slim", extra = ["google"] },
 ]
@@ -4311,6 +4314,10 @@ parquet = [
 pdf = [
     { name = "pypdf" },
 ]
+skills = [
+    { name = "apache-airflow-providers-git" },
+    { name = "pydantic-ai-skills" },
+]
 sql = [
     { name = "apache-airflow-providers-common-sql" },
     { name = "sqlglot" },
@@ -4322,12 +4329,14 @@ dev = [
     { name = "apache-airflow-devel-common" },
     { name = "apache-airflow-providers-common-compat" },
     { name = "apache-airflow-providers-common-sql", extra = ["datafusion"] },
+    { name = "apache-airflow-providers-git" },
     { name = "apache-airflow-providers-standard" },
     { name = "apache-airflow-task-sdk" },
     { name = "langchain" },
     { name = "llama-index-core" },
     { name = "llama-index-embeddings-openai" },
     { name = "llama-index-llms-openai" },
+    { name = "pydantic-ai-skills" },
     { name = "pydantic-ai-slim", extra = ["mcp"] },
     { name = "sqlglot" },
 ]
@@ -4341,6 +4350,8 @@ requires-dist = [
     { name = "apache-airflow-providers-common-compat", editable = 
"providers/common/compat" },
     { name = "apache-airflow-providers-common-sql", marker = "extra == 
'common-sql'", editable = "providers/common/sql" },
     { name = "apache-airflow-providers-common-sql", marker = "extra == 'sql'", 
editable = "providers/common/sql" },
+    { name = "apache-airflow-providers-git", marker = "extra == 'git'", 
editable = "providers/git" },
+    { name = "apache-airflow-providers-git", marker = "extra == 'skills'", 
editable = "providers/git" },
     { name = "apache-airflow-providers-standard", editable = 
"providers/standard" },
     { name = "fastavro", marker = "python_full_version >= '3.14' and extra == 
'avro'", specifier = ">=1.12.1" },
     { name = "fastavro", marker = "python_full_version < '3.14' and extra == 
'avro'", specifier = ">=1.10.0" },
@@ -4350,6 +4361,7 @@ requires-dist = [
     { name = "llama-index-llms-openai", marker = "extra == 'llamaindex'", 
specifier = ">=0.6.0" },
     { name = "pyarrow", marker = "python_full_version >= '3.14' and extra == 
'parquet'", specifier = ">=22.0.0" },
     { name = "pyarrow", marker = "python_full_version < '3.14' and extra == 
'parquet'", specifier = ">=18.0.0" },
+    { name = "pydantic-ai-skills", marker = "extra == 'skills'", specifier = 
">=0.11.0" },
     { name = "pydantic-ai-slim", specifier = ">=1.71.0" },
     { name = "pydantic-ai-slim", extras = ["anthropic"], marker = "extra == 
'anthropic'" },
     { name = "pydantic-ai-slim", extras = ["bedrock"], marker = "extra == 
'bedrock'" },
@@ -4360,7 +4372,7 @@ requires-dist = [
     { name = "python-docx", marker = "extra == 'docx'", specifier = ">=1.0.0" 
},
     { name = "sqlglot", marker = "extra == 'sql'", specifier = ">=30.0.0" },
 ]
-provides-extras = ["anthropic", "bedrock", "google", "openai", "mcp", "avro", 
"parquet", "sql", "common-sql", "langchain", "llamaindex", "pdf", "docx"]
+provides-extras = ["anthropic", "bedrock", "google", "openai", "mcp", 
"skills", "avro", "parquet", "sql", "common-sql", "langchain", "llamaindex", 
"pdf", "docx", "git"]
 
 [package.metadata.requires-dev]
 dev = [
@@ -4369,12 +4381,14 @@ dev = [
     { name = "apache-airflow-providers-common-compat", editable = 
"providers/common/compat" },
     { name = "apache-airflow-providers-common-sql", editable = 
"providers/common/sql" },
     { name = "apache-airflow-providers-common-sql", extras = ["datafusion"], 
editable = "providers/common/sql" },
+    { name = "apache-airflow-providers-git", editable = "providers/git" },
     { name = "apache-airflow-providers-standard", editable = 
"providers/standard" },
     { name = "apache-airflow-task-sdk", editable = "task-sdk" },
     { name = "langchain", specifier = ">=1.0.0" },
     { name = "llama-index-core", specifier = ">=0.13.0" },
     { name = "llama-index-embeddings-openai", specifier = ">=0.6.0" },
     { name = "llama-index-llms-openai", specifier = ">=0.6.0" },
+    { name = "pydantic-ai-skills", specifier = ">=0.11.0" },
     { name = "pydantic-ai-slim", extras = ["mcp"] },
     { name = "sqlglot", specifier = ">=30.0.0" },
 ]
@@ -18784,6 +18798,20 @@ email = [
     { name = "email-validator" },
 ]
 
+[[package]]
+name = "pydantic-ai-skills"
+version = "0.11.0"
+source = { registry = "https://pypi.org/simple"; }
+dependencies = [
+    { name = "anyio" },
+    { name = "pydantic-ai-slim" },
+    { name = "pyyaml" },
+]
+sdist = { url = 
"https://files.pythonhosted.org/packages/94/d1/f8fbc1c792ba8d73fc424ddab143ab0e1d95f9e982be5a949aaa3c84d64e/pydantic_ai_skills-0.11.0.tar.gz";,
 hash = 
"sha256:d4040f0b81da34e25b8f14dac5e1895e59d00390db8896448aac1ed1a4d0cf90", size 
= 9023711, upload-time = "2026-05-26T01:52:55.375Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/9a/80/d9b4bf0f5a3e8d256d62aa30b1c4589dc492efdb2a2d0da0334b4e71f25c/pydantic_ai_skills-0.11.0-py3-none-any.whl";,
 hash = 
"sha256:af8d78d451ce192dd2ef33abe86ad900bd51d9fd10c81a11abee82f62e8daf30", size 
= 61218, upload-time = "2026-05-26T01:52:53.773Z" },
+]
+
 [[package]]
 name = "pydantic-ai-slim"
 version = "1.102.0"

Reply via email to