Metadata-Version: 2.4
Name: abstractskill
Version: 0.3.0
Summary: Agent Skills (SKILL.md) parsing, discovery, trust gating, and demand derivation for AbstractFramework
Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
License-Expression: MIT
Project-URL: Homepage, https://github.com/lpalbou/AbstractSkill
Project-URL: Repository, https://github.com/lpalbou/AbstractSkill
Project-URL: Documentation, https://www.lpalbou.info/AbstractSkill/
Keywords: abstractframework,abstractcore,abstractgateway,agent-skills,skill-md,skills,mcp,ai-agents,trust,progressive-disclosure
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: PyYAML>=6.0
Provides-Extra: apple
Provides-Extra: all-apple
Provides-Extra: gpu
Provides-Extra: all-gpu
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest>=7.0.0; extra == "test"
Dynamic: license-file

# AbstractSkill

[![PyPI version](https://img.shields.io/pypi/v/abstractskill.svg)](https://pypi.org/project/abstractskill/)
[![CI](https://github.com/lpalbou/AbstractSkill/actions/workflows/ci.yml/badge.svg)](https://github.com/lpalbou/AbstractSkill/actions/workflows/ci.yml)
[![Tested Python](https://img.shields.io/badge/dynamic/yaml?url=https%3A%2F%2Fraw.githubusercontent.com%2Flpalbou%2FAbstractSkill%2Fmain%2F.github%2Fworkflows%2Fci.yml&query=%24.jobs.test.strategy.matrix%5B%22python-version%22%5D&label=tested%20python&color=blue)](https://github.com/lpalbou/AbstractSkill/actions/workflows/ci.yml)

AbstractSkill is the shared Python library for [Agent Skills](https://agentskills.io/) (`SKILL.md`) in the
[AbstractFramework](https://github.com/lpalbou/AbstractFramework) ecosystem.

It provides a small, dependency-light foundation for:

- parsing and validating `SKILL.md` frontmatter and instructions
- discovering skills on disk (progressive disclosure: metadata first)
- computing stable content hashes for skill evolution and replay safety
- formatting compact `<available_skills>` prompt blocks for hosts and agents
- composing a skill's tool declarations with an operator grant (never widening)
- classifying skill trust: validated skills, do-not-use advisories, and a
  fail-closed verdict ([trust model](docs/trust.md))

Flows run; skills are activated. AbstractSkill owns the portable skill contract so `abstractruntime`,
`abstractgateway`, and thin clients can share identical semantics without duplicating parsers.

## Install

```bash
pip install abstractskill
```

The wheel carries the library and the curated skill registry (the shelf below).

## The bundled skill registry

`pip install abstractskill` installs the reviewed shelf as package data:

```
abstractskill/registry/skills/            # 14 curated skills (one folder per SKILL.md)
abstractskill/registry/licenses/          # upstream licenses of catalog-vendored skills
abstractskill/registry/validations.yaml   # trust records: byte pins per skill tree
abstractskill/registry/advisories.yaml    # do-not-use advisories (empty at v1, by design)
abstractskill/registry/guidance.yaml      # class-level curation guidance
abstractskill/registry/catalog.yaml       # vendoring catalog + the bundle `version`
```

In this repository the same files live under `src/abstractskill/registry/`;
[docs/skills-catalog.md](docs/skills-catalog.md) is the human-readable index.

Hosts do not serve the installed package directory (it is replaced on every
upgrade). They copy it into a directory they own with `seed_registry`:

```python
from pathlib import Path

from abstractskill import bundled_registry_dir, bundled_registry_version, seed_registry

print(bundled_registry_dir(), bundled_registry_version())

report = seed_registry(Path("/srv/my-host/skills-shelf"))
print(report.added, report.updated, report.unchanged)
print(report.kept)          # {item: reason} for everything left untouched
print(report.not_in_bundle) # seeded earlier, no longer bundled
```

`seed_registry` is safe to run on every start, and from several processes at
once (the whole call holds an exclusive lock on `<dest>/.seed.lock`):

- a skill folder or file missing at the destination is added;
- one byte-identical to the bundle is unchanged;
- one still byte-identical to what an earlier seed wrote (recorded in
  `<dest>/.seeded.json`, compared by tree hash) is replaced by the newer
  bundled content — unless the installed bundle is older than that seed, in
  which case it is kept (`kept_newer`);
- anything else is kept as it is, with its reason: `kept_user_modified` (an
  operator edit), `kept_foreign` (content no seed wrote),
  `kept_unknown_provenance` (the destination has no seed manifest),
  `kept_symlink`, `kept_unreadable`. `report.kept` maps every kept item to
  its reason;
- items an earlier seed wrote that the bundle no longer contains are
  reported in `not_in_bundle` and left in place; removing them is the host's
  decision. Nothing outside the bundle is ever deleted.

Without a manifest (a first seed into a populated folder, or a lost
`.seeded.json`), items byte-identical to the bundle are adopted and recorded;
every other existing item is kept as `kept_unknown_provenance`, because the
seed cannot tell an old seeded copy from an operator edit. Keep `.seeded.json`
with the shelf when you back it up or move it.

Seeding twice writes nothing. No network access is involved.

The seeded directory has the layout a host reads as its shelf: `<dest>/skills`,
`<dest>/licenses` and the four yaml files (`validations.yaml`,
`advisories.yaml` and `guidance.yaml` drive the trust gate; `catalog.yaml`
records the curated sources and the bundle version). Trust records bind to
content hashes, never to paths, so a seeded shelf verifies wherever it lives,
and an edited skill honestly drops to unverified until it is re-validated.

A host uses the API this way: call `seed_registry` on its own shelf
directory at start-up, serve `<dest>/skills` through
`select_skills_for_context` with the seeded trust files, and surface the
report (in particular the kept items and `not_in_bundle`) to its operators.
AbstractGateway, for example, seeds `<data dir>/skills/registry` when it
starts and lets operators point it at another shelf with its `skills.shelf`
setting (console or `abstractgateway config set skills.shelf <folder>`).

## Quick start

```python
from pathlib import Path

from abstractskill import FilesystemSkillLoader, format_available_skills_xml, parse_skill_md

# Parse a SKILL.md file
doc = parse_skill_md(Path("my-skill/SKILL.md").read_text(encoding="utf-8"))
print(doc.metadata.name, doc.metadata.description)

# Discover skills under one or more roots (later roots override earlier ones).
# NOTE: discovery is for LISTING only — it applies no trust gate. Do not pipe
# discover() straight into activation.
loader = FilesystemSkillLoader([Path.home() / ".abstract" / "skills", Path(".abstract/skills")])
skills = loader.discover()
print(format_available_skills_xml(skills))

# Load full instructions when a skill is activated
loaded = loader.load("my-skill")
print(loaded.document.content_hash)
```

To ACTIVATE skills into a context, gate them through trust in one call so the
order (load → hash → evaluate_trust → compose) cannot be skipped:

```python
from pathlib import Path

from abstractskill import TrustRegistry, select_skills_for_context, format_available_skills_xml

shelf = Path("/srv/my-host/skills-shelf")  # a directory filled by seed_registry
registry = TrustRegistry.load(
    validations_path=shelf / "validations.yaml",
    advisories_path=shelf / "advisories.yaml",
)
selection = select_skills_for_context(
    registry, shelf_root=shelf / "skills",
    names=["coredoc", "backlog"],  # names-only is enough: sources derive from the registry
    enabled=[],  # names the operator explicitly review-enabled for this context
)
# Only trust-gated skills reach the prompt; blocked skills never appear.
block = format_available_skills_xml(
    list(selection.active), descriptions=selection.activation_descriptions
)
```

## Package scope

- `parse_skill_md` — YAML frontmatter + markdown body (LF/CRLF/CR; spec-validated
  name/description/compatibility)
- `FilesystemSkillLoader` — list metadata and load full documents; `discover()` and
  `load()` resolve identically (a broken copy never shadows a valid one) and degrade
  loudly (`#FALLBACK` warnings via logging and optional `on_warning`)
- `content_hash` — SHA-256 digest of one document for evolution tracking
- `hash_skill_tree` / `inspect_skill_dir` / `read_skill_resource` — whole-tree
  tamper hash (injective manifest), structural inventory (`has_scripts` is a
  structural fact), bounded in-tree resource reads
- `effective_tools` / `effective_tools_for_skill` — grant ∩ allowed-tools
  composition (skills can narrow below the grant, never widen beyond it;
  absence of `allowed-tools` implies nothing)
- `format_available_skills_xml` — deterministic discovery prompt block
- `evaluate_trust` + `TrustRegistry` / `ValidationRecord` / `AdvisoryEntry` /
  `GuidanceEntry` — validated-skill attestations bound to tree hashes, a
  do-not-use advisory registry (four mandated fields, graded severity), and a
  fail-closed `TrustVerdict` (blocked / requires_review / attachable). The
  curated shelf (first-party + catalog-vendored skills) ships in the package under
  `abstractskill/registry/`. See the
  [trust model](docs/trust.md).
- `select_skills_for_context` + `SkillSelection` — the one trust-gated activation
  pipeline (load → hash → evaluate → gate); hash-pinned enables; declared MCP/tool
  dependencies surfaced for host-side refusal.
- `load_catalog` / `CatalogEntry` / `SkillCatalog` — curated vendoring catalog
  (pinned upstream commits, expected tree hashes).
- `bundled_registry_dir` / `bundled_registry_version` / `seed_registry` + `SeedReport` —
  the shelf shipped in the wheel and the policy-safe copy into a host directory.
- `derive_demand` + `DemandReport` — derived demand tier from declared tool/MCP
  requirements joined against a host inventory (informational; hosts enforce grants).

### Hashing contract: hash = bytes, parse = meaning

`content_hash` and `hash_skill_tree` are byte-exact deliberately — tamper detection
must never call two byte-different trees "the same". A CRLF-authored skill and its
LF twin parse identically but hash differently: vendor skills from archives or
byte-copies, never through EOL-rewriting checkouts (e.g. git `autocrlf`), or hash
verification will honestly report the rewrite as a mismatch.

Out of scope: gateway registry APIs, zip `.skill` packaging, and runtime activation handlers.
Those layers live in `abstractgateway` and `abstractruntime` and consume this library.

## Documentation

Full documentation is in [`docs/`](docs/README.md) and on
[GitHub Pages](https://www.lpalbou.info/AbstractSkill/):

- [Getting started](docs/getting-started.md) — install, parse, discover, trust, seed, activate
- [Architecture](docs/architecture.md) — components, data flow and seeding (with diagrams)
- [API reference](docs/api.md) — every public function and type
- [FAQ](docs/faq.md) and [Troubleshooting](docs/troubleshooting.md)
- [Trust model](docs/trust.md) and [curated skills catalog](docs/skills-catalog.md)

See also [SECURITY.md](SECURITY.md) for the trust guarantees this library does
and does not make, [CONTRIBUTING.md](CONTRIBUTING.md) for the development
workflow, and [CHANGELOG.md](CHANGELOG.md) for release history.

## Development

```bash
python -m pip install -e ".[test]"
python -m pytest -q
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for building the package and the docs
site, and for the rules that keep the bundled registry verifiable.

## License

MIT — see [LICENSE](LICENSE).
