to11 skill sync applies it. There is no command for any of them, because a command per key would be a second interface to the same file.
Two locations, both at once
They are not alternatives. Inside a configured repository you have your own skills and the repository’s, each written into that location’s own directories.
$TO11_HOME moves the home location. Your credential lives alone in ~/.to11/credentials.json at mode 0600 — configuration never contains a secret, which is what makes a repository’s copy safe to commit and easy to read.
The file
Every key
projectId is read and never written. It was a second key that named a
project, and a file an earlier CLI wrote still carries it. It is accepted,
folded into project, and gone from the next thing the CLI writes — with
project winning if a file somehow has both.
There is no key for the API or gateway URL. A repository carries its own
config, so a key naming the host would let a checkout point the CLI — and
the credential it sends — at a machine you never chose. Both live in
TO11_API_URL and TO11_GATEWAY_URL, which nothing in a checkout can set. A
file that still names either one is refused as an unknown key.
Unknown keys are an error, naming the key. A typo in a file that decides what reaches an agent should not be silently ignored: a misspelled disabeld would leave a skill you believe you turned off.
version is the config’s own format version, not a skill version. An older file is converted forward; one newer than your CLI is refused, naming the file and the versions it reads. Guessing at instructions about where to write files on your machine is the one place less still beats nothing does not hold.
cacheTtlSeconds belongs to you, so a repository’s copy setting it is ignored: refresh cadence is machine behavior, the same as a hook.
Turning a skill off
enforced: true beside it. Writing it is allowed and redundant.
Holding a version
unchanged in to11 skill list, with the version column saying the rest: pinned 4 (live: 7). No sync will change it — your configuration is already satisfied.
Taking one skill from a project
A project entry with nolabel installs only the skills named under it, and each of those says which version it follows:
skills map with no label installs nothing, and is reported as such rather than passing silently — it is almost always a half-finished edit.
Precedence
Two phases, in this order.Phase 1 — each config governs its own scope
The repo config governs repo scope — what lands in the repository’s tree. Your config governs user scope — what lands in your own directories. They do not combine into one set. Two things cross from your scope into a repository’s, and both are you refusing or insisting:- a disable, because declining always works;
- an
enforced: truestatement, which wins that scope in the repository’s tree too.
acme says nothing about a repository’s config for globex.
Phase 2 — the most specific statement wins
A statement about one skill beats a statement about the whole project. That is the entire rule. Enforcement decides who owns a scope; it does not promote a statement to a higher scope. So your enforced project-wide label still loses to a repository’s per-skill statement — enforcement won the project scope, and then the more specific statement answered.Worked answers
Each row is one skill, in the location where the two statements actually meet.
Access is the floor, not a phase. No statement, enforced or not, adds a skill you cannot access. A statement narrows what you already have.
A declaration is not a guarantee. Enforcement exists, so any developer can enforce their way out of a repository’s set. Nothing — no review process, no build step — can rely on a skill being present on a machine.
Where skills land
A target has a type and a path. The type is also the render target: it decides what is rendered, not only where it lands, which is why a bare path is not accepted.
Paths are relative and contained. Absolute paths are refused. For a repository’s config this is a security boundary rather than a style rule: that file is committed, so an absolute path in it is a way for one person to write directories onto every colleague’s machine.
.. is refused too, and a resolved path that leaves the root through a symlink is refused as well.
standard is how an agent nobody has heard of still works: a known type at a stated path, receiving the portable form.
targets absent means both defaults; present means exactly what is listed.
A skill occupies one directory, named <slug> — or <prefix>-<slug> when prefix names one. The prefix is not what makes the directory ours; the state file is. Nor is it what keeps the skills out of git: every installed directory carries its own .gitignore holding *, which hides the directory and the ignore file with it, whatever the directory is called.
What a prefix is for is a name collision. A published code-review and a code-review/ you already have want the same directory, and the CLI refuses that path rather than taking it. A prefix moves every directory out of the way at once.
The value carries no separator — the dash is the CLI’s. acme and acme- are the same prefix and both install acme-<slug>; acme-- installs acme--<slug> for somebody who meant two.
to11 init asks, and writes the key only when you answer. --prefix supplies it without being asked, and --prefix "" clears one already set.
A prefix names a directory, it does not choose where one goes: a value holding / or \ is refused, at init and on every read, so a skill cannot land outside the target it was resolved for.
What the CLI records
Each location holds.to11/.skills.state.json beside its configuration, recording what the CLI wrote: one entry per skill and target, with its project, the version installed, the directory it went to relative to the state file, and per file its name, its hash and its block id.
This file is what makes a directory ours — not the name it has. A directory the CLI did not record writing is never touched, never read, never counted.
It is state, not a cache: the hash of what was written cannot be recomputed once it is gone, and neither can ownership. Deleting it orphans skills rather than freeing them — the next sync finds the paths occupied and reports conflict, and to11 skill sync --force is the recovery.
It also records when a refresh last ran, which is the cache window’s clock. It records the attempt rather than the outcome: a refresh that failed stamps it too. Nothing else about a past run is kept here.
A visible file you edit inside an installed skill makes that skill a local variant, and sync leaves the whole of it alone. Dot-prefixed files are not compared, so editing one changes nothing here — see the warning above. Writing a new version of one file beside an edited copy of another would produce a tree whose links point at content that never shipped together. It ends one of two ways, both explicit: commit it with to11 skill store, or revert it with to11 skill sync --force.
That is about a skill being UPDATED. A skill or target the configuration stops asking for is judged per directory: each one is taken back if it still holds what the CLI wrote, and is otherwise reported and left — edited, or unreadable. So one edited copy no longer keeps the rest of the skill on disk, and the directories that remain are named on every run until you move what you want to keep.
code.claude.yaml
A second file, beside the first, naming the provider to11 code routes a coding session to. It is required for a routed session: without one the command refuses to start the agent.
All three keys are required. Together they name one provider activation: a provider is connected to a project and activated per environment.
Each value takes either a slug or an id, in one field. The value is matched against both the id and the slug of what the project holds.
to11 init writes slugs.
An unknown key is an error naming the key, as in skills.yaml.
The file is read from two locations — ~/.to11/code.claude.yaml and <repo>/.to11/code.claude.yaml. Unlike skills.yaml they are alternatives, not additive: inside a configured repository that file is used whole, and no key merges across the two.
There is no model key; the agent chooses its own model. There is no version key; a provider’s config version is a property of its activation in an environment, so environment names one.