jeevesagent.skills
==================

.. py:module:: jeevesagent.skills

.. autoapi-nested-parse::

   Skills — packaged, on-demand instructions for agent tasks.

   Anthropic Agent Skills (Oct 2025) plus the LangChain DeepAgents
   extensions, implemented over our existing primitives:

   * A **Skill** is a directory containing ``SKILL.md`` plus optional
     supporting files (additional markdown docs, scripts, templates).
   * The agent sees every registered skill's NAME + DESCRIPTION at
     startup (~50 tokens per skill in the system prompt).
   * When the user's request matches a skill's description, the model
     calls a ``load_skill(name)`` tool to read the full body — the
     recipe — into context.
   * Bundled supporting files are read via the standard ``read_tool``
     / ``bash_tool`` the agent already has; we don't need a new
     filesystem abstraction for skills.

   Multi-source layering with last-source-wins override::

       agent = Agent(
           "...",
           skills=[
               "~/.jeeves/skills/system/",           # base
               "~/.jeeves/skills/user/",             # user override
               ("./.jeeves-skills/", "Project"),      # project, labelled
           ],
       )

   Inline skills (no folder needed)::

       agent = Agent(
           "...",
           skills=[
               Skill.from_text('''---
               name: standup-format
               description: Format a daily standup update.
               ---
               # Standup
               Always 3 sections: Yesterday, Today, Blockers.
               '''),
           ],
       )

   Public surface:

   * :class:`Skill` — one loadable skill
   * :class:`SkillSource` — a directory of skills with optional label
   * :class:`SkillRegistry` — collection with override semantics
   * :class:`SkillMetadata` — startup-loaded descriptor
   * :class:`SkillError` — raised on bad SKILL.md or unknown skill name
   * :func:`make_load_skill_tool` — internal: builds the ``load_skill``
     tool the framework injects into agents that have skills configured



Submodules
----------

.. toctree::
   :maxdepth: 1

   /api/jeevesagent/skills/registry/index
   /api/jeevesagent/skills/skill/index
   /api/jeevesagent/skills/source/index
   /api/jeevesagent/skills/tools/index


Attributes
----------

.. autoapisummary::

   jeevesagent.skills.SkillSpec


Exceptions
----------

.. autoapisummary::

   jeevesagent.skills.SkillError


Classes
-------

.. autoapisummary::

   jeevesagent.skills.Skill
   jeevesagent.skills.SkillMetadata
   jeevesagent.skills.SkillRegistry
   jeevesagent.skills.SkillSource


Functions
---------

.. autoapisummary::

   jeevesagent.skills.make_load_skill_tool


Package Contents
----------------

.. py:exception:: SkillError

   Bases: :py:obj:`ValueError`


   Raised on invalid skill construction or frontmatter.

   Initialize self.  See help(type(self)) for accurate signature.


.. py:class:: Skill(path: str | pathlib.Path, *, source_label: str | None = None)

   A loadable agent skill.


   .. py:method:: from_text(text: str, *, source_label: str | None = None) -> Skill
      :classmethod:


      Build an inline skill from a SKILL.md-formatted string.

      No filesystem path; bundled scripts and ``tools.py`` aren't
      accessible. Useful for one-off skill definitions in code.



   .. py:method:: list_files() -> list[pathlib.Path]

      Enumerate every file bundled with this skill.



   .. py:method:: load_body() -> str

      Return the full SKILL.md body (without frontmatter).



   .. py:property:: description
      :type: str



   .. py:attribute:: metadata


   .. py:property:: name
      :type: str



   .. py:attribute:: path


   .. py:property:: pending_tools
      :type: list[jeevesagent.tools.registry.Tool]


      The Tool instances this skill will register on load.

      Both Mode B (Python @tool from ``tools.py``) and Mode C
      (subprocess wrappers from frontmatter ``tools:`` manifest)
      contribute to this list. Empty for pure markdown skills.


.. py:class:: SkillMetadata

   Lightweight skill descriptor — what loads at startup.

   The body is NOT in here; it's read on demand via
   :meth:`Skill.load_body`. Keep this small — it lives in the
   system prompt for the entire agent's lifetime.


   .. py:method:: to_catalog_line() -> str

      One-line catalog entry for the system prompt.



   .. py:attribute:: allowed_tools
      :type:  list[str] | None
      :value: None



   .. py:attribute:: compatibility
      :type:  str | None
      :value: None



   .. py:attribute:: declared_tool_count
      :type:  int
      :value: 0



   .. py:attribute:: description
      :type:  str


   .. py:attribute:: extra
      :type:  dict[str, Any]


   .. py:attribute:: has_python_tools
      :type:  bool
      :value: False



   .. py:attribute:: license
      :type:  str | None
      :value: None



   .. py:attribute:: name
      :type:  str


   .. py:attribute:: source_label
      :type:  str | None
      :value: None



.. py:class:: SkillRegistry(items: collections.abc.Iterable[SkillSpec] | None = None)

   A keyed collection of :class:`Skill` instances.


   .. py:method:: add(skill: jeevesagent.skills.skill.Skill) -> None

      Append (or override) a single skill after construction.



   .. py:method:: catalog_section() -> str

      The markdown bullet list that gets appended to the
      agent's system prompt.

      Empty registry → empty string (so the constructor can
      unconditionally call this without polluting the system
      prompt with a blank "Available skills" header).



   .. py:method:: get(name: str) -> jeevesagent.skills.skill.Skill | None


   .. py:method:: is_loaded(name: str) -> bool

      Whether the skill's pending tools have been registered.



   .. py:method:: load(name: str) -> str

      Return the full body of a skill (the load_skill tool's
      result). Raises :class:`SkillError` for unknown names so
      the model gets a clear error in the tool result.

      Does NOT register pending Tools. For the full load-and-
      register flow, see :meth:`load_with_tools`.



   .. py:method:: load_with_tools(name: str) -> tuple[str, list[jeevesagent.tools.registry.Tool]]

      Return ``(body, newly_pending_tools)`` — the body of the
      skill plus the Tool instances the framework should register
      with the agent's tool host on this load.

      Idempotent: subsequent calls for the same skill return the
      body and an empty tool list, since registration only needs
      to happen once.



   .. py:method:: metadata_map() -> collections.abc.Mapping[str, jeevesagent.skills.skill.SkillMetadata]

      All currently-registered skills' metadata, keyed by name.
      Cheap to compute — used to build the catalog section.



   .. py:method:: names() -> list[str]


   .. py:method:: remove(name: str) -> jeevesagent.skills.skill.Skill | None

      Drop a skill by name. Returns the removed instance or
      ``None`` if no such skill was registered.



.. py:class:: SkillSource

   A folder of skills + an optional label.


   .. py:method:: coerce(item: SkillSource | str | pathlib.Path | tuple[str | pathlib.Path, str]) -> SkillSource
      :classmethod:


      Normalize one user-supplied source spec.

      Accepts:
      * ``SkillSource(...)`` — used as-is
      * ``str`` / ``Path`` — bare path, no label
      * ``(path, label)`` — path with explicit label



   .. py:method:: discover() -> list[jeevesagent.skills.skill.Skill]

      Find every SKILL.md under this source directory.

      Recurses one level (most common layout: ``skills/<name>/SKILL.md``)
      but also handles deeper nesting. Each SKILL.md becomes one
      :class:`Skill` instance with this source's label attached.



   .. py:attribute:: label
      :type:  str | None
      :value: None



   .. py:attribute:: path
      :type:  pathlib.Path


.. py:function:: make_load_skill_tool(registry: jeevesagent.skills.registry.SkillRegistry, *, host: jeevesagent.core.protocols.ToolHost | None = None, tool_name: str = 'load_skill') -> jeevesagent.tools.registry.Tool

   Build the ``load_skill`` tool for a given registry.

   When ``host`` is provided, the tool will register a skill's
   pending Tools (from Mode B / Mode C) with the host on first
   load — making them callable on subsequent turns. Without a
   host, ``load_skill`` only returns the body (skill brings no
   tools, or the framework integration handles registration
   elsewhere).


.. py:data:: SkillSpec

   Anything an :class:`Agent`'s ``skills=`` argument accepts.

