Policies
A policy is a deterministic, governance-authored rule that decides what happens when a developer's agent invokes a skill. Policies are the blocking gate: they turn "this version is retired" into a real block with a reason the developer sees — and an entry in the audit log. You author them in the console; the gate enforces them locally on every machine.
Anatomy of a policy
| Field | What it is |
|---|---|
| Matcher type | how the policy finds skills — manifest_id, namespace, declared_metadata, or capability |
| Match value | the value the matcher compares against (e.g. a namespace, or target:teamleader) |
| Action | Allow, Warn or Block — stored in the API as allow, warn, halt |
| Reason | the explanation shown to the developer at the gate and recorded in the audit log |
| Status | draft → active → archived; only active policies enforce |
Reasons are not optional in spirit
A blocking policy with no reason is a dead end for the developer whose agent just got blocked. Always write a reason that says why and what to use instead — it is the difference between governance and an outage.
How a skill is matched
You choose one matcher type per policy. They are listed here most specific first:
| Matcher | Matches when |
|---|---|
manifest_id ("Specific skill" in the console) | the skill's namespaced identity equals the match value, e.g. acme/crm-entry. Use it to warn about or block one skill without touching the rest of its namespace. It also catches the skill when Claude Code invokes it by its bare name (crm-entry). |
namespace | the skill's namespace (the part before / in acme/crm-entry) equals the match value |
declared_metadata | the skill's targets contain the value — written as target:<value>, e.g. target:teamleader |
capability | the skill does something, as skillsvault's scanner reads it from the content of its current version: shell (runs shell commands), packages (installs packages), writes (writes files), network (talks to any host), network:<host> (that host or a subdomain, e.g. network:pastebin.com), or env:<NAME> (reads that environment variable, e.g. env:AWS_SECRET_ACCESS_KEY) |
A capability policy matches what the content does, not what it declares — so capability: shell with Block stops every skill that runs shell commands, including ones published after the policy. Capabilities are read by a deterministic scan of each version (no model), carried in the signed bundle, and matched on the machine; a version is scanned before any capability policy is evaluated against it. The scan reads what the skill's text and files say they do, so it can't see behaviour hidden from it — treat it as a strong default, not a sandbox.
targets come from the skill's skillsvault.json manifest — which is exactly why that file exists; the namespaced identity is the skill's registry name. As you author a policy, the console previews "this matches N skills", how many decisions it changes, and what it would have done to real runs: "In the last 30 days they ran 412 times; 37 of those would now be blocked." (counted from your audit log, within your plan's audit history), so you see the blast radius before you activate.
No regex, no model on the decision
Matchers are intentionally exact — there is no regex matcher and no LLM anywhere in evaluation (capabilities come from a deterministic scanner, not a model). Every policy is written and activated by a person on your team. The publish-time AI review may suggest that one of your existing policies should also cover a new skill; ratifying the suggestion creates a draft Specific skill policy, which still has to be activated by hand.
Commands are capped at warn
A Block that matches a slash command (an artifact of kind command) is enforced as a warn: the gate matches commands from typed text, so it warns rather than refusing. Skills and subagents are blocked. See the gate.
Status drives decisions too
You don't always need a hand-written policy. A skill's status, set in the registry, contributes directly:
- a skill marked
banned→ block - a skill marked
deprecated→ warn - a skill under a managed namespace that isn't in the registry → warn (the bundle's "managed missing manifest" default) — this surfaces sideloaded or pre-skillsvault folders by their missing proof of currency.
Most restrictive wins
When more than one signal applies to the same invocation, the most restrictive action wins:
block > warn > allow
A banned status is never downgraded by a broader warn policy. Among equally-restrictive candidates, the most specific match keeps the explanation, so the reason the developer sees is the one that best describes the block.
Exceptions
Sometimes a policy is right and one case still needs through: a hotfix during a release freeze, a migration that needs the retired skill for another week. Instead of turning the policy off, ask for an exception.
- Ask. A block on a machine ends with a link: Need it? Ask for an exception. It opens Requests → Request an exception with the policy and the skill filled in. Say why you need it and for how long (7, 30 or 90 days). Anyone in the organisation except a billing-only member can ask.
- Decide. Owners, admins and governance officers are emailed and see it under Requests. They grant it, for the time asked or a different one, or decline it with a note. Until then the skill stays blocked.
- Enforced everywhere. A granted exception is compiled into the signed policy bundle, so the gate on every machine skips that one policy for that one skill. Delivery over MCP and
skillsvault pullhonour it too. It ends on its own when it expires, or earlier if a reviewer revokes it. Two days before it ends, the person who asked is emailed a link to request a renewal. - Narrow by design. An exception covers one skill and one policy. Every other policy still applies, and a skill banned in the registry stays blocked: unban it if it should run.
Each request, grant, decline, withdrawal and revoke is recorded under Audit log → Admin activity with who did it.
CLI version
The gate honours exceptions and shows the request link from CLI 0.6.8. Older versions keep blocking the skill, so an exception never loosens enforcement on a machine that can't read it. Re-run the install script to upgrade: curl -fsSL https://skillsvault.io/install.sh | sh.
Worked example: retiring a skill
Say acme/teamleader-crm-entry is being replaced by acme/hubspot-crm-entry.
Publish the replacement
Publish acme/hubspot-crm-entry and mark it active. See Authoring & publishing.
Ban the old skill
Open acme/teamleader-crm-entry in the registry and choose Ban skill. If you want a custom reason shown to developers, author a Specific skill policy for acme/teamleader-crm-entry (or a declared_metadata policy such as target:teamleader) with action Block and the reason:
Teamleader was retired 2026-03-01. Use acme/hubspot-crm-entry instead.
Distribute
The change is rolled into your org's signed bundle. Each machine picks it up the next time it syncs — skillsvault sync, skillsvault try, skillsvault pull, or a fresh login. The gate also keeps the bundle current on its own: when it runs on a bundle more than an hour old, it decides on the bundle it has, then starts a background skillsvault sync --quiet so the next call uses the new one.
Enforcement
In Claude Code with the hook installed, the next time an agent reaches for the old skill the gate blocks it and surfaces your reason. In every harness, skillsvault pull removes the banned folder. Every gate decision lands in the audit log.
Related
How a policy becomes an allow / warn / block on the developer's machine.
The manifest_id and targets your matchers compare against.
Which harnesses can be vetoed at runtime vs. only governed by distribution.
The immutable record of every decision a policy produced.