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 on a skill you believe you disabled.
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.
Disabling a skill
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. One thing crosses from your scope into a repository’s: anenforced: true statement, which wins that scope in the repository’s tree too. A disable crosses only when it is enforced; a plain one decides your own folders.
Nothing crosses the other way. A committed config cannot redirect your targets, disable a skill you installed for yourself, or change how your machine behaves.
Both apply only within the same organization: a config for 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 environment 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.
codex’s folder, .agents/skills, is shared. In a repository Codex, Cursor, GitHub Copilot, Gemini CLI and other agents read it; at home Codex, Cline, Warp, Zed and other agents do, while Cursor, GitHub Copilot and Gemini CLI read ~/.cursor/skills, ~/.copilot/skills and ~/.gemini/skills. The type keeps its name.
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.
Only the file and to11 link --prefix set it: to11, to11 link and to11 ui do not ask for it, and keep it as it is when they save.
A prefix names a directory, it does not choose where one goes: a value holding / or \ is refused 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 — unless you run to11 skill sync --force and a configured skill needs its name, which reports it as replaced.
Ownership is recorded per file, and hidden files are not compared. The CLI skips dot-prefixed entries when it compares a skill against what it wrote — it is what keeps a
.DS_Store from marking a skill changed and blocking published updates — so editing or adding a hidden file never marks a skill dirty, and a new version is written beside it.The one exception is the .gitignore the CLI writes. It is dot-prefixed and it is recorded and compared, because we wrote it — editing it makes the skill read as changed, which is what stops a sync replacing it without saying so.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 file: each file the CLI wrote that still holds what it wrote is removed, and every other file stays — one you changed, one you added, hidden or not, and one reached through a link inside the skill directory, because the CLI never writes through a link — except a .DS_Store written by macOS Finder, which holds only how Finder displays the folder. A directory left empty is removed. One that is not, or one the CLI cannot read, is reported as orphaned on every run until it is empty, and no flag removes it.
code.claude.yaml
A second file, beside the first, naming the provider to11 code routes a coding session to. Each agent has its own: code.claude.yaml for to11 code claude, code.codex.yaml for to11 code codex. Both take the same keys and resolve the same way; everything below applies to either. One is required for a routed session with that agent. When there is none, the first session sets one up from skills.yaml, as to11 code describes; with no skills.yaml either, the command refuses to start the agent.
project, environment and provider 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 ui writes slugs; a first to11 code session writes the project as skills.yaml names it. Both always write organization.
Which organization a workspace/project resolves in, first match wins:
--organizationonto11 code;TO11_ORGANIZATION;organizationin this file;organizationin theskills.yamlin the same location;- the one organization you belong to;
- the one of your organizations that has that
workspace/project.
to11 code stops, names them, and asks for --organization or organization: <slug>. A project given as an id needs no organization. Every command that takes a project resolves it the same way; see Naming a project.
One location, one organization. When any two of skills.yaml, code.claude.yaml and code.codex.yaml in the same location name different organizations, every command that reads configuration stops until they agree — to11 skill …, to11 code, and the refresh hooks. to11 with no arguments, to11 login, to11 logout, to11 whoami and to11 version read no configuration and are unaffected.
An unknown key is an error naming the key, as in skills.yaml.
Each file is read from two locations — ~/.to11/code.<agent>.yaml and <repo>/.to11/code.<agent>.yaml. Unlike skills.yaml they are alternatives, not additive: inside a repository that has the agent’s file, that file is used whole, and no key merges across the two. The two agents resolve independently, so a repository can hold code.claude.yaml alone while Codex sessions there still read ~/.to11/code.codex.yaml.
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.
Environment overrides
Each takes precedence over the file, for CI and one-off runs — exceptTO11_API_KEY, which is used only when no person is signed in.
Which credential a command uses
Every command exceptto11 ui picks, in order:
- the person signed in with
to11 login,to11,to11 linkorto11 ui; TO11_API_KEY, when it is set;- an API key stored by an older CLI.
TO11_API_KEY. to11 logout hands commands back to the key. to11 ui uses only the signed-in person.