AGENTS.md, .agents/skills/ and .mcp.json, with .outerlayer/ for its policy. A repository that has not moved to that layout commits .outerlayer/ and the files outerlayer context emit writes from it.
A factory can instead name one repository as its control plane. A factory’s control plane is one of its linked repositories. It governs every other repository the factory links. A laptop session gets its instructions at launch from a copy kept outside the repository. As a result, nothing from the control plane is written into the working tree or committed there.
A control plane governs the factory’s repositories in every GitHub organization the factory links, not only its own. A control plane in acme governs a repository in acme-labs. OuterLayer reads the control plane through the installation that covers acme. It reads each governed repository through its own organization’s installation. See Repositories from more than one GitHub organization.
Use this when you run several repositories and want one place to change how agents behave and what evidence they must produce.
The dashboard, CLI messages and API errors all call it the control plane. The API path still says
context-source, and older versions of the dashboard said context source.
Pull request checks and work item evaluation use the control plane of the factory the repository belongs to. A control plane in another factory never governs it.
A factory with one repository and no control plane named checks each pull request under the policy in its own repository, at its base branch. The control plane itself is checked the same way.
The default control plane
A factory with several linked repositories and none named has its first-linked repository as its control plane, read at that repository’s default branch. First-linked means the repository whose link to the factory is oldest. A repository that is unlinked and linked again counts from its new link. No step is needed. Linking a second repository makes the first one the control plane. Unlinking the default control plane makes the next first-linked repository the default. A factory with one repository is its own control plane and builds as before. The Repositories list marks a control plane chosen this way with a Default control plane badge. An admin can name another repository as the control plane, as described below. A named control plane replaces the default. In such a factory, the other repositories stop reading their own.outerlayer/ folder. They follow the control plane’s instructions and checks. A build’s image and setup commands follow it too. See Build a repository’s image from its devcontainer file.
Set the control plane
Link the repository to the factory first, as you would any repository. Then open the factory’s Settings → General. In the Repositories list, choose Use as control plane on the repository’s row. The dialog asks you to confirm by typing the name. The control plane is read from the branch that row links. The row of the control plane then carries a Control plane badge, and every other row offers Use as control plane. To remove it, choose Stop using as control plane on its row. Setting the control plane takes thegovernance.insert permission, and removing it takes governance.delete. Through the API, a repository the factory does not link is refused with repository_not_linked. A factory has at most one control plane. Choosing another repository replaces the one in place, and the dialog names the one it replaces.
Unlinking a control plane you named removes it. The factory then uses its first-linked repository as the control plane, or none when only one repository is left. Renaming the repository on GitHub does not: it stays the control plane under its new name.
Linking a repository puts it under the control plane. Nothing else needs to name it. The gateway does not read a governs list to decide this. The control plane itself is not governed: it keeps its own instructions.
OuterLayer stores, for each linked repository, whether it is governed and the commit of the control plane’s branch that answer was read at. It refreshes that when you set or remove the control plane, and when you link a repository. It also refreshes it when someone pushes to the control plane’s branch, and every 15 minutes. If the control plane cannot be read, a governed repository stays governed and keeps the commit it had.
The
governs list is ignored. A governs list in the control plane’s .outerlayer/config.json decides nothing, and you do not need to keep one. outerlayer doctor, run in the control plane, warns when one is left. CLI versions before 0.17.0 still read it, so delete the list once every machine and runner runs 0.17.0 or later.What a governed repository commits
A governed repository keeps its own instructions next to the shared ones. A governed repository commits its ownCLAUDE.md, AGENTS.md and skills. Claude Code reads them together with the control plane’s.
One rule protects the shared skills. A repository cannot replace a shared skill. If it commits a skill with the same name as a shared skill, in .claude/skills/ or .agents/skills/, outerlayer claude refuses to start. The message names the folder where the repository keeps the skill, and it names .claude/skills/ when the skill is in both. Rename or remove the repository’s skill.
Earlier CLI versions wrote .outerlayer-pin.json at the repository root and asked you to commit it. The launcher and the session hook do not read it. A governed repository can delete .outerlayer-pin.json. A repository that no longer carries it shows no trace of its control plane.
A governed repository commits its own AGENTS.md or .claude/, or deletes .outerlayer-pin.json, only after everyone who works in it has upgraded the CLI. A CLI from before this change still refuses a repository that commits its own AGENTS.md or .claude/, and that repository’s sessions then get no control plane.
What a laptop session does in a governed repository
A laptop session gets the control plane throughouterlayer claude. It starts Claude Code, and passes the caller’s arguments on unchanged:
~/.outerlayer/context/<owner>/<repo>/<sha>/. It starts Claude Code with that directory added, so the agent and every subagent follow the shared AGENTS.md, skills, agents and hooks. A session therefore reads the same commit that the pull request check later judges its work against, not the branch’s newest commit. The launcher records the copy it used in the repository’s git directory, which is not part of the working tree.
It copies only four paths from the control plane, as committed there: AGENTS.md, everything under .claude/, everything under .outerlayer/, and everything under .agents/. A root CLAUDE.md, .cursor/ and .mcp.json are not copied. So a governed repository gets Claude Code’s skills and commands, the skills the control plane keeps in .agents/skills/, and the shared AGENTS.md. It does not get Cursor’s skills or any MCP servers. It also adds the five maintained skills at the installed CLI’s version.
In three cases, outerlayer claude starts Claude Code with your arguments alone and makes no network call. It does this outside a git repository, in a repository that no control plane governs, and when you are not signed in.
If OuterLayer cannot be reached, the launcher starts with the last copy it stored and says that copy may be out of date. With no stored copy it stops, and says the control plane could not be fetched. It does not start a governed session without the shared rules.
outerlayer init can make plain claude do the same. It asks whether to add a claude function to your zsh or bash startup file. The function runs outerlayer claude with the same arguments. Run outerlayer init --shell-function to add it without being asked, or outerlayer init --no-shell-function to remove it. The function exists only in an interactive shell.
Sessions that do not get the control plane
The Claude desktop app, IDE extensions, cloud sessions, and scripts that runclaude directly do not get the control plane. They do not start through a shell that has the function.
A session started that way says so. The session-start hook shows a message that the shared instructions and skills are missing, and names outerlayer claude.
A script gets the control plane by running outerlayer claude. Give it the arguments you gave claude.
Files an earlier CLI copied
Earlier CLI versions copiedAGENTS.md, .claude/ and .outerlayer/ into the working tree. At the next session start, and at the next outerlayer claude, the CLI removes exactly the files its record of those copies lists. It also removes the exclude block it added to .git/info/exclude, and the pre-commit guard it installed. Any other file under .claude/, such as settings.local.json, stays, and so does any other pre-commit hook. When an earlier CLI rewrote the committed .outerlayer-pin.json and the change is not staged, the file is put back to its committed version. A staged change to it, or one that is not in the pin’s format, is left alone.
What a build does in a governed repository
A container or microVM build of a governed repository does not write the instructions into its checkout. The runner delivers them to a directory in the build user’s home, outside the checkout.claude in the build runs outerlayer claude, which starts Claude Code with that directory attached. The agent and every subagent it starts follow the shared AGENTS.md, skills, agents and hooks. A command in your runner config, such as claude -p "Build the issue", needs no change. See Container builds.
Because nothing is written into the checkout, nothing from the control plane can be committed by the build.
A repository cannot replace a shared skill in its own builds. If the repository has a skill in .claude/skills/ or .agents/skills/ with the same name as a shared skill, the build is refused before the agent starts. The message names the folder where the repository keeps the skill, and both skills. Rename or remove the repository’s skill.
A hook in the control plane must reach its scripts through ${OUTERLAYER_CONTROL_PLANE_DIR:-$CLAUDE_PROJECT_DIR}/.claude/…, because the control plane’s .claude/ is not in the build’s checkout. Delivery also rewrites $CLAUDE_PROJECT_DIR/.claude, "$CLAUDE_PROJECT_DIR"/.claude and ${CLAUDE_PROJECT_DIR}/.claude. Run outerlayer doctor in the control plane to name any hook that reaches .claude/ another way.
Keep skills in .agents/skills/
A control plane may keep its skills in .agents/skills/. Codex and other tools that follow the SKILL.md standard read that folder. Claude Code looks only in .claude/skills/, so the CLI delivers each skill from .agents/skills/ into the delivered .claude/skills/ under its own name. A skill is copied whole. When the control plane has a skill of the same name in both folders, the .claude/skills/ copy is delivered. No file from the other copy is mixed in.
A path under .agents/ in a skill is never rewritten, so a skill should name its own files relative to its folder, or with ${CLAUDE_SKILL_DIR}. In the control plane’s AGENTS.md, a path to a file under .agents/ that the delivery holds names that file in the delivery directory. A path to a folder, or to a file the control plane does not deliver, keeps naming the checkout.
A control plane that keeps skills in .agents/skills/ can declare "layout": 2 in .outerlayer/config.json. A control plane with no layout key is layout 1. A control plane that declares any other layout is refused, by outerlayer claude and by a build, with a message that names what the file says. For a layout newer than 2 the message says the CLI needs upgrading. For anything else, such as 0, 1.5 or "two", or a config.json that is not valid JSON, it says to fix the file. A build ends with context_layout_unsupported.
Move a control plane without breaking older CLIs
A CLI or runner older than the first release that reads.agents/skills/ delivers only .claude/skills/. It gives no warning for a control plane that keeps its skills elsewhere. A control plane can move without breaking those CLIs and runners if it keeps both folders for a while.
- Keep
.claude/skills/as an identical copy of.agents/skills/. Older CLIs deliver it, and newer CLIs prefer it. - Make every edit in both folders. An edit to
.agents/skills/alone reaches nobody, because the.claude/skills/copy wins. - Delete
.claude/skills/once every CLI and runner your team uses runs the first release that reads.agents/skills/. That release is 0.21.0.outerlayer runner statusshows a runner’s version. The control plane’s owner decides when.
A repository’s own skills
A repository can keep its own skills in.agents/skills/ too, in a control plane or in a repository that no control plane governs. See Agent context.
Set a check’s level everywhere at once
Give a check a level in the control plane’s policy, and it applies to every repository it supplies. This turnscode-review off everywhere:
Manage it through the API
The factory’s settings page calls/v1/orgs/{orgName}/apps/{appId}/context-source, and a script can call it too. {appId} is the factory’s id.
A signed-in member’s session works on every plan. A management API key works on an Enterprise plan. A factory’s own API key never can. See Organization API for keys and permissions.