What this system is, and its 5 parts
Think of this as the way a 5,000-person company stops re-inventing the same agent prompt fifteen times. Every team writes its own Skills. Refund handling, expense reporting, deployment runbooks. And they all live in one shared library, organised by team and version, just like a code repo. When an agent on any team needs to do something, it searches the library, finds the right Skill, checks that the user is allowed to use it (finance Skills are not for the support team), and runs it. The whole point is that knowledge gets re-used safely at enterprise scale, not copy-pasted into a hundred system prompts.
Skill Definition File
.claude/skills/{team}/{name}.mdThe unit of enterprise knowledge. Markdown body holds the instructions; YAML frontmatter holds the metadata the registry indexes (name, version, description, tags, depends_on, access_level). Lives in version control next to code, reviewed via PR like any other team artifact.
ConfigurationPath convention: .claude/skills/{team}/{name}.md. Frontmatter required: name, version (semver), description, tags, depends_on, access_level. Body: the actual prompt + examples. Reviewed in PRs.Concept: skills →Shared Registry & Indexer
rebuilds on every commitA CI job that walks .claude/skills//*.md, parses frontmatter, validates schema, builds a searchable index, and publishes it to the registry service. Idempotent. Fast (sub-minute on 500 Skills). When a Skill commit lands, the index is fresh within 60s and the new version is discoverable.
ConfigurationTriggered on push to main. Steps: glob skills, parse YAML, validate (semver, ACL, deps exist), upload index to registry. Reindex SLA: <60s. Failed parses fail the CI; bad Skills never reach the registry.Concept: structured-outputs →
Search Service
embeddings-based at scaleIndexes Skill descriptions + tags + frontmatter. Agents query in natural language ('find a Skill for processing customer refunds') and get the top-k matches with their metadata. Full-text works at <50 Skills; embeddings (OpenAI / Voyage) become essential past 100; org-wide deployments use a hybrid (embeddings for recall, full-text for precision).
ConfigurationPOST /search { query, k=5, filters: { team?, access_level?, tag? } } → [{slug, version, description, score}]. Latency p95 < 200ms. Cache embeddings keyed by (skill_slug, content_hash); recompute only on content change.Concept: context-window →Git-Based Versioning
semver in frontmatter + Git tagsEvery Skill carries a semver version in its frontmatter; every release tags Git so older versions stay reachable. Callers pin a MAJOR version (refund-resolver:v1.x); the registry serves the latest patch within that major. Breaking changes bump the major; old callers keep working until they migrate.
ConfigurationFrontmatter: version: 1.2.3. Caller: depends_on: ['support/refund-resolver:1.x']. Registry resolves to latest patch within pinned major. Deprecated versions stay queryable for 6 months before archive.Concept: tool-calling →
Access Control Layer
permission-aware invocationSits between Skill discovery and Skill execution. Reads the calling agent's role + the Skill's access_level (public | team | role-restricted | sensitive). Denies invocation when the agent's role isn't in the allowlist. Returns a structured permission-denied error. The agent observes it and can request access via the org's standard flow, not bypass it.
ConfigurationPre-invocation: { agent_role, skill_acl } → { allowed: bool, reason }. ACL stored in frontmatter access_level + team-level org config. Denied: structured error { code: 'ACL_DENIED', skill, reason, request_url }.Concept: evaluation →The problem
- One source of truth across 15 teams. No copy-pasted Skill prompts drifting in 15 different repos.
- Discoverable at enterprise scale. An agent on the marketing team finds the right finance Skill in seconds, not hours.
- Permission-aware. Finance's budget-approval Skill must be unreachable from support's agent, no matter how the support agent is prompted.
- 200+ Skills in one flat folder → collision week 1 (refund-resolver exists in support/, growth/, AND finance/, all mean different things).
- No semver → v2 silently breaks v1 callers when frontmatter shape changes; agents start failing silently across the org.
- No ACL → support agent invokes finance/budget-approval because the Skill description sounded relevant; policy violation at scale.
- ✓ Naming collision rate = 0 (team namespace prefix enforced)
- ✓ Breaking-change incidents = 0 (semver in frontmatter, callers pin major)
- ✓ Cross-team unauthorized invocation rate = 0 (ACL check before execution)
- ✓ Skill-discovery p95 latency < 200ms (embeddings or full-text index)
- ✓ Reindex SLA < 60s from commit to searchable
One run, traced end to end
8 steps to production
Lay out the team-namespaced directory
Create .claude/skills/{team}/{name}.md per team. Even on day one with 5 Skills, namespace from the start. Retrofitting a flat layout into namespaces at 100 Skills is painful. The directory IS the registry's source of truth.
Concept: skills →# Repository layout
# .claude/
# └── skills/
# ├── support/
# │ ├── refund-resolver.md # support/refund-resolver
# │ └── escalation-router.md # support/escalation-router
# ├── platform/
# │ ├── deploy-runbook.md
# │ └── incident-triage.md
# ├── data/
# │ ├── query-builder.md
# │ └── pii-redactor.md
# └── finance/
# └── budget-approval.md # access_level: sensitive
# Bootstrap script for a fresh repo
import os
TEAMS = ["support", "platform", "data", "growth", "finance"]
for t in TEAMS:
os.makedirs(f".claude/skills/{t}", exist_ok=True)
with open(f".claude/skills/{t}/.gitkeep", "w") as f:
pass
print("namespace-by-team layout ready; commit and start authoring.")Define the Skill frontmatter schema
Every Skill carries the same YAML frontmatter shape, validated by the indexer. Required: name, version (semver), description, tags, access_level. Optional: depends_on, deprecated, owners. Schema lives in the repo so PRs that break it fail CI before merging.
Concept: structured-outputs →Build the registry indexer
A CI job walks .claude/skills//*.md, parses each Skill's frontmatter, validates the schema, resolves dependencies, and writes a searchable index. Runs on every push to main; reindex SLA <60s on 500 Skills. Bad Skills (broken schema, missing dep, semver violation) fail the CI. They never reach the registry.
Concept: structured-outputs →Add semantic search over the registry
At <50 Skills, full-text on description+tags is enough. Past 100, agents need to discover by intent rather than keyword (a Skill that handles customer refunds should match refund-resolver even without the word 'refund' in the query). Embeddings + vector index over Skill description+tags is the play; cache embeddings keyed by body_hash so re-embedding only fires on content change.
Concept: context-window →Pin versions on every dependency edge
Every depends_on in a Skill's frontmatter pins a MAJOR version (support/case-facts:1.x), not a fixed PATCH. The registry resolves to the latest PATCH within the pinned major. When case-facts ships a breaking change, it bumps to v2. Old callers continue against v1.x; new callers opt in to v2 explicitly. This is exactly how pip / npm work, applied to Skills.
Concept: tool-calling →Enforce ACLs before invocation
Permission-aware RAG isn't built into Claude. You implement it. Read the calling agent's role + the Skill's access_level, run a hard check before invoking, and return a structured error on deny. This is a deterministic gate, not a prompt-language constraint; the Skill's body never executes if ACL fails.
Concept: evaluation →Wire the agent's Skill discovery into its tool loop
Expose two tools to every agent: search_skills(query, filters) and invoke_skill(name, version, payload). The agent finds Skills by intent, the ACL gate runs inside invoke_skill, and the Skill body executes only on allow. The agent never sees the registry's raw 200+ entries. Just the top-k matches for its query, gated by access_level.
Concept: tool-calling →Track usage + deprecation lifecycle
Once Skills are in production, the registry needs to know which Skills are hot, which are stale, which have known broken versions. Log every invoke_skill call with name, version, agent role, outcome. Surface a deprecation notice in search_skills results when an old version is queried. Auto-archive Skills with zero invocations in 6 months.
Concept: evaluation →9 decisions the exam turns into distractors
Flat folder, full-text search, no ACL ('we'll add permissions later')
Team-namespaced directory ({team}/{name}) + shared registry + embeddings search + ACL layer
Edit v1 in place; tell teams to update their callers
Bump major (v2.0.0); existing callers stay on v1.x until they migrate; deprecation notice in v1's frontmatter
Trust the prompt; finance Skills not in support agent's tool list
ACL gate denies pre-execution; structured ACL_DENIED error with request_url returned to the agent
Show all 200+ Skills in the agent's tool list
search_skills(query). Embeddings/full-text returns top-k matches with metadata
200+ Skills in .claude/skills/ flat folder. Naming collisions appear in week 1 (refund-resolver exists in support, growth, and finance contexts, all meaning different things). Discovery becomes a grep contest.
Team-namespaced layout: .claude/skills/{team}/{name}.md. Collisions become structurally impossible. support/refund-resolver and growth/refund-resolver are distinct paths. Past 50 Skills, add an embeddings-based search service.
Skill case-facts ships a breaking change (frontmatter shape changes). Every agent in the org that depends on it starts failing silently. No way to roll back a single Skill's update.
Semver in frontmatter (version: 1.2.3) + Git tags. Callers pin major (case-facts:1.x); registry resolves to latest patch. Breaking changes bump major, callers migrate deliberately.
Two teams independently author a refund-resolver Skill. Both end up in .claude/skills/refund-resolver.md (last commit wins). Agents call the wrong one; nobody notices for weeks.
Team namespace prefix: support/refund-resolver vs growth/refund-resolver. The directory layout enforces uniqueness; the indexer rejects duplicates. PR review surfaces collisions before merge.
Support agent's prompt is cleverly engineered (or injected via PR content) to invoke finance/budget-approval. The Skill executes; an unauthorized $50K refund is approved. Audit log shows the agent did it; ACL log shows nothing because there is no ACL.
ACL gate (access_level: public | team | role-restricted | sensitive) on every Skill, checked pre-invocation. Denied calls return a structured ACL_DENIED error; the agent observes it and either escalates or routes differently. Deterministic, not prompt-based.
Each agent's system prompt copy-pastes the relevant Skill content inline. When the Skill changes, 12 agents need updating. Nobody updates them all; behavior drifts over months.
Skills are reusable, composable, versioned units. Agents reference them via invoke_skill('support/refund-resolver:1.x', payload). One source of truth; one Skill update propagates to every caller automatically.
Cost & latency
Skill body ~500 tokens system + ~200 input + ~100 output. Sonnet 4.5 pricing. Most Skills are narrow, focused units. No inflation from generic prompt scaffolding.
Voyage / OpenAI embedding ~512 dims at fractional cost per query. Embeddings cached by body_hash so re-embedding only fires on content change. At 1M queries/month, ~$100.
Indexer is pure parsing on GitHub Actions free tier. Only cost is re-embedding Skills with changed content. Typically <5% of the registry per push.
ACL is a deterministic dictionary lookup against frontmatter + agent role. No LLM call. Latency is unmeasurable in the pipeline; cost is in maintenance, not execution.
Embeddings store + search service + reindex compute. Small relative to the per-invocation Skill execution cost which dominates total spend at scale.
Check every gate before release
5 exam-pattern questions
Work through one question at a time, check the architecture, then move through the set.
An enterprise has 200+ Skills across 15 teams. Skill-name collisions occur weekly (refund-resolver exists in support, growth, and finance, all meaning different things). How should you structure the registry to prevent this structurally?
Frequently asked
What's the maximum number of Skills per organization?
Unbounded with the right infrastructure. Per-project (a single agent's working set), keep <12 for discoverability. Per-team, low hundreds is comfortable with a search service. Org-wide, thousands work with embeddings + namespaces + ACLs. The bottleneck is rarely raw Skill count. It's how the agent finds the right one and how the org governs change.
Can a Skill depend on other Skills?
Yes, declared in frontmatter. depends_on: ['support/case-facts:1.x', 'shared/escalation-queue:2.x']. The registry validates dependencies exist at index time (CI fails on missing dep) and resolves them topologically at invocation time. Avoid cycles. The dep resolver detects them and rejects.
How do you version Skills without breaking existing agents?
Semver in frontmatter + callers pin major. A Skill at v1.2.3 keeps backward compatibility for all v1.x callers. When a breaking change is needed, bump to v2.0.0; existing callers continue against v1.x until they migrate deliberately. Deprecation notices in the v1 frontmatter point to v2; the registry surfaces the warning in search_skills results.
Is permission-aware RAG built into Claude?
No. You implement it. Claude's tool layer doesn't know about your org's roles. Implement an ACL gate that runs pre-invocation: read agent role + Skill access_level, deny if not allowed, return structured ACL_DENIED. The Skill body never executes if the ACL check fails. This is the same pattern as authorization middleware in any HTTP service. Deterministic, not LLM-judged.
Should sensitive Skills be versioned differently?
No. Same versioning, different access control. Versioning is about backward compatibility; access control is about who can invoke. They're orthogonal. A sensitive Skill ships v1.2.3 just like a public one; the ACL gate gates who can call it, regardless of version.
How do you find the right Skill from 200+?
Two tools, one query. First, search_skills(query, k=5, filters). Embeddings search returns top-k matches by intent. Second, invoke_skill(name, version, payload). Runs the chosen Skill with ACL check. The agent never sees raw access to the registry; it queries through the search tool. This keeps the agent's tool list small (just 2 tools) while exposing the entire Skills library.
What happens to old Skill versions when a new major ships?
They stay queryable for 6 months by default. The deprecation lifecycle: ship v2.0.0 → mark v1.x with a deprecation note in frontmatter → registry serves v1.x to existing callers but flags the deprecation in search results → after 6 months of zero invocations, auto-archive. Active Skills stay forever; truly cold ones get cleaned up.
