July 2026Level 3 · ContextSkills
skilltree — How It Works (Deep Dive)
The mechanics only: the tree format, coordinate addressing, the Read-breadcrumb descent, and the CLI loop that keeps a hundred skills coherent while loading one layer at a time.
The story of why this exists is in Blog 1: The narrative — why this exists. This post is the mechanics only — assembled from skilltree's own impl docs (the
README.md, and the two paperspapers/skilltree-paper.mdandpapers/flat-vs-tree.md). It states what the system IS, not what it might become.
The architecture
skilltree imposes a coordinate-addressed tree on the flat .claude/skills substrate
using the only three levers the platform leaves open (source: README, "Turn a flat folder…"
intro):
- placement — each node is a plain directory carrying its own one-skill
.claude/; the path is the coordinate (0->0.1->0.1.1); - the inert-nested-
.claudeboundary — only the top layer auto-loads, so deeper nodes stay out of context until entered; - breadcrumbs — each node's
SKILL.mdends with an index summary of its subtree and explicit instructions to Read its children.
The construction emulates the two things a graph would supply natively (source:
papers/skilltree-paper.md §2, "coordinate as identity, breadcrumb as edge"):
- Identity is a hierarchical coordinate. The root is
0; its children are0.1,0.2, …; their children0.1.1, and so on. Coordinates are assigned by a single pass over the directory tree (assign_coords), the one canonical source of the scheme — every other module reads it rather than re-deriving it. - Edges are breadcrumbs. Each non-leaf node's body carries, for every child, a line of the exact form:
- 0.1-A (cor): Read `…/domain/A/.claude/skills/0.1-A/SKILL.md`
The breadcrumb names the child's coordinate and the single operation that loads it. A coordinate stands in for node identity; a breadcrumb stands in for a directed edge.
Retrieval that survives flattening (source: papers/skilltree-paper.md §4) recovers what
a flat list of leaves loses. Each non-leaf node's body carries an index summary composed from
its subtree's own vocabulary, so a query about a descendant still retrieves the branch that
leads to it. A retrieval index (SQLite FTS5 / BM25) ranks nodes by relevance, and because every
node carries its coordinate, the same index can restrict a query to a coordinate subtree — the
coordinate is simultaneously the address and the search scope. One engine, one --scope flag.
How it runs
The runtime cycle is descent by reading (source: README, "The load mechanism it exploits
(verified against the runtime)"; corroborated by papers/skilltree-paper.md §6). Exactly one
node — the root — auto-loads; every other node loads only when its breadcrumb is followed.
Entering a node loads exactly its layer — its own content plus the breadcrumbs to its children —
and nothing below it. You load one layer and walk; you never load the pile.
The three runtime facts this depends on, verified directly against the tested Claude Code
runtime and observed as a state-change in the live skill manifest (not asserted) (source:
papers/skilltree-paper.md §6, reproduced verbatim):
| Observation | Result |
|---|---|
A .claude/skills nested below the directory in scope |
not auto-loaded |
Reading a file in a directory with the Read tool |
injects that directory's layer, one level; ancestors load, descendants do not |
A shell cat of the same file |
injects nothing |
The third row is a correctness condition on the emulation: the breadcrumb must invoke the
Read tool, not a byte read, or the edge silently fails to load and is therefore not an edge.
Because the platform will not maintain a structure it never shipped, skilltree maintains its own
coherence with a four-operation loop (source: README, "Self-management (it keeps itself
coherent)"; papers/skilltree-paper.md §5):
discoverreconstructs the on-disk tree from reality alone, independent of any manifest.coherereports the drift between that reality and the engineered shape — a bare (unrooted) forest, a stale breadcrumb, a coordinate that no longer matches, a skill dropped in flat.emitre-coheres in place; given a flat forest it tree-ifies it, moving each skill directory whole (all files preserved, symlinks de-symlinked) and journaling every move, sounemitrestores the prior state byte-for-byte.
A scheduled check (skilltree watch <root>) writes its verdict into a single managed rule, so a
decohered tree announces itself in every subsequent session.
Where things live
The file geometry, on disk and in source.
A tree on the filesystem looks like this (source: README, "How a tree looks on disk", reproduced verbatim):
domain/.claude/skills/0-domain/SKILL.md # root menu — the only node that auto-loads
domain/A/.claude/skills/0.1-A/SKILL.md # child A — loaded only when Read
domain/A/A1/.claude/skills/0.1.1-A1/SKILL.md # grandchild — one level deeper
domain/B/.claude/skills/0.2-B/SKILL.md
The root menu's generated body carries the index summary and the descend block (source:
papers/flat-vs-tree.md Appendix A.1, reproduced verbatim):
## Index summary
[0] domain — a sc index node; opens to 2 branch(es): A (cor), B (ac). Reachable below: A, A1, A2, B.
## Descend — the next layer (2)
Only this layer is loaded now. To descend, use the Read tool on a child below — that injects
the child's layer. A shell `cat` reads the bytes but loads nothing; you must use the Read tool:
- 0.1-A (cor): Read `…/domain/A/.claude/skills/0.1-A/SKILL.md`
- 0.2-B (ac): Read `…/domain/B/.claude/skills/0.2-B/SKILL.md`
The implementing source, by path:line (source: papers/flat-vs-tree.md Appendix A and
papers/skilltree-paper.md):
src/skilltree/model.py:70—assign_coords(the one canonical coordinate pass);compose_summary(the internal-node index summary).src/skilltree/materialize.py:36—_write_node, the breadcrumb emitter; theReadbreadcrumb format is fixed at:25(_CRUMB).src/skilltree/validate.py:60— the gate that requires a resolvableReadbreadcrumb for every child.src/skilltree/cohere.py—discover:117,cohere:152,emit:261,unemit:339; the scheduled checkwrite_notifications:430/watch:448.
The one CLI exposes the whole toolkit — skilltree <command> … (source: README, "Subcommands",
reproduced verbatim):
| Command | What it does |
|---|---|
tree <root> |
show the coordinate tree on disk (every node by its <coord>-<name> address) |
map <folder> [--write] |
render a flat folder into one coordinate-addressed CLAUDE.md (Folder Map + addressable Index + branch summaries) — progressive disclosure over a flat pile |
search <folder> <query> [--scope <coord>] |
FTS5/BM25 over any folder; --scope restricts to a coordinate subtree when the folder carries skilltree coordinates (a plain folder = coordinate-free search) |
fold <framework_dir> --into <node> --tree <root> |
bind a framework (its skill dir) into a volume node's ## Frameworks tome table — idempotent, one row per framework; the manifest is the source of truth and the node's SKILL.md is regenerated from it |
project <root> <target> [--policy flat\|progressive] [--link absolute\|relative] [--node N] |
deploy a tree into a skills dir as symlinks: progressive surfaces root + first-layer branches; flat projects a volume's tome rows + holder so every held framework auto-discovers top-level |
cohere <root> |
report drift between the on-disk tree and its coherent shape |
emit <root> [--root-forest] |
re-cohere in place; --root-forest tree-ifies a bare forest (lossless, journaled) |
unemit <root> |
reverse the last emit, byte-for-byte |
discover · validate · build · notify · watch |
read a tree · gate breadcrumb-resolvability · materialize from a manifest · write the notification rule · run the decoherence cron |
The invariants
The rules and gates the system holds itself to (source: papers/skilltree-paper.md §6 and
README, "Self-management" + the v0.3.0 changelog):
- Breadcrumb-resolvability gate. Every non-leaf node must carry a
Readbreadcrumb for each child, and every breadcrumb path must resolve to a real file (validate.py:60). An edge the reader cannot follow is not an edge. - Read, not
cat. The descend instruction is fixed to theReadtool (_CRUMB,materialize.py:25); a byte read injects nothing, so descent throughcatsilently fails. - Lossless emit/unemit.
emitmoves each skill directory whole (no body-only re-render, normtree) and journals every move;unemitrestores the prior state byte-for-byte. - One canonical coordinate source.
assign_coordsis the single pass that assigns the scheme; every other module reads it rather than re-deriving it. - Tome-table round-trip (v0.3.0). Manifest
frameworksrows must appear in the generated## Frameworkstable, every row's target must resolve, and undeclared table rows warn;foldis idempotent (one row per framework) and the node's SKILL.md is regenerated from the manifest, so a folded table can never be wiped or drift.
Read the story first, then the mechanics: Blog 1 — the narrative.
Chapter links
- The story — why this exists
- Get the plugin: https://pypi.org/project/agent-skilltree/
Read the story, then install: https://pypi.org/project/agent-skilltree/