The problem

The same agent, the same machine, the same person typing. In one repository it picks up the review skill, a file of instructions the client feeds into the session on its own, and uses it. In the repository next door it ignores the skill entirely and writes its own version of the rules.

Nothing in a chat window explains that. The difference lives in four places at once, and none of them is visible from inside the session:

scope            the skill is global, or installed into one folder only
precedence       two clients read the same directory under different rules
instructions     CLAUDE.md names a skill, or names one that does not exist
launch directory skills are read relative to where the agent started

A project in Onplate is a folder of code on the local machine. Adding it installs nothing, creates no files, makes no commit, and sends nothing outside. It is a note that says “I work here”, and everything below is read from the folder that note points at.

What the agent receives there is read from the folder itself and from one directory outside it:

<project>/
├── CLAUDE.md, AGENTS.md    the agent reads these before any skill
├── .claude/skills/         project resources, read if the agent starts here
├── .agents/skills/         one copy, read by both Codex and Cursor
├── .mcp.json               project MCP servers
└── .onplate/profile.json   not read by the agent: what the team agreed on

~/.claude/skills/           global resources, in every project of that client
        |
        v
what the agent gets in this folder
  always in context       spent on every request
  maximum on activation   added only when a resource fires

Every row but the last one belongs to this folder, and that is the whole reason two repositories on one machine can behave differently.

This article follows the four places in the order they break, then ends with the comparison screen that answers the original question directly.

Three shapes, and a fourth answer

Not every directory has the same shape, and the shape decides where a resource belongs:

Shape What it is How it differs
A single code folder an ordinary repository one part, one profile
A monorepo one .git with backend, frontend, and so on inside parts are listed, one branch for all, one profile
A workspace folder an empty folder with several repositories cloned into it every part has its own branch and its own profile

There is a fourth answer, and it is the one people are surprised by: this looks like a folder of projects, not a project. That is what Onplate says about ~/code holding a dozen independent repositories. Adding it as one project would mean one profile and one card describing everything on the machine at once.

The shape is decided by facts, whether a part has its own .git and what markers the code leaves behind, not by folder names.

Act 1: what the folder says before any setup exists

The Overview tab is readable on a project that was added a minute ago and configured never. Sections with nothing to say collapse into a single row with their own summary: “the tracked project state has not changed”, “in sync with the profile”, “Git is not available on this machine”. A section that failed or is still loading never collapses, because that one does have something to say.

The top block is the setup status, and it is a task list rather than a verdict:

State What it means
Synced the folder, the profile, and the AI deployments agree
Needs action there is something to review
Blocked the folder is unavailable, the profile does not parse, or a conflict was found

“Needs action” is the normal state of a living project. Each entry carries a concrete next step (“Check again”, “Open folder”, “Create profile”, “Review the profile”, “Review local setup”) instead of a warning that leaves the reader to guess.

One row on that list is worth more than the rest: a resource that has not been invoked in 30 days, shown together with the price of keeping it, in tokens of persistent context spent on every agent run. That pairing is the whole argument. An unused skill is a shrug; an unused skill costing tokens on every request is a decision.

Below the status is what the project actually is. Each part gets a row with its path from the root, its branch if it has its own repository, the stack facts, and the commands to start it. Every fact carries its evidence: the language, framework, image, service, and port chips each name the file and line they were taken from.

That is not pedantry. Somebody who catches the tool inventing a fact about their own code will stop trusting its security verdicts too, which is why there is no “probably” here and nothing is listed as a guess. When nothing was found, it says so.

Launch commands are collected from docker-compose.yml, Makefile, package scripts, and only then the README, in that order of reliability, each labelled with its purpose and its source file. Disagreements are not resolved for you: if the Makefile says one thing and the README another, both are shown, and often that is the most useful fact on the screen, because the README is out of date.

Commands can be copied and are never run. Risky ones are marked “the scanner is wary” or “the scanner says no”.

Act 2: what reaches the model in this folder

The context block answers one question per client: what does the model actually receive when it starts here? Inside the selected client the answer splits three ways:

instructions        CLAUDE.md, AGENTS.md, and what their @-imports pull in
project resources   installed into this folder
global resources    installed into the client, visible in every project

always in context      spent on every request
maximum on activation  added only when a resource fires

Each row carries its source and its price in tokens. Instruction files also carry how many files their @-imports pull in and how many of those references are broken.

The two numbers in the header stay apart on purpose: adding them produces a total that never happens.

The origin split matters more than the count, and the Resources tab makes it the primary axis:

Group What it is
Installed through Onplate entries it knows, so they can be updated, replaced, removed
Arrived with the code sits inside the project folder, not installed by Onplate, read by the AI just the same
Shared by the AI tool lives in the client’s home directory, applies to every project it opens

The middle group used to be shown nowhere, and in an ordinary repository it is almost all of the content. A find in it can be adopted into storage: the file stays where it is, and only what Onplate knows about it changes.

The two install scopes are the thing worth understanding in this section:

Project resource Global resource
Where it is visible in this folder only in every project of that client
Who decides you, or the project profile you, once
Does it travel to the repository yes, if the profile includes it no, unless picked explicitly

A Go review skill belongs in the backend repository only. A skill encoding a personal commit style belongs globally: it is needed everywhere and the team does not need it at all.

Act 3: why the agent can see this skill at all

There are four reasons a resource is available here, and they are named rather than summed:

installed into the project folder   it sits inside this folder
available through AI                global, visible in every project of the client
named by instructions               CLAUDE.md mentions it, or pulls it in with @
delivered by a package              installed together with others, not on its own

Above the list is the count that makes it checkable: how many instruction files were read, how many resources are project-scoped and how many global, how many direct references exist, and how many of those are broken. A broken reference is a mention pointing at nothing, seen from the resource’s side instead of the file’s.

This is a map of availability and declared links. It is not an invocation log, and it does not claim that one resource starts another. Actual invocations are counted in exactly one place, on the Sessions tab, and this block points there with a number instead of repeating the same table under a different heading.

If the instruction files could not be read, the count of failures is named. Reporting “no relations” for a file that failed to open would be the same kind of lie as reporting zero usage for logs that were never found.

Act 4: skills are read relative to the launch directory

This is the quiet one, and it costs the most time. In a monorepo, a skill installed into frontend is read when the agent starts in frontend. Start it in backend, which is where the work usually happens, and the skill is not read at all:

monorepo/
├── backend/            the agent is started here, 40 sessions in 30 days
│   └── .claude/skills/ read
└── frontend/
    └── .claude/skills/ on disk, never read from backend

Nothing reports an error. The install succeeded, the file is valid, the path is right, and the model simply never sees it.

The install dialog warns about this, and only when the warning can be earned: some part of the project collected invocations over 30 days and the folder you picked did not. The warning names that part and offers to switch in one click. With no launch history at all it stays silent, because there would be nothing to compare against.

Installing into a quiet folder is a legitimate choice. It may be prepared in advance for work that has not started. The warning only states the condition: the skill will be read when the agent starts from that folder.

Two more things are visible before anything is written: the full path of every file that will appear on disk, and how many tokens the install adds to the context of every agent run. What cannot be installed is named with its reason instead of being skipped: the client has no such place inside a project, the scanner says no and a confirmation is required, or the destination folder already holds foreign files.

Instructions: the file the agent reads before any skill

CLAUDE.md and AGENTS.md are the third place project context lives, alongside skills and MCP servers, and the agent reads them before any skill. They are found in the project root and in its parts, and each one is broken down:

What you see Why it is there
Mentioned skills and subagents with their state “sits in the project”, “sits in backend”, “global”, “in the catalog but not installed”, “found nowhere”
Invocations of each mention mentioned in the instructions and never fired is a reason to rewrite the description
@-imports and their size these spend tokens every session, and they are visible nowhere else
Onplate blocks that paragraph was written by a package install, not by a person

The breakage this was built for is the quietest one available: the instructions point at a skill that does not exist. The agent reads the name, does not find the skill, and invents its own version of what it was supposed to do. Those mentions are marked in the text and in the list.

A file named like CLAUDE.local.md gets a “personal” badge. It is usually in .gitignore, so a colleague on the same repository does not have it, and a rule that exists only on one machine explains a whole class of “it works for me”.

The file can be edited in place and saved back into the project. No copy is kept: a second source of truth about text edited daily in somebody’s editor would be worse than no copy at all.

Sessions: what actually fired

Local agent sessions are matched to a project by their recorded working directory. A session shows its title, the client, the date, the model, the number of turns, and which resources were verifiably invoked, with the evidence for each invocation named:

Claude Code   a recorded Skill or Task call
Codex         a SKILL.md opened from a known skill directory
OpenCode      a recorded skill call

Three limits are stated rather than smoothed over. Conversation text is never stored, only metadata is counted. MCP calls are left out where the source does not allow identifying them reliably, instead of being guessed. And zero activity is reported as “no supported logs were found”, because a zero would answer a question that was never measured.

A session that started from another folder can be associated by hand, and one that does not belong can be marked as such. Both decisions stay on this machine.

A project-scoped skill with no invocations is the single most useful row on this tab. Either its description never catches the agent, or it was installed at the wrong scope, or the work happens in a different part of the repository. All three are fixable, and none of them is visible anywhere else.

The project profile

Everything above is local. A profile is the part that survives a new laptop and a new colleague, and it lives next to the code:

.onplate/
  profile.json                      manifest: name, version, checksum, targets
  README.md                         for people who do not use Onplate
  files/skills/go-review/SKILL.md   skill bodies as separate files
  files/agents/db-migrator.md

Skill bodies are separate files on purpose. A manifest with the instructions inlined produces a diff nobody reads, and the entire point of putting this in the repository is that the decision gets discussed where every other decision about the project is discussed, rather than in a chat thread that will be forgotten.

How many profiles a project has follows from its shape:

Shape Profiles Where
A single folder one the repository root
A monorepo one the root, and entries can name parts
A workspace with clones one per clone each repository root

In a monorepo that last column is the useful one: the migrations skill is written into the profile as belonging to backend, not to the root, which is the same launch-directory fact from Act 4 in a form the team inherits.

In practice the round trip has two halves. One developer sets the project up locally, installs a review skill, a database MCP server, and a subagent for tests, adds them to the profile, and sends the .onplate folder as an ordinary pull request. A colleague pulls, opens the same repository, and presses “Review what to install”. Before anything is applied the exact list is visible, and the secrets for the MCP server are entered separately and locally. Both end up with the same reproducible setup, and their personal global skills stay personal.

Four rules keep the file safe to commit. Secrets are stored as names only, never values. No executable content is allowed: skills, servers, and subagents only. A newer version is never rolled back automatically, and a divergence produces a new version rather than overwriting one. And no commit is ever created for you, no fetch is ever performed: after the profile is written, reviewing the diff, committing, and pushing stay a person’s decision.

The reverse direction is the same button with the opposite meaning. “Install settings from the profile” applies what the team agreed on, and shows the plan before touching a file. “Add local settings to the profile” offers the team what already works locally, which is how a personal setup usually becomes a shared one.

Team registries and roles

A project profile describes one repository. When the set of resources is common to a whole team, its source can be a separate registry repository listing roles and the packages each role needs.

A role is selected along with a synchronization mode: disabled, notifications only, or automatic installation of what is missing. The report names four differences rather than one status: what is absent, what is stale, what is installed outside the profile, and what the registry does not have. Several registries can be configured and switched between explicitly.

Project launch modes

An agent session is defined by more than its resources: the model, the reasoning effort, the permissions, the tools, and the visible directories. A project tends to repeat a few combinations, such as exploring the repository, everyday implementation, and a one-off task with wider permissions.

Onplate calls a combination a mode. Every project has starter modes and can define its own. Before launch the exact command is shown, along with the resources entering context, the selected permissions, and a compatibility table stating which settings the chosen client understands. Launching opens a session without modifying files; persisting the settings into a client config is a separate action with a visible diff and a copy of the previous file.

What was configured and what actually ran are displayed apart, because they are known differently. The number of sessions Onplate launched itself is exact, since it opened them. Which models did the work is read out of the clients’ own local journals, and the model name there is whatever the client wrote down.

Custom modes export to .onplate/modes.json and travel as an ordinary pull request. Modes arriving from a repository are not activated automatically, and anything that widens permissions, such as running without confirmation or access outside the project, requires separate consent.

Comparing two projects

With two projects selected, the comparison answers the question this article opened with. The header holds both sides: how many resources, how many instruction files, how many tokens are always in context, and how many items need attention. Below is a row-by-row comparison, differences first, matches behind one button.

A row is a kind plus a name, not an install. One skill has as many installs as it has clients, and the question being asked is “is it there”, not “how many times”. Where several installs disagree, the worst status wins, because “broken somewhere” is the answer.

Four kinds of difference are named in words: installed into different clients, different versions, something wrong with the install on one side, and noticeably different weight in context. That last one has a threshold of 200 tokens, and the threshold is the interesting part. Two living CLAUDE.md files almost never match byte for byte, so flagging a ten-token gap would fill the screen with rows that explain nothing.

Instruction files are compared by their path from the project root, since absolute paths differ between two projects by definition. Global resources are left out entirely: they are identical for any two projects on this machine, so a “both have it” row would be noise, and the token count therefore covers the project scope and the instructions only. The screen says so instead of leaving the number unexplained.

How to debug “the agent behaves differently here”

  1. Compare the two projects before reading either one. The screen shows only differences, and four named kinds cover most of what goes wrong: scope, version, a broken install, and context weight.
  2. Check which folder the agent starts in. Invocation counts per part are shown next to each part. A skill installed into a part with no sessions is on disk and out of reach.
  3. Read the instruction files for mentions that point nowhere. A named skill that does not exist is worse than no mention, because the agent fills the gap itself.
  4. Separate availability from usage. The relations map says what could be reached; the Sessions tab says what was. A resource connected three ways that never fired once is exactly the combination to look for.
  5. Check the origin, not the count. “Arrived with the code” is the group that explains most surprises in an unfamiliar repository, and it is the one nothing else reports.

Pitfalls

A correct install can be unreachable. Skills are read relative to the launch directory, so frontend and backend are different configurations in one repository even though they share a .git.

A personal instruction file explains a missing bug. CLAUDE.local.md is usually gitignored, so a rule that exists on one machine will not be found by anyone comparing the repository.

A profile does not require global resources, by design. A global row marked “not in project profile” is not a defect to be fixed, and reading it as one leads to committing personal preferences to a shared repository.

Counters can be silent, and silence is the honest answer. If a calculation failed, no number is shown. “0 skills” and “we could not tell” are different statements, and only one of them is safe to act on.

A folder of repositories is not a project. Adding ~/code produces one profile and one card for a dozen unrelated codebases, which is why Onplate suggests adding them separately instead.

“Forget” is not a cleanup. It removes the project from the list along with the local decisions about its profile. The folder, the profile, and the installs stay exactly where they are.

Takeaways

  • What the agent receives in a folder is not one setting. It is the product of scope, client precedence, the instruction files, and the directory the session started in, and any one of the four can make a correct install invisible.
  • The most useful grouping is by origin, not by kind. “Arrived with the code” is most of the content of a real repository and is the group that explains why an agent behaves unexpectedly in an unfamiliar project.
  • Availability and usage are different questions and deserve different screens. A resource can be connected three ways and never fire once, and only a log answers that.
  • A profile is worth committing because of the review, not the automation. .onplate/profile.json with the skill bodies in separate files produces a diff a colleague can actually read in a pull request.
  • Every number that could be a guess is either evidenced or absent. A stack fact names its file and line, an invocation names what confirms it, and a failed calculation shows nothing rather than a zero.

The chain is short and it breaks in the middle. A skill is installed into a folder, the client reads that folder when a session starts there, the instruction files name what the agent should reach for, and the model uses whatever survived all three steps. The comparison screen exists because the failure is almost never in the skill. It is in which of those steps happened in this folder and not in the one next door.


Onplate: onplate.tsymbal.dev · documentation