Scope
dotagents._scope
Where a session's config lives, and where overlays come from.
-
Scope -- the stores a command writes to and a session reads. :class:
Scopeis theuserscope (<agents_dir>/, the configurable store:$AGENTS_HOME, default~/.agents) or aprojectscope (<project>/.agents/, which also walks the user store). An overlay installs into<store>/overlays/<name>/and skills publish into the shared<store>/skills/; installed overlays are discovered by their presence underoverlays/, with no registry file. :meth:Scope.pathsis the contract-A walk every reader (env,context, command discovery) goes through. :func:resolve_scopepicks a writing command's scope, :func:resolve_user_store, :func:project_root_defaultand :func:install_project_rootresolve its parts. -
Source -- where an overlay to install comes from. :func:
resolve_sourcereturns the repos in precedence order (--repo, the env repos, the project and user stores'dotagents.*registries); a repo is a directory of overlays, a registry file, or a git spec, and the first offering a name wins. See :mod:dotagents._sourcesfor the returned object.
The system store (:func:system_root_default: $AGENTS_SYSTEM_ROOT, or
/etc/agents on POSIX; only when administrators alone can write it) is
walked like any store, first in precedence, but nothing installs into it --
there is no --system scope for init / overlays.
AGENTS_DIR_ENV = 'AGENTS_HOME'
module-attribute
LEVEL_NAMES = frozenset({'default', 'overlay', 'system', 'user', 'project', 'project-root'})
module-attribute
SYSTEM_ROOT_ENV = 'AGENTS_SYSTEM_ROOT'
module-attribute
Scope(level, agents_root, *, user_root=None, project_root=None, system_root=None)
Where a session's config lives -- the one object every walk takes.
level is "user" or "project" and agents_root the scope's own
store (the install target of overlays add / init): <agents_dir>/
(the configurable store, default ~/.agents) for the user scope,
<project_root>/.agents/ for a project. A project scope also knows the
user_root (the user store -- every walk starts from it) and the
project_root; stores is the pair in precedence order, and
:attr:overlays is :meth:Overlay.installed over them (a same-named
project overlay shadows the store's copy). The user scope has one store.
:meth:paths is the contract-A walk for this scope; env, context
and cli._cmds_dirs all go through it.
The overlays/ and skills/ subdirs beneath agents_root are the
discover-and-publish surfaces of THIS scope (Overlay.discover(scope.overlay_root)).
A project whose .agents is the user store (the project root is ~)
is constructed as the user scope, keeping project_root for the
project-root level, so the store is walked once; :attr:stores also drops
any directory that appears twice.
Source code in src/dotagents/_scope.py
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
agents_root = Path(agents_root)
instance-attribute
global_scope
property
level = level
instance-attribute
overlay_root
property
overlays
property
Every overlay a session in this scope uses (:meth:Overlay.installed
over :attr:stores: the project's copy shadows a same-named store copy).
Memoized on the stores and their overlays/ listings: one env
run walks :meth:paths several times, and each rescan checked every
entry; an overlay added or removed since (overlays add reads this
again after installing) changes a listing and is seen.
project_root = Path(project_root) if project_root else None
instance-attribute
project_store
property
The project's store (agents_root) -- None in the user scope.
shared_skills_dir
property
stores
property
The stores in play, in precedence order: the system store (when there is one), the user store, then (in a project scope) the project's.
system_root = Path(system_root) if system_root else system_root_default()
instance-attribute
user_root = self.agents_root
instance-attribute
__repr__()
Source code in src/dotagents/_scope.py
278 279 280 281 | |
of(*, agents_dir, project_root=None, global_scope=False)
classmethod
The walk's scope from its parts: agents_dir (the user store),
project_root and global_scope -- what env / context build
after resolving each part (resolve_user_store,
project_root_default, -g).
Source code in src/dotagents/_scope.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
overlay_dir(name)
Source code in src/dotagents/_scope.py
275 276 | |
paths(*names, include_missing=False)
Resolve file paths across this scope's precedence hierarchy (Contract A).
Each name is a filename (resolved at every level) or a per-level
dict -- {"default": ..., "overlay": ..., "<level>": ...} -- where an
empty / missing entry skips that level.
Precedence order -- each of :attr:stores in turn, its overlays first,
then the store itself; finally the project root:
1. system overlays, then system (:attr:system_root)
2. user-store overlays, then user (:attr:user_root)
3. project overlays (<project_root>/.agents/overlays/<name>/), then
project (<project_root>/.agents) -- a project scope only
4. project-root (:attr:project_root) -- whenever there is one: a
project scope, or the user scope a project rooted at ~ becomes
An overlay installed in more than one store under the same name is one
overlay, the later store's: it SHADOWS the earlier copies entirely
(:meth:Overlay.installed), so its bin/lib/env/cmds/CONTEXT.md are the
only ones that resolve -- not stacked.
Each returned tuple is (level, path, root): for an overlay,
level is the overlay's directory name and root its directory;
for every other level root is None -- that is how callers tell
overlays apart. include_missing returns every candidate, else only
the ones that exist.
Source code in src/dotagents/_scope.py
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | |
store_level(store)
The contract-A level name of one of :attr:stores: system,
user or project.
Source code in src/dotagents/_scope.py
181 182 183 184 185 186 187 188 189 190 191 | |
filter_names(names, pattern)
Glob-filter overlay names (sync 'py*'). None/'*' keep all.
Source code in src/dotagents/_scope.py
542 543 544 545 546 | |
install_project_root()
The project root the WRITING commands (init, overlays,
findings) target when none is passed explicitly.
A pinned root ($AGENTS_PROJECT_ROOT, then the harness vars of
:data:_HARNESS_PROJECT_ROOT_VARS) is honoured only while the current
directory is inside it. A hooked session exports the pin once for its whole
life, so after cd ../other the pin still names the FIRST project; a
command that writes then targets where it runs -- the nearest ancestor of
the cwd with a .git or its own .agents, else the cwd -- and says so
with a warning. With no pin, the cwd.
env / context keep :func:project_root_default, where the pin wins
regardless of the cwd: what they read must stay stable across the
subdirectories a session's commands run in.
Source code in src/dotagents/_scope.py
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 | |
project_root_default()
The project root when none is passed explicitly, in precedence order:
$AGENTS_PROJECT_ROOT (dotagents' canonical var) -> a known agent-native var
(:data:_HARNESS_PROJECT_ROOT_VARS, e.g. Claude Code's CLAUDE_PROJECT_DIR)
-> the current working directory.
Emitting AGENTS_PROJECT_ROOT (see dotagents env) lets every command and
subprocess agree on one root regardless of the cwd it happens to run in.
Source code in src/dotagents/_scope.py
460 461 462 463 464 465 466 467 468 469 470 471 472 | |
resolve_scope(global_scope=False, *, agents_dir=None, project_root=None)
Pick the install scope.
-g/--global forces the user scope: agents_dir (--agents-dir)
if given, else the configurable store -- $AGENTS_HOME, then
~/.agents (:func:resolve_user_store,
the same chain env / context use, so a session that pinned the store
installs into it rather than into the literal home dir).
Otherwise the scope is project: agents_dir if given (the store root
override applies to either scope), else <project_root>/.agents where the
root is an explicit project_root argument, else
:func:install_project_root -- the pinned root ($AGENTS_PROJECT_ROOT,
then $CLAUDE_PROJECT_DIR) while the current directory is inside it, and
the project the current directory is in once it has left it. A project whose
.agents is the user store (the root is ~) is the user scope.
Source code in src/dotagents/_scope.py
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | |
resolve_source(repos=None, *, scope=None, logger=None, allow_empty=False)
Resolve where overlays come from: the repos in precedence order --
repos (--repo values), then $AGENTS_OVERLAYS_REPO_<KEY> by KEY,
then $AGENTS_OVERLAYS_REPO, then the project and user stores'
dotagents.{json,toml,yaml,yml} registries -- the first repo offering a
name wins. A repo is a directory of overlays, a registry file, or a git spec
(<repo>[@ref][#path]) that materializes to one; see
:mod:dotagents._sources. No build bundles overlays: they live on the
repo branch and are named as a repo like any other. Callers use only
available() / overlay_dir() / root. Raises when nothing at all
is configured, unless allow_empty.
Source code in src/dotagents/_scope.py
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 | |
resolve_user_store(agents_dir=None)
The USER store root, in precedence order: an explicit agents_dir
(--agents-dir) -> $AGENTS_HOME -> ~/.agents.
:func:resolve_scope defaults its -g store through this; env and
context -- whose Contract-A walk takes the user store as agents_dir
and the project root separately, and whose -g only skips the project
tiers -- call it directly, so the store never becomes the project dir.
Never logs or prints the raw env value (Leakage rule); only the resolved path is ever reported.
Source code in src/dotagents/_scope.py
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 | |
system_root_default()
The machine-wide store, or None when there is none to walk.
$AGENTS_SYSTEM_ROOT when set (it must be an absolute path), else
/etc/agents on POSIX and nothing on Windows -- there /etc/agents is
drive-relative (\etc\agents), and any local account can create it.
A root that does not exist is None too, so nothing of it (a
bin/ on PATH included) is walked. A root that a non-administrator can
write is skipped with a warning: its env.py and cmds/ would run in
every user's session. Resolved once per process and value.
Source code in src/dotagents/_scope.py
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 | |