Designing an Onion:
Domain-Scoped AI Context at Monorepo Scale
Na'aman Hirschfeld
Principal Systems Engineer @ ServiceNow (Armis)
WHERE WE STARTED
services/
libs/
frontends/
deploy/
data/
tools/
Product A's code
every other product's code
The question
Product B dev: “show me this customer's details”
Root skill fires
written when the repo held only Product A
Wrong system
queries Product A's system and data
Plausible answer
wrong product, no error, unseen in review
2
PRIOR ART
First try: a plugin marketplace per domain
26
marketplaces
130
plugins
1
enabled by default
3
THE GOAL
Maintain AI context as software
Versioned
Owned
Linted
Reviewed
Tested
Measured
Each session gets its domain's knowledge, and nothing else
4
THE DESIGN
The onion: each layer loads when work reaches it
Load behaviour probed with a unique sentinel token per file, Claude Code 2.1.306.
Repo root
every session start
Standards, a products map, shared skills, generic subagents, the skill index
Area folder
on first Read inside
Its CLAUDE.md, rules and skills, all at once
Service folder
on first Read inside
The same, one level deeper; the closest CLAUDE.md wins, the root still loads
The file
glob-matched rules
Only the rules whose glob matches this file
Grep and Glob load nothing
5
WHERE THINGS LIVE
The repository tree, before and after
Before
repo/
├─ CLAUDE.md
product terms
├─ tools/
│ └─ marketplaces/
26 of them
│ ├─ team-a/plugins/
skills, agents
│ ├─ team-b/plugins/
commands
│ └─ …
├─ services/
│ ├─ a-api/
no context here
│ └─ b-ingest/
or here
└─ frontends/
~/.claude/settings.json
per engineer
After
repo/
├─ CLAUDE.md
standards + map
├─ .claude-plugin/
8 plugins
│ └─ marketplace.json
one marketplace
├─ .claude/
│ ├─ settings.json
declares it
│ ├─ rules/
glob-scoped
│ └─ skills/ agents/
shared, generic
└─ services/
├─ CLAUDE.md
area guide
└─ a-api/
├─ CLAUDE.md
service guide
└─ .claude/
rules, skills
6
THE LAYERS
Nested layers load on first read
Load behaviour probed with a unique sentinel token per file, Claude Code 2.1.306.
Session start
root layer only
Grep / Glob
nothing nested loads
First Read in a/
the folder is touched
Layer loads
its CLAUDE.md, rules, skills
Subagent reads it
gets the same layer
Probe, don't trust the docs: a unique sentinel token in every file, one question per session
7
THE LAYERS
Rules, skills and what stays at the root
Rules
Always-on, or glob-scoped with paths in the frontmatter. Loaded automatically when a matching file is read.
Skills
Loaded on demand: the model matches the description, or you type /name. Only name and description sit in context.
Gated skills
paths: hidden and uncallable until a matching file is read. disable-model-invocation: slash command only.
Slash commands
A nested skill is unknown until its folder is read, so commands needed from the root live in the root skills.
Root: global skills, plus the skills that must be root slash commands. Everything product-specific: nested, next to its code
8
DISCOVERY
Dynamic skill discovery
ai-rulez: open source, by the speaker — https://github.com/Goldziher/ai-rulez
The problem
The fix: an index over MCP
Task
“details for this customer?”
find_skill
ranked, scored matches under the product's path
Choose
the best match for the task
load_skill
only that skill enters the context
9
EVALS I
Two layers of tests: static and dynamic
Static analysis using ai-rulez
free · offline · in CI
Dynamic evaluation: AI as a judge
local · tens of dollars a run
Tune on one split of the cases; a held-out split gives the verdict
10
EVALS II
The static layer is ai-rulez
ai-rulez 5.10.0, pinned in CI and in the harness; a test fails if the pins diverge.
ai-rulez exposure
what a session sees at start, in tokens per phase
exposure leakage
product terms at start; a CI gate that exits 2
exposure collisions
skills in different areas with overlapping listings
ai-rulez validate
the same rules as findings, in CI
areas.toml
the area map: 19 areas with paths, terms and ambiguous words
validate findings
A product term at session start (error)
An ambiguous word with no product term beside it
An acceptance that accepts nothing
7 → 0
product terms at start, kept at 0 by CI
8 accepted, each with a written reason
11
EVALS III
Why trust the numbers
Example case
prompt: show me this customer's details
asker: Product B developer
must_use: b-customer-lookup
must_not: any Product A skill
behaviour: answer, or ask which product
criteria:
- customer means a Product B account
- never uses Product A's records
Regex checks
skill loaded · no forbidden term · command ran
Judge grades
is the answer right, per this case's criteria?
κ 0.669
agreement beyond chance, answers
κ 0.847
agreement beyond chance, criteria
86%
broken answers caught
93%
good answers passed
Decision rule and a 6-read holdout budget, fixed in advance · Sonnet sessions, Opus judge
12
RESULTS
What we tried: a staircase of results
Held-out: 53 cases, two runs each; user-specific context covers 39 of them. Wrong-product answers rest on 5 cases.
1
Wording tweaks
read the guide first, hoist shared skills
no gain
2
Start-hook skill list
a cross-product menu, +16k start tokens
dropped
3
Scoped discovery
+31 recall (tune split); small on held-out
kept
4
User-specific context
developer’s product, confirmed once, in a local file
opt-in
master
layered design
+ user context
Pass rate
39%
81%
96%
Right skill found
22%
72%
94%
Wrong-product answers
70%
0%
0%
/init-command saves a developer’s product, in a git-ignored CLAUDE.local.md loaded at every session start
13
Thank you
Na'aman Hirschfeld
Principal Systems Engineer @ ServiceNow (Armis)