跳转至

jianmu.skill

适用对象:Skill 作者 / Skill 运行时集成者 / 核心维护者 是否必读:按需 相关模块:jianmu.node, jianmu.tool, jianmu.execution

1. 模块职责

jianmu.skill 负责把技能定义从文件系统和元数据装载成可执行对象。

这里的重点是“装载与选择”,不是“实际执行节点”;执行通常由 SkillNode 等上层对象负责。

2. 适合查什么

  • Skill 数据结构:Skill、SkillRecord、SkillRequirements
  • 目录与选择:SkillsCatalog
  • 装载器:SkillLoader、SkillTreeLoader
  • BT 上下文:BTSkillContext

3. 注意事项

  • SkillLoader 依赖可选包 python-frontmatter
  • BT skill 场景通常还会依赖额外约束与执行环境
  • 如果只是使用 skill 节点,通常无需直接操作全部装载细节

4. builtin 路径说明

jianmu.skill.builtin 更适合承接“框架随包分发的内建 skill 资源”,而不是普通用户的主导入入口。

这意味着:

  • jianmu.skill 负责 skill 的公开抽象、目录、装载和 BT 集成
  • jianmu.skill.builtin 更像内建资源的归档层

如果未来要把某些内建 skill 提升为正式公开 API,也应优先通过 jianmu.skill 或专门的稳定子入口暴露,而不是让主文档默认要求用户直接依赖 builtin 路径。

5. 最小示例

from jianmu.skill import SkillLoader

loader = SkillLoader()
skill = loader.load("/path/to/SKILL.md")

一个最小 SKILL.md 可以是:

---
name: repo_summary
description: Summarize this repository
execution: prompt
---
Read the repository and summarize the main architecture.

6. 常见入口

  • 想从文件加载 skill:看 SkillLoader
  • 想做 skill 目录发现与覆盖:看 SkillsCatalog
  • 想加载 BT skill 的 tree.py:看 SkillTreeLoader
  • 想在节点里直接使用 skill:去看 jianmu.node.SkillNode

7. API 参考

skill

Skill system for jianmu.

Skill dataclass

Skill(
    name: str,
    description: str,
    prompt: str,
    source: Optional[str] = None,
    base_dir: Optional[str] = None,
    always: bool = False,
    role: Optional[str] = None,
    push_to_chat: bool = False,
    inputs: Optional[dict[str, Any]] = None,
    outputs: Optional[dict[str, Any]] = None,
    required: Optional[list[str]] = None,
    tools: list[str] = list(),
    tools_entry: list[str] = list(),
    execution: str = "prompt",
    orchestration: str = "react",
    tree: Optional[str] = None,
    files: list[str] = list(),
    constraints: Constraints = Constraints(),
)

Represents a fully parsed skill model in Jianmu.

属性:

名称 类型 描述
name str

Stable skill identifier used for routing and display.

description str

Human-readable summary shown to models and operators.

prompt str

Primary prompt body for prompt-based skills.

source Optional[str]

Optional source file path of the originating skill definition.

base_dir Optional[str]

Optional root directory used to resolve bundled skill files.

always bool

Whether the skill should always be injected into prompt context.

role Optional[str]

Optional swarm role name associated with the skill.

push_to_chat bool

Whether the skill output should be appended to chat history.

inputs Optional[dict[str, Any]]

Optional JSON-schema-like input definition.

outputs Optional[dict[str, Any]]

Optional JSON-schema-like output definition.

required Optional[list[str]]

Optional list of required output field names.

tools list[str]

Tool names enabled for this skill.

tools_entry list[str]

Optional explicit custom-tool entry files relative to the skill root. When omitted, docker tool discovery falls back to the legacy tools.py convention.

execution str

Execution mode, typically prompt or bt.

orchestration str

SkillNode execution strategy, react or direct.

tree Optional[str]

Optional behavior-tree file path for BT-backed skills.

files list[str]

Additional resource files exposed to the skill at runtime.

constraints Constraints

Guard or execution constraints applied to the skill.

SkillCandidate dataclass

SkillCandidate(
    name: str,
    path: str,
    source: str,
    description: str = "",
    available: bool = True,
    always: bool = False,
    execution: str = "prompt",
    orchestration: str = "react",
    inputs: dict[str, Any] = dict(),
    outputs: dict[str, Any] = dict(),
    required: list[str] = list(),
    tools: list[str] = list(),
    role: str | None = None,
    push_to_chat: bool = False,
    missing_reasons: str | None = None,
    metadata: dict[str, Any] = dict(),
)

Routing candidate generated from a skill, used during routing selection.

属性:

名称 类型 描述
name str

Candidate skill name presented to the router.

path str

Path to the backing skill definition.

source str

Origin label such as workspace or builtin.

description str

Human-readable summary for ranking and prompting.

available bool

Whether the candidate can run in the current environment.

always bool

Whether the candidate must always be included.

execution str

Execution mode, typically prompt or bt.

orchestration str

SkillNode execution strategy, react or direct.

inputs dict[str, Any]

Input schema exposed to the router or caller.

outputs dict[str, Any]

Output schema exposed to the router or caller.

required list[str]

Required output field names for structured responses.

tools list[str]

Tool names granted to the skill.

role str | None

Optional associated role for swarm-driven routing.

push_to_chat bool

Whether the produced output is mirrored to chat history.

missing_reasons str | None

Pre-rendered explanation for unavailability.

metadata dict[str, Any]

Extra routing metadata preserved from the source record.

SkillRecord dataclass

SkillRecord(
    name: str,
    path: str,
    source: str,
    description: str = "",
    available: bool = True,
    always: bool = False,
    requires: SkillRequirements = SkillRequirements(),
    missing_bins: list[str] = list(),
    missing_env: list[str] = list(),
    unsupported_os: list[str] = list(),
    raw_metadata: dict[str, Any] = dict(),
)

Internal registry entry for cataloged skill files.

属性:

名称 类型 描述
name str

Skill name declared by the loaded metadata.

path str

Absolute or workspace-relative path to the skill definition file.

source str

Origin label such as workspace or builtin.

description str

Short summary extracted from skill metadata.

available bool

Whether the skill can run in the current environment.

always bool

Whether the skill is always included during routing/prompting.

requires SkillRequirements

Structured availability requirements for the skill.

missing_bins list[str]

Missing CLI binaries that block execution.

missing_env list[str]

Missing environment variables that block execution.

unsupported_os list[str]

OS constraints that are not satisfied by the host.

raw_metadata dict[str, Any]

Unprocessed metadata dictionary captured from the source.

missing_reasons_text

missing_reasons_text() -> str

Render unmet availability requirements as one display string.

返回:

类型 描述
str

A comma-separated string describing missing CLI binaries, env vars, or OS support.

源代码位于: jianmu/skill/types.py
def missing_reasons_text(self) -> str:
    """Render unmet availability requirements as one display string.

    Returns:
        A comma-separated string describing missing CLI binaries, env vars, or OS support.
    """
    reasons: list[str] = []
    for item in self.missing_bins:
        reasons.append(f"CLI: {item}")
    for item in self.missing_env:
        reasons.append(f"ENV: {item}")
    if self.unsupported_os:
        reasons.append(f"OS: requires {', '.join(self.unsupported_os)}")
    return ", ".join(reasons)

SkillRequirements dataclass

SkillRequirements(
    bins: list[str] = list(),
    env: list[str] = list(),
    os: list[str] = list(),
)

Defines execution requirements for a skill, such as platform and binaries.

属性:

名称 类型 描述
bins list[str]

Required CLI binaries that must be available on the host.

env list[str]

Required environment variable names.

os list[str]

Supported operating-system names for this skill.

SkillsCatalog

SkillsCatalog(
    *,
    workspace_skills_dir: str | Path | None = None,
    builtin_skills_dir: str | Path | None = None,
)

Framework-level catalog for builtin and workspace skills.

The catalog is responsible for discovery, precedence, availability checks, and prompt-oriented rendering. Workspace skills override builtin skills by name, so application-local customizations can replace packaged defaults without forking the framework.

属性:

名称 类型 描述
workspace_skills_dir

Optional project-local skill root.

builtin_skills_dir

Packaged builtin skill root.

Configure workspace and builtin skill search roots.

参数:

名称 类型 描述 默认
workspace_skills_dir str | Path | None

Optional project-local skill root. Skills here override builtin skills with the same name.

None
builtin_skills_dir str | Path | None

Optional override for the packaged builtin skill directory.

None
源代码位于: jianmu/skill/catalog.py
def __init__(
    self,
    *,
    workspace_skills_dir: str | Path | None = None,
    builtin_skills_dir: str | Path | None = None,
):
    """Configure workspace and builtin skill search roots.

    Args:
        workspace_skills_dir: Optional project-local skill root. Skills here
            override builtin skills with the same name.
        builtin_skills_dir: Optional override for the packaged builtin skill
            directory.
    """
    self.workspace_skills_dir = Path(workspace_skills_dir).expanduser().resolve() if workspace_skills_dir else None
    self.builtin_skills_dir = (
        Path(builtin_skills_dir).expanduser().resolve()
        if builtin_skills_dir
        else _builtin_skills_dir().resolve()
    )

resolve_runtime classmethod

resolve_runtime(
    runtime_prompt: "PromptRuntimeContext | None" = None,
) -> "SkillsCatalog | None"

Resolve the effective skills catalog for the current runtime.

参数:

名称 类型 描述 默认
runtime_prompt 'PromptRuntimeContext | None'

Optional runtime prompt overrides describing skills context.

None

返回:

类型 描述
'SkillsCatalog | None'

Effective skills catalog for the current runtime, or None.

源代码位于: jianmu/skill/catalog.py
@classmethod
def resolve_runtime(
    cls,
    runtime_prompt: "PromptRuntimeContext | None" = None,
) -> "SkillsCatalog | None":
    """Resolve the effective skills catalog for the current runtime.

    Args:
        runtime_prompt: Optional runtime prompt overrides describing skills context.

    Returns:
        Effective skills catalog for the current runtime, or ``None``.
    """
    if runtime_prompt is not None and runtime_prompt.skills_catalog is not None:
        return runtime_prompt.skills_catalog

    runtime_skills_dir = runtime_prompt.skills_dir if runtime_prompt is not None else None
    if runtime_skills_dir:
        try:
            return cls(workspace_skills_dir=Path(runtime_skills_dir))
        except Exception:
            return None

    static_skills_dir = get_config().paths.skills_dir
    if static_skills_dir:
        try:
            return cls(workspace_skills_dir=Path(static_skills_dir))
        except Exception:
            return None

    workspace_skills_dir = Path.cwd() / "skills"
    if workspace_skills_dir.exists() and workspace_skills_dir.is_dir():
        try:
            return cls(workspace_skills_dir=workspace_skills_dir)
        except Exception:
            return None

    return None

list_skills

list_skills(
    filter_unavailable: bool = True,
) -> list[SkillRecord]

List known skills, optionally filtering unavailable entries.

参数:

名称 类型 描述 默认
filter_unavailable bool

Whether to exclude unavailable skills.

True

返回:

类型 描述
list[SkillRecord]

A list of SkillRecord objects.

源代码位于: jianmu/skill/catalog.py
def list_skills(self, filter_unavailable: bool = True) -> list[SkillRecord]:
    """List known skills, optionally filtering unavailable entries.

    Args:
        filter_unavailable: Whether to exclude unavailable skills.

    Returns:
        A list of SkillRecord objects.
    """
    records = self._collect_records()
    if filter_unavailable:
        records = [record for record in records if record.available]
    return records

get_skill

get_skill(
    name: str, *, filter_unavailable: bool = False
) -> SkillRecord | None

Return one skill by name.

参数:

名称 类型 描述 默认
name str

The name of the skill to search for.

必需
filter_unavailable bool

Whether to exclude unavailable skills.

False

返回:

类型 描述
SkillRecord | None

The matching SkillRecord, or None if not found.

源代码位于: jianmu/skill/catalog.py
def get_skill(self, name: str, *, filter_unavailable: bool = False) -> SkillRecord | None:
    """Return one skill by name.

    Args:
        name: The name of the skill to search for.
        filter_unavailable: Whether to exclude unavailable skills.

    Returns:
        The matching SkillRecord, or None if not found.
    """
    target = (name or "").strip().lower()
    if not target:
        return None
    for record in self.list_skills(filter_unavailable=filter_unavailable):
        if record.name.strip().lower() == target:
            return record
    return None

resolve_records

resolve_records(
    names: Iterable[str] | None = None,
    *,
    filter_unavailable: bool = True,
) -> list[SkillRecord]

Resolve ordered skill records for one runtime selection request.

参数:

名称 类型 描述 默认
names Iterable[str] | None

Collection of name values.

None
filter_unavailable bool

Whether unavailable skills should be excluded.

True

返回:

类型 描述
list[SkillRecord]

Ordered skill records matching the requested names.

源代码位于: jianmu/skill/catalog.py
def resolve_records(
    self,
    names: Iterable[str] | None = None,
    *,
    filter_unavailable: bool = True,
) -> list[SkillRecord]:
    """Resolve ordered skill records for one runtime selection request.

    Args:
        names: Collection of name values.
        filter_unavailable: Whether unavailable skills should be excluded.

    Returns:
        Ordered skill records matching the requested names.
    """
    if names is None:
        return self.list_skills(filter_unavailable=filter_unavailable)

    records_by_name = {
        record.name.strip().lower(): record
        for record in self.list_skills(filter_unavailable=False)
    }
    resolved: list[SkillRecord] = []
    seen: set[str] = set()
    for raw_name in names:
        name = str(raw_name or "").strip()
        if not name:
            continue
        key = name.lower()
        if key in seen:
            continue
        seen.add(key)
        record = records_by_name.get(key)
        if record is None:
            continue
        if filter_unavailable and not record.available:
            continue
        resolved.append(record)
    return resolved

select_records_for_context

select_records_for_context(
    *, include_unavailable_skills: bool = False
) -> list[SkillRecord]

Resolve the skill records exposed to prompt context for one run.

参数:

名称 类型 描述 默认
include_unavailable_skills bool

Whether unavailable skills may be exposed to prompts.

False

返回:

类型 描述
list[SkillRecord]

Skill records exposed to prompt context for the current run.

源代码位于: jianmu/skill/catalog.py
def select_records_for_context(
    self,
    *,
    include_unavailable_skills: bool = False,
) -> list[SkillRecord]:
    """Resolve the skill records exposed to prompt context for one run.

    Args:
        include_unavailable_skills: Whether unavailable skills may be exposed to prompts.

    Returns:
        Skill records exposed to prompt context for the current run.
    """
    return self.list_skills(filter_unavailable=not include_unavailable_skills)

load_skill_content

load_skill_content(name: str) -> str | None

Load raw SKILL.md content for one named skill.

参数:

名称 类型 描述 默认
name str

The name of the skill.

必需

返回:

类型 描述
str | None

The raw Markdown text content of the skill, or None.

源代码位于: jianmu/skill/catalog.py
def load_skill_content(self, name: str) -> str | None:
    """Load raw ``SKILL.md`` content for one named skill.

    Args:
        name: The name of the skill.

    Returns:
        The raw Markdown text content of the skill, or None.
    """
    record = self.get_skill(name, filter_unavailable=False)
    if record is None:
        return None
    path = Path(record.path)
    if not path.exists():
        return None
    try:
        return path.read_text(encoding="utf-8")
    except Exception:
        return None

load_skill_from_path

load_skill_from_path(path: str | Path) -> 'Skill'

Load a skill object directly from a filesystem path.

参数:

名称 类型 描述 默认
path str | Path

filesystem path to the skill.

必需

引发:

类型 描述
RuntimeError

If SkillLoader dependency is not installed.

返回:

类型 描述
'Skill'

The resulting 'Skill' value.

源代码位于: jianmu/skill/catalog.py
def load_skill_from_path(self, path: str | Path) -> "Skill":
    """Load a skill object directly from a filesystem path.

    Args:
        path: filesystem path to the skill.

    Raises:
        RuntimeError: If SkillLoader dependency is not installed.

    Returns:
        The resulting `'Skill'` value.
    """
    if SkillLoader is None:
        raise RuntimeError("SkillLoader unavailable. Install jianmu[skill].")
    return SkillLoader().load(str(Path(path).expanduser().resolve()))

load_skills_for_context

load_skills_for_context(skill_names: Iterable[str]) -> str

Concatenate selected skills into one prompt-ready context block.

参数:

名称 类型 描述 默认
skill_names Iterable[str]

An iterable of skill names to load.

必需

返回:

类型 描述
str

A prompt-ready concatenated context string.

源代码位于: jianmu/skill/catalog.py
def load_skills_for_context(self, skill_names: Iterable[str]) -> str:
    """Concatenate selected skills into one prompt-ready context block.

    Args:
        skill_names: An iterable of skill names to load.

    Returns:
        A prompt-ready concatenated context string.
    """
    parts: list[str] = []
    for name in skill_names:
        content = self.load_skill_content(str(name))
        if not content:
            continue
        body = self._strip_frontmatter(content)
        if not body:
            continue
        parts.append(f"### Skill: {name}\n\n{body}")
    return "\n\n---\n\n".join(parts)

get_always_skills

get_always_skills() -> list[SkillRecord]

Return available skills marked as always-on.

返回:

类型 描述
list[SkillRecord]

A list of available SkillRecord objects marked as always-on.

源代码位于: jianmu/skill/catalog.py
def get_always_skills(self) -> list[SkillRecord]:
    """Return available skills marked as always-on.

    Returns:
        A list of available SkillRecord objects marked as always-on.
    """
    return [record for record in self.list_skills(filter_unavailable=True) if record.always]

snapshot

snapshot(
    *, include_unavailable: bool = True
) -> dict[str, Any]

Build a stable snapshot digest for skill catalog caching.

参数:

名称 类型 描述 默认
include_unavailable bool

Whether to include unavailable skills in the snapshot.

True

返回:

类型 描述
dict[str, Any]

A dictionary containing the version hash and rows of skill metadata.

源代码位于: jianmu/skill/catalog.py
def snapshot(self, *, include_unavailable: bool = True) -> dict[str, Any]:
    """Build a stable snapshot digest for skill catalog caching.

    Args:
        include_unavailable: Whether to include unavailable skills in the snapshot.

    Returns:
        A dictionary containing the version hash and rows of skill metadata.
    """
    records = self.list_skills(filter_unavailable=not include_unavailable)
    rows: list[dict[str, Any]] = []
    for record in records:
        path = Path(record.path)
        mtime = 0.0
        if path.exists():
            try:
                mtime = float(path.stat().st_mtime)
            except Exception:
                mtime = 0.0
        rows.append(
            {
                "name": record.name,
                "path": record.path,
                "source": record.source,
                "available": record.available,
                "always": record.always,
                "mtime": mtime,
            }
        )
    rows = sorted(rows, key=lambda item: (item["name"].lower(), item["path"]))
    digest = hashlib.sha1(
        json.dumps(rows, sort_keys=True, ensure_ascii=True).encode("utf-8")
    ).hexdigest()
    return {"version": digest, "skills": rows}

build_skills_summary

build_skills_summary(
    records: list[SkillRecord] | None = None,
    *,
    include_unavailable_reasons: bool = True,
) -> str

Render skills as an XML-like summary block for prompts.

参数:

名称 类型 描述 默认
records list[SkillRecord] | None

Optional list of SkillRecord objects. Defaults to all catalog skills.

None
include_unavailable_reasons bool

Whether to output XML tag for missing requirements.

True

返回:

类型 描述
str

The rendered XML-like skills summary block.

源代码位于: jianmu/skill/catalog.py
def build_skills_summary(
    self,
    records: list[SkillRecord] | None = None,
    *,
    include_unavailable_reasons: bool = True,
) -> str:
    """Render skills as an XML-like summary block for prompts.

    Args:
        records: Optional list of SkillRecord objects. Defaults to all catalog skills.
        include_unavailable_reasons: Whether to output XML tag for missing requirements.

    Returns:
        The rendered XML-like skills summary block.
    """
    rows = records if records is not None else self.list_skills(filter_unavailable=False)
    if not rows:
        return ""
    lines = ["<skills>"]
    for record in rows:
        lines.append(f'  <skill available="{str(record.available).lower()}">')
        lines.append(f"    <name>{_escape_xml(record.name)}</name>")
        lines.append(f"    <description>{_escape_xml(record.description or record.name)}</description>")
        lines.append(f"    <location>{_escape_xml(record.path)}</location>")
        lines.append(f"    <source>{_escape_xml(record.source)}</source>")
        if record.always:
            lines.append("    <always>true</always>")
        if include_unavailable_reasons and not record.available:
            reasons = record.missing_reasons_text()
            if reasons:
                lines.append(f"    <requires>{_escape_xml(reasons)}</requires>")
        lines.append("  </skill>")
    lines.append("</skills>")
    return "\n".join(lines)

build_candidates

build_candidates(
    *,
    skill_files: Iterable[str | Path] | None = None,
    skills_dir: str | Path | None = None,
    enabled_skills: Iterable[str] | None = None,
    include_unavailable: bool = False,
) -> list[SkillCandidate]

Build routing candidates from explicit files and/or skill directories.

参数:

名称 类型 描述 默认
skill_files Iterable[str | Path] | None

Optional files to load as routing candidates.

None
skills_dir str | Path | None

Optional directory containing candidate subdirectories.

None
enabled_skills Iterable[str] | None

Optional filter of enabled skill names.

None
include_unavailable bool

Whether to load candidates that are currently not available.

False

返回:

类型 描述
list[SkillCandidate]

A list of constructed SkillCandidate objects.

源代码位于: jianmu/skill/catalog.py
def build_candidates(
    self,
    *,
    skill_files: Iterable[str | Path] | None = None,
    skills_dir: str | Path | None = None,
    enabled_skills: Iterable[str] | None = None,
    include_unavailable: bool = False,
) -> list[SkillCandidate]:
    """Build routing candidates from explicit files and/or skill directories.

    Args:
        skill_files: Optional files to load as routing candidates.
        skills_dir: Optional directory containing candidate subdirectories.
        enabled_skills: Optional filter of enabled skill names.
        include_unavailable: Whether to load candidates that are currently not available.

    Returns:
        A list of constructed SkillCandidate objects.
    """
    paths: list[tuple[Path, str]] = []
    seen: set[str] = set()

    def _remember(path: Path, source: str) -> None:
        """Record a unique existing skill path with its source label."""
        resolved = str(path.expanduser().resolve())
        if resolved in seen or not path.exists() or path.name != "SKILL.md":
            return
        seen.add(resolved)
        paths.append((Path(resolved), source))

    for raw in list(skill_files or []):
        path = Path(raw).expanduser().resolve()
        _remember(path, self._infer_source(path, preferred="explicit"))

    base_dir = Path(skills_dir).expanduser().resolve() if skills_dir else None
    enabled = [str(item).strip() for item in (enabled_skills or []) if str(item).strip()]
    if base_dir is None and enabled:
        base_dir = self.builtin_skills_dir
    if base_dir is not None and base_dir.exists():
        if enabled:
            for name in enabled:
                _remember(base_dir / name / "SKILL.md", self._infer_source(base_dir / name / "SKILL.md"))
        else:
            for path in self._iter_skill_files(base_dir):
                _remember(path, self._infer_source(path))

    candidates: list[SkillCandidate] = []
    for path, source in paths:
        candidate = self._build_candidate(path, source=source)
        if candidate is None:
            continue
        if not include_unavailable and not candidate.available:
            continue
        candidates.append(candidate)
    return candidates

build_routing_summary

build_routing_summary(
    candidates: list[SkillCandidate],
    *,
    include_paths: bool = False,
    include_requires: bool = False,
) -> str

Render routing candidates into a compact summary string.

参数:

名称 类型 描述 默认
candidates list[SkillCandidate]

List of SkillCandidate objects to summarize.

必需
include_paths bool

Whether to output file paths.

False
include_requires bool

Whether to output missing requirements block.

False

返回:

类型 描述
str

A compact newline-separated summary of routing candidates.

源代码位于: jianmu/skill/catalog.py
def build_routing_summary(
    self,
    candidates: list[SkillCandidate],
    *,
    include_paths: bool = False,
    include_requires: bool = False,
) -> str:
    """Render routing candidates into a compact summary string.

    Args:
        candidates: List of SkillCandidate objects to summarize.
        include_paths: Whether to output file paths.
        include_requires: Whether to output missing requirements block.

    Returns:
        A compact newline-separated summary of routing candidates.
    """
    if not candidates:
        return ""
    lines: list[str] = []
    for candidate in candidates:
        input_keys = ", ".join(sorted(candidate.inputs.keys())) or "(none)"
        output_keys = ", ".join(sorted(candidate.outputs.keys())) or "(none)"
        parts = [
            f"- {candidate.name}",
            f"execution={candidate.execution}",
            f"orchestration={candidate.orchestration}",
            f"description={candidate.description or 'No description'}",
            f"inputs={input_keys}",
            f"outputs={output_keys}",
        ]
        if include_paths:
            parts.append(f"path={candidate.path}")
        if include_requires and candidate.missing_reasons:
            parts.append(f"requires={candidate.missing_reasons}")
        lines.append(" | ".join(parts))
    return "\n".join(lines)

SkillSet

SkillSet(
    skills: Sequence[Skill],
    *,
    catalog: SkillsCatalog | None = None,
)

Selected skills for one run.

属性:

名称 类型 描述
skills

Loaded runtime skills selected for the current run.

catalog

Optional catalog used to resolve the selected skills.

源代码位于: jianmu/skill/set.py
def __init__(
    self,
    skills: Sequence[Skill],
    *,
    catalog: SkillsCatalog | None = None,
) -> None:
    self.skills = list(skills)
    self.catalog = catalog

prompt_skills property

prompt_skills: list[Skill]

Return prompt-driven skills in this set.

返回:

类型 描述
list[Skill]

Prompt-driven skills selected for the current run.

bt_skills property

bt_skills: list[Skill]

Return BT skills in this set.

返回:

类型 描述
list[Skill]

Behavior-tree-backed skills selected for the current run.

resolve classmethod

resolve(
    *,
    skill_files: Sequence[str | Path] | None = None,
    enabled_skills: Sequence[str] | None = None,
    explicit_enabled_skills: bool = False,
    skills_dir: str | Path | None = None,
    runtime_prompt: "PromptRuntimeContext | None" = None,
) -> "SkillSet"

Resolve the selected skills for one runtime request.

参数:

名称 类型 描述 默认
skill_files Sequence[str | Path] | None

Collection of skill file values.

None
enabled_skills Sequence[str] | None

Collection of enabled skill values.

None
explicit_enabled_skills bool

Whether an explicitly provided empty enabled_skills selection should remain empty instead of falling back to all catalog skills.

False
skills_dir str | Path | None

Optional workspace skill directory override.

None
runtime_prompt 'PromptRuntimeContext | None'

Optional runtime prompt overrides describing skills context.

None

返回:

类型 描述
'SkillSet'

Resolved runtime skill set.

源代码位于: jianmu/skill/set.py
@classmethod
def resolve(
    cls,
    *,
    skill_files: Sequence[str | Path] | None = None,
    enabled_skills: Sequence[str] | None = None,
    explicit_enabled_skills: bool = False,
    skills_dir: str | Path | None = None,
    runtime_prompt: "PromptRuntimeContext | None" = None,
) -> "SkillSet":
    """Resolve the selected skills for one runtime request.

    Args:
        skill_files: Collection of skill file values.
        enabled_skills: Collection of enabled skill values.
        explicit_enabled_skills: Whether an explicitly provided empty
            ``enabled_skills`` selection should remain empty instead of
            falling back to all catalog skills.
        skills_dir: Optional workspace skill directory override.
        runtime_prompt: Optional runtime prompt overrides describing skills context.

    Returns:
        Resolved runtime skill set.
    """
    catalog = cls._resolve_catalog(skills_dir=skills_dir, runtime_prompt=runtime_prompt)
    if catalog is None and enabled_skills:
        try:
            catalog = SkillsCatalog()
        except Exception:
            catalog = None
    selected_paths = cls._resolve_paths(
        catalog=catalog,
        skill_files=skill_files,
        enabled_skills=enabled_skills,
        explicit_enabled_skills=explicit_enabled_skills,
    )
    loader = SkillLoader()
    skills = [loader.load(str(path)) for path in selected_paths]
    return cls(skills, catalog=catalog)

from_records classmethod

from_records(
    records: Sequence[SkillRecord],
    *,
    catalog: SkillsCatalog | None = None,
) -> "SkillSet"

Build a skill set from an existing catalog record selection.

参数:

名称 类型 描述 默认
records Sequence[SkillRecord]

Records to process.

必需
catalog SkillsCatalog | None

Optional catalog associated with the selected records.

None

返回:

类型 描述
'SkillSet'

Skill set loaded from the provided catalog records.

源代码位于: jianmu/skill/set.py
@classmethod
def from_records(
    cls,
    records: Sequence[SkillRecord],
    *,
    catalog: SkillsCatalog | None = None,
) -> "SkillSet":
    """Build a skill set from an existing catalog record selection.

    Args:
        records: Records to process.
        catalog: Optional catalog associated with the selected records.

    Returns:
        Skill set loaded from the provided catalog records.
    """
    loader = SkillLoader()
    skills = [loader.load(str(Path(record.path).expanduser().resolve())) for record in records]
    return cls(skills, catalog=catalog)

has_skill

has_skill(name: str) -> bool

Return whether one selected skill matches the given name.

参数:

名称 类型 描述 默认
name str

Skill name to look up.

必需

返回:

类型 描述
bool

True if a selected skill matches the given name.

源代码位于: jianmu/skill/set.py
def has_skill(self, name: str) -> bool:
    """Return whether one selected skill matches the given name.

    Args:
        name: Skill name to look up.

    Returns:
        ``True`` if a selected skill matches the given name.
    """
    target = (name or "").strip().lower()
    if not target:
        return False
    return any(str(skill.name or "").strip().lower() == target for skill in self.skills)

builtin_tool_names

builtin_tool_names() -> list[str]

Return builtin tool names declared by the selected skills.

返回:

类型 描述
list[str]

Builtin tool names declared across all selected skills.

源代码位于: jianmu/skill/set.py
def builtin_tool_names(self) -> list[str]:
    """Return builtin tool names declared by the selected skills.

    Returns:
        Builtin tool names declared across all selected skills.
    """
    names: list[str] = []
    for skill in self.skills:
        names.extend(skill.tools or [])
    return names

SkillLoader

Parse SKILL.md files into runtime Skill objects.

SkillLoader is the lowest-level entrypoint for file-based skills. It reads frontmatter metadata, prompt body, execution mode, bundled files, and embedded constraints, then produces a normalized Skill instance.

Minimal skill file::

---
name: repo_summary
description: Summarize this repository
execution: prompt
---
Read the repository and produce a short summary.

load

load(path: str) -> Skill

Parse a SKILL.md file and return a Skill object.

参数:

名称 类型 描述 默认
path str

Local filesystem path to the SKILL.md file.

必需

返回:

类型 描述
Skill

The parsed Skill configuration object.

引发:

类型 描述
RuntimeError

If the frontmatter dependency is not installed.

ValueError

If an unsupported execution mode is specified.

FileNotFoundError

If a behavior tree file is specified but not found.

Notes

execution: prompt uses the Markdown body as prompt content. execution: bt additionally resolves a sibling tree.py (or custom tree field) for behavior-tree-backed skills.

源代码位于: jianmu/skill/loader.py
def load(self, path: str) -> Skill:
    """Parse a ``SKILL.md`` file and return a ``Skill`` object.

    Args:
        path: Local filesystem path to the SKILL.md file.

    Returns:
        The parsed Skill configuration object.

    Raises:
        RuntimeError: If the frontmatter dependency is not installed.
        ValueError: If an unsupported execution mode is specified.
        FileNotFoundError: If a behavior tree file is specified but not found.

    Notes:
        ``execution: prompt`` uses the Markdown body as prompt content.
        ``execution: bt`` additionally resolves a sibling ``tree.py`` (or
        custom ``tree`` field) for behavior-tree-backed skills.
    """
    if frontmatter is None:
        raise RuntimeError("python-frontmatter is required for SkillLoader. Install jianmu[skill].")
    skill_path = Path(path).resolve()
    post = frontmatter.load(str(skill_path))
    meta = post.metadata

    constraints_meta = meta.get("constraints", {}) or {}
    constraints = Constraints.from_dict(constraints_meta)

    tools_meta = meta.get("tools", {})
    if isinstance(tools_meta, dict):
        tools = tools_meta.get("builtin", [])
    elif isinstance(tools_meta, (list, tuple)):
        tools = list(tools_meta)
    elif tools_meta is None:
        tools = []
    else:
        tools = [tools_meta]

    tools_entry = self._parse_string_list(meta.get("tools_entry"))

    execution = str(meta.get("execution", "prompt") or "prompt").strip().lower()
    if execution not in {"prompt", "bt"}:
        raise ValueError(f"Unsupported skill execution mode: {execution}")
    orchestration = str(meta.get("orchestration", "react") or "react").strip().lower()
    if orchestration not in {"react", "direct"}:
        raise ValueError(f"Unsupported skill orchestration mode: {orchestration}")
    if execution != "bt" and orchestration == "direct":
        raise ValueError("orchestration='direct' is only supported for execution='bt'")

    tree_value = meta.get("tree")
    tree = str(tree_value).strip() if tree_value is not None else None
    if execution == "bt":
        tree = tree or "tree.py"
        tree_path = (skill_path.parent / tree).resolve()
        if not tree_path.exists() or not tree_path.is_file():
            raise FileNotFoundError(
                f"BT skill tree file not found: {tree_path} (skill={skill_path})"
            )

    resource_paths = self._discover_resource_paths(skill_path.parent)

    return Skill(
        name=meta["name"],
        description=meta.get("description", ""),
        prompt=post.content,
        source=str(skill_path),
        base_dir=str(skill_path.parent),
        always=bool(meta.get("always", False)),
        role=meta.get("role"),
        push_to_chat=bool(meta.get("push_to_chat", False)),
        inputs=meta.get("inputs"),
        outputs=meta.get("outputs"),
        required=meta.get("required"),
        tools=tools,
        tools_entry=tools_entry,
        execution=execution,
        orchestration=orchestration,
        tree=tree,
        files=resource_paths,
        constraints=constraints,
    )

load_as_node

load_as_node(
    name: str,
    path: str,
    model_client: Optional[Any] = None,
    trace: Optional[Any] = None,
    model: Optional[str] = None,
) -> Behaviour

Load a SKILL.md as a SkillNode-style behaviour.

参数:

名称 类型 描述 默认
name str

The name of the resulting skill node.

必需
path str

Local path to the SKILL.md file.

必需
model_client Optional[Any]

Optional model client instance.

None
trace Optional[Any]

Optional trace configuration.

None
model Optional[str]

Optional model name.

None

返回:

类型 描述
Behaviour

The loaded SkillNode instance, wrapped in a Timeout node if the

Behaviour

skill declares a timeout constraint.

源代码位于: jianmu/skill/loader.py
def load_as_node(
    self,
    name: str,
    path: str,
    model_client: Optional[Any] = None,
    trace: Optional[Any] = None,
    model: Optional[str] = None,
) -> Behaviour:
    """Load a ``SKILL.md`` as a ``SkillNode``-style behaviour.

    Args:
        name: The name of the resulting skill node.
        path: Local path to the SKILL.md file.
        model_client: Optional model client instance.
        trace: Optional trace configuration.
        model: Optional model name.

    Returns:
        The loaded SkillNode instance, wrapped in a ``Timeout`` node if the
        skill declares a timeout constraint.
    """
    from jianmu.node.builtin.skill import SkillNode
    from jianmu.node.builtin.utility import Timeout

    skill = self.load(path)
    config = None
    if model is not None:
        config = SkillNodeConfig(react_config=ReActConfig(model=model))
    node = SkillNode(
        name=name,
        skill_files=[path],
        model_client=model_client,
        config=config,
    )

    if skill.constraints.timeout:
        return Timeout(name=f"{name}/Timeout", duration=skill.constraints.timeout, child=node)
    return node

SkillTreeLoader

Load and materialize tree.py -> build_tree() for execution=bt skills.

resolve_tree_path staticmethod

resolve_tree_path(skill: Skill) -> Path

Resolve the tree.py path for a BT skill.

参数:

名称 类型 描述 默认
skill Skill

The skill object to resolve the path for.

必需

返回:

类型 描述
Path

The resolved absolute Path to the tree.py file.

引发:

类型 描述
ValueError

If the skill has no base directory configured.

FileNotFoundError

If the tree.py file does not exist on disk.

源代码位于: jianmu/skill/tree_loader.py
@staticmethod
def resolve_tree_path(skill: Skill) -> Path:
    """Resolve the ``tree.py`` path for a BT skill.

    Args:
        skill: The skill object to resolve the path for.

    Returns:
        The resolved absolute Path to the tree.py file.

    Raises:
        ValueError: If the skill has no base directory configured.
        FileNotFoundError: If the tree.py file does not exist on disk.
    """
    if not skill.base_dir:
        raise ValueError(f"Skill '{skill.name}' has no base_dir for tree resolution.")
    tree_name = (skill.tree or "tree.py").strip()
    if not tree_name:
        tree_name = "tree.py"
    path = (Path(skill.base_dir) / tree_name).resolve()
    if not path.exists() or not path.is_file():
        raise FileNotFoundError(f"BT skill tree file not found: {path}")
    return path

load_build_fn

load_build_fn(skill: Skill) -> Callable[..., Any]

Import and return the build_tree function for a BT skill.

参数:

名称 类型 描述 默认
skill Skill

The skill object to load the build function for.

必需

返回:

类型 描述
Callable[..., Any]

The imported callable build_tree function.

引发:

类型 描述
ImportError

If the tree.py module fails to import.

ValueError

If the module is missing a callable build_tree function.

源代码位于: jianmu/skill/tree_loader.py
def load_build_fn(self, skill: Skill) -> Callable[..., Any]:
    """Import and return the ``build_tree`` function for a BT skill.

    Args:
        skill: The skill object to load the build function for.

    Returns:
        The imported callable build_tree function.

    Raises:
        ImportError: If the tree.py module fails to import.
        ValueError: If the module is missing a callable build_tree function.
    """
    tree_path = self.resolve_tree_path(skill)
    module_name = f"jianmu_skill_tree_{hashlib.sha1(str(tree_path).encode('utf-8')).hexdigest()[:12]}"
    spec = importlib.util.spec_from_file_location(module_name, str(tree_path))
    if spec is None or spec.loader is None:
        raise ImportError(f"Failed to create import spec for skill tree: {tree_path}")
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    build_tree = getattr(module, "build_tree", None)
    if not callable(build_tree):
        raise ValueError(f"BT skill tree module missing callable build_tree(): {tree_path}")
    return build_tree

build_tree

build_tree(skill: Skill, ctx: BTSkillContext) -> Behaviour

Build and validate the behaviour tree for one BT skill.

参数:

名称 类型 描述 默认
skill Skill

The skill object.

必需
ctx BTSkillContext

The runtime BTSkillContext context payload.

必需

返回:

类型 描述
Behaviour

The instantiated behavior tree root Behaviour node.

引发:

类型 描述
TypeError

If the build function does not return a py_trees Behaviour instance.

源代码位于: jianmu/skill/tree_loader.py
def build_tree(self, skill: Skill, ctx: BTSkillContext) -> Behaviour:
    """Build and validate the behaviour tree for one BT skill.

    Args:
        skill: The skill object.
        ctx: The runtime BTSkillContext context payload.

    Returns:
        The instantiated behavior tree root Behaviour node.

    Raises:
        TypeError: If the build function does not return a py_trees Behaviour instance.
    """
    build_fn = self.load_build_fn(skill)
    params = inspect.signature(build_fn).parameters
    if len(params) == 0:
        tree = build_fn()
    else:
        tree = build_fn(ctx)
    if not isinstance(tree, Behaviour):
        raise TypeError(
            f"build_tree() must return py_trees.behaviour.Behaviour, got {type(tree).__name__}"
        )
    return tree

BTSkillContext dataclass

BTSkillContext(
    skill_result_key: str,
    messages: list[Message] = list(),
    inputs: dict[str, Any] = dict(),
    namespace: str = "",
    tools: dict[str, Any] = dict(),
    model_client: Any = None,
    constraints: Any = None,
)

Runtime context passed to BT skill build_tree().

This keeps behavior-tree-backed skills independent from Jianmu internals by exposing only the inputs they typically need: messages, namespace, tools, model client, and constraints.

属性:

名称 类型 描述
messages list[Message]

Conversation messages visible to the BT skill.

inputs dict[str, Any]

Structured input payload resolved for the BT invocation.

namespace str

State namespace prefix reserved for this skill run.

skill_result_key str

Namespace-local state key where BT skills should write their final structured result.

tools dict[str, Any]

Mapping of enabled tool names to tool objects.

model_client Any

Active model client exposed to the skill.

constraints Any

Guard or execution constraints applied to the skill run.