Commands
The dotagents CLI is an umbrella of subcommands. Run it as the installed
dotagents wrapper, as python -m dotagents, via the python install.py <cmd>
dev shim (from a source checkout), or from a built dotagents.pyz. Most commands
take a scope flag: project by default or user with -g / --global (the
~/.agents store, configurable with $AGENTS_HOME).
Which project, for the commands that write (init, overlays, findings): a pinned
$AGENTS_PROJECT_ROOT (then $CLAUDE_PROJECT_DIR) while the current directory is
inside it; outside it, the nearest directory up with a .git or its own .agents,
else the current directory, with a warning; with no pin, the current directory — so
run them from the project root. env, context and launch always use the pinned
root when there is one (else the current directory), so what they assemble is the
same in every subdirectory. --agents-dir names the store the scope resolves to: the
project store for init / overlays / findings without -g, the user store
otherwise.
Command set
| Command | What it does |
|---|---|
init |
Lay down the neutral base config; block-merge AGENTS.md (its first line names the file's own path); wire the agents' includes and hooks; --bin-dir also writes a PATH wrapper. |
overlays |
Manage opt-in overlays by name: add / remove / list / sync / show. |
context |
Assemble the effective context for one or more agents. |
env |
Assemble the chained env-file layers + identity vars, in a chosen format. |
path |
Print the bin dirs env puts on PATH (or, with --lib, the lib dirs it puts on PYTHONPATH). |
build-pyz |
Build the self-contained dotagents.pyz zipapp (from a source checkout). |
about |
dotagents-cli <version>, then the packages bundled in the .pyz (or installed beside a plain install) with their versions. |
findings |
Per-scope findings queue: add / list / show / done / reopen / remove / index / path. |
launch |
Start an agent's CLI with the env exported and the context handed over; everything after -- goes to the agent. |
That table is the whole shipped surface: six built-in commands and two bundled
command modules, findings and launch (discovered from the package itself). Every
other command is discovered from a directory of command modules. The sources, in
order — a later one overrides a same-named command from an earlier one:
- the built-in commands;
- the bundled
findingsandlaunch; - store by store — the system store, the user store, then (in a project) the
project's
.agents/— each store's installed overlays'cmds/first, then the store's owndotagents/cmds/; - the directories in
$AGENTS_CMDS_PATH(os.pathsep-separated); --cmdspath <dir>(repeatable).
So a command an overlay ships is overridden by one in the same store's
dotagents/cmds/, a user-store command by a project one, and any of them by
$AGENTS_CMDS_PATH or --cmdspath. A checkout's own top-level dotagents/cmds/ (outside
.agents/) is never a source. Two consequences worth knowing:
link-project/sync-project(the per-project private-store workflow) come from the opt-inprivate-syncoverlay, which ships the commands and their logic together — install it and they appear (see link-project / sync-project).- A personal command module dropped into a scope's
dotagents/cmds/is discovered like any other, so tooling you keep private never has to live in this repo.
Your own commands work the same way: drop a *.py defining a duho command class
into <store>/dotagents/cmds/ (create the dir — init does not) and it becomes a
subcommand, no registration needed; see
Authoring → Custom commands.
As tools for an agent
Every command above except env, and every discovered one, is also an MCP tool.
Start dotagents with DOTAGENTS_MCP=stdio in its environment and it serves that
command set over stdio instead of running a command:
DOTAGENTS_MCP=stdio dotagents # register this as a stdio MCP server in your agent
- One tool per leaf command, named with dots:
dotagents.findings.add,dotagents.overlays.list, and for an overlay's commandsdotagents.<command>. - A tool's input schema is generated from the command's fields; its result is what the command prints.
- The command set is discovered once, when the server starts, for the directory it starts in. Restart it after installing an overlay or changing project.
Use it for a command an agent calls many times in a session — a findings queue, a
plan's progress, a lookup. A tool call skips the interpreter start-up and the
command discovery that every dotagents <command> process pays, and its arguments
are typed values, not strings quoted through a shell.
env is not served: it prints resolved values, which can be secrets. Everything
else is, including the commands that change the store (init, overlays add,
overlays remove). Register the server only where the agent is meant to be able to
run them.
For a machine-readable description without a server, set AGENT_HELP=1 and ask for
--help: dotagents findings --help then prints one JSON document for that command
tree. It is complete and long; a single subcommand's plain --help is shorter when
that is all that is needed.
init
See Install for the full walkthrough. In brief:
dotagents init # base config into ./.agents (project scope)
dotagents init -g # ...into ~/.agents (user scope)
dotagents init --bin-dir ~/.local/bin # also write a `dotagents` command on PATH
dotagents init --no-hooks # skip agent hook wiring + the skills links
dotagents init --no-venv # skip the store's Python venv (duho for skills)
dotagents init --agents claude,codex # exactly these agents (default: detected + claude)
dotagents init --dry-run
init also creates or updates the store's Python venv, where skills run (see
the scope venv). Offline, or
with no pip to be had, that is a warning; overlays sync retries.
Hook wiring
For each active agent, init adds what that harness needs to load the config, and
--no-hooks skips the hooks and skill links. Every merge is additive and idempotent:
unrelated settings keys and hooks you wrote yourself are preserved verbatim, a hook
that is already what init would write is left alone, and re-running init writes
nothing. Each hook runs dotagents by path — the project's .agents/bin wrapper, else
the store's, else dotagents on PATH.
Claude Code. Hooks go into settings.json in Claude's config dir
($CLAUDE_CONFIG_DIR, else ~/.claude) for the user store, and into the project's
.claude/settings.local.json — the uncommitted file — for a project store.
| Hook | Command | Why |
|---|---|---|
SessionStart |
writes dotagents env --diff --format export into $CLAUDE_ENV_FILE (--into: one block, replaced on every run; PATH as a prefix on the shell's own), then runs dotagents context |
Claude sources $CLAUDE_ENV_FILE before each Bash command, so the env layers reach every command in the session; and it injects the hook's stdout into the session context, which is how the assembled context reaches the model. |
CwdChanged |
when the new directory has a .agents/, writes export AGENTS_PROJECT_ROOT=<it> into $CLAUDE_ENV_FILE, replacing the pin an earlier cd wrote; then prints the directory's AGENTS.md if there is one |
Re-pins the project root after a cd into another project, and surfaces that directory's AGENTS.md. |
On Windows each event gets a second, PowerShell-native handler (shell:
"powershell") that runs only when Claude Code would find no Git Bash, so a machine
with Git Bash does not get the context twice. The PowerShell SessionStart handler is
context-only: $CLAUDE_ENV_FILE feeds Bash commands only. On other platforms init
removes PowerShell handlers an earlier run wrote.
Claude Code's PowerShell tool never reads $CLAUDE_ENV_FILE, so its commands do not
see the env layers. On Windows, init --powershell-env-hook adds a PreToolUse hook
(matched on the PowerShell tool) that prepends the dotagents env change set to each
PowerShell call (env --diff --cache, so an unchanged environment is not reassembled
on every call). It auto-approves every PowerShell tool call: rewriting a command
without a prompt on each one requires permissionDecision: "allow", which skips the
approval prompt (your deny and ask rules still apply). It is off by default, and a
plain init removes one an earlier release wired.
The env half never truncates the file and is guarded by [ -n "$CLAUDE_ENV_FILE" ]
(see the hooks docs):
another hook may write to the same file, so each hook replaces only its own part --
SessionStart its env --into block, CwdChanged its one AGENTS_PROJECT_ROOT line --
and keeps every other line. Appending instead grew the file on every re-run (resume,
compact, clear) and every cd, and Claude Code inlines it into each Bash command, which
Git for Windows' bash cuts off at about 8 KB. Each run starts from a fresh environment
(the hooks docs: previously sourced env files are not loaded in the hook's process), so
SessionStart recomputes the whole diff: a value changed since the last run is updated,
and one no env layer sets any more drops out. $CLAUDE_ENV_FILE exists only inside SessionStart/Setup/CwdChanged/FileChanged
hook processes — it is absent from the session's own shell, so checking for it with
env | grep proves nothing.
Skills are linked one directory per skill: each <store>/skills/<name> becomes
<Claude config>/skills/<name>, so a skills directory of your own is never replaced,
and a same-named skill you placed there yourself is kept (with a warning). A link is a
symlink where the OS permits one, else on Windows a directory junction, else a
copy. init and overlays add / sync / remove (once init has wired Claude
for that store) keep them current: a copy you have not edited is refreshed, a skill
the store no longer has is removed, and a copy you edited is kept with a warning.
Which entries are dotagents' is recorded in <Claude config>/skills/.dotagents-linked.json.
In a project, init warns when those skills are not gitignored.
Codex — user store (-g) only, since its hooks file is global. init deploys two
small Python scripts into <codex-home>/hooks/ ($CODEX_HOME, else ~/.codex), run
by the interpreter init ran with so they work under any shell, and wires them in
<codex-home>/hooks.json — never config.toml, so your main config is not rewritten
(if you keep inline [hooks] there, Codex warns about the split; use --no-hooks and
add the hooks yourself):
SessionStartprintsdotagents context --agents codex; Codex adds a SessionStart hook's stdout as developer context.PreToolUse, matched on Codex'sBashtool, prepends thedotagents envchange set to each command throughupdatedInput, so the env is live. On native Windows Codex runs commands through PowerShell, which cannot parse that prefix, so the hook passes commands through unchanged: Windows Codex sessions get the context but not the env.
Earlier releases wrote a static [shell_environment_policy] snapshot into your
config.toml on init --agents codex. It pinned one project's paths into the
global config and could produce invalid TOML, so it is gone: init removes a block
it left there (between # dotagents:begin / # dotagents:end) and touches nothing
else in the file.
Antigravity — only with an explicit --agents antigravity (it sets no variable
dotagents could detect it by), and user store (-g) only. init deploys a script
into ~/.gemini/config/hooks/ and wires it as a PreInvocation hook under a
"dotagents" entry in ~/.gemini/config/hooks.json. The hook fires every model turn
and sends the context on the first one only. It carries no env: Antigravity's
PreToolUse cannot rewrite a command.
Gemini CLI, Cursor, Copilot, pi — no hooks. Gemini CLI gets an @ import of the
store's AGENTS.md in its GEMINI.md, pi a pointer in its config dir (user store
only); all four get the context through dotagents launch or
dotagents context --write-agent.
overlays
Manages opt-in overlays by name. See Overlays for the full model.
dotagents overlays add python engineering # install into the scope, publish skills, merge rules/routing
dotagents overlays add '*' -g # everything the repos offer (a store registry: all it names)
dotagents overlays list # installed (discovered) + available from the repos
dotagents overlays sync 'py*' # refresh installed overlays matching a glob
dotagents overlays sync --prune # also clear what .gitignore/.ignore protect: exactly upstream
dotagents overlays remove python # delete the overlay dir, unpublish its skills, un-merge its rules
dotagents overlays show python # describe one: manifest, requires, setup, skills, source (--json)
addtakes names or globs:'*'is every overlay the repos offer, so with a store registry it installs everything the registry names, and'py*'the ones starting withpy; a glob that matches nothing is an error. One overlay failing (its copy, requirements or setup) does not stop the rest: it is logged, what requires it is skipped, everything else installs, and the command exits 1 naming each failure. A failed overlay this run was installing for the first time is removed again; one that was already installed is left in place, not set up.addinstalls what each manifest'srequiresnames first, each requirement installed and set up before the overlay that needs it (--no-requiresto skip); a requirement the scope already has, in its own store or the user store for a project, is satisfied and left alone, while a name you ask for explicitly is always (re)installed and set up. It validates and resolves every name against the repos before touching anything — the first repo that offers a name wins — records where each overlay came from, and publishes skills from the installed copy, never replacing a skill already in the sharedskills/dir (and links them into Claude's skills dir wheninitwired Claude for the store). Each overlay'srequirements.txtis installed into the scope venv before its setup script runs (--no-venvskips it; see the scope venv). A fresh install whose requirements or setup script fail is rolled back. A source inside the store's ownoverlays/directory is refused (bysynctoo):removewould delete the only copy.add,removeandshowwithout an overlay name are usage errors (exit 2), and a--dry-runsays what it would write (would recompose ...) instead of reporting it as done.syncrefreshes each installed overlay from the repo it was installed from (recorded in<overlay>/.dotagents-install.json);--reporeplaces that recorded source for the run. The install is made to match the source exactly: new and changed files land,overlay.tomlis refreshed, and files the source does not ship are removed. A file you edited (or added) there is copied to<store>/install_backup/<timestamp>/overlays/<name>/before it is replaced or removed. What the.gitignore/.ignoreat the overlay's root match — setup output, local state — and tool caches (__pycache__,.venv, …) are the install's own and left alone;--pruneclears them too, leaving exactly the upstream files. Arequiresadded upstream is installed, and published skill copies are refreshed.addof an overlay already installed does the same (with--prunetoo). Keep your own additions outside<store>/overlays/— akb/file of your own, anAGENTS.local.md— where a sync never touches them.removedeletes the overlay's directory, unpublishes the skills it published (and their links in Claude's skills dir), and recomposesAGENTS.md's managed block over the overlays that remain, so its rules and routing leave with it. It refuses an overlay another installed overlay requires (and no other store provides) unless--force.listshows the installed overlays per store (in a project, the user store's too; a same-named project overlay shadows the store's copy), flags arequiresno installed overlay provides, and lists what the repos offer (*= installed).showalso names the repo an installed overlay came from.
context
Assembles the effective context an agent should load — the overlay CONTEXT.mds
(by priority), then each store's AGENTS.md, the project store's AGENTS.local.md,
and the project root's own AGENTS.md / AGENTS.local.md — minus whatever the harness already loads by itself (for Claude Code, whatever its
CLAUDE.md files really @-include) — and prints it to stdout by default
(POSIX convention); pass a path to write a file, or --write-agent to merge it into
each agent's own instruction file. It ends with the skills on offer and, when there are
any, your local additions to overlay files — a store file at the same path as an
overlay's (~/.agents/kb/RUST.md for the rust overlay's kb/RUST.md; see
Overlays); --format json carries both as their own keys (skills,
local_additions).
dotagents context # print the active agent's context to stdout
dotagents context out.md # write it to out.md (positional path)
dotagents context --format json --agents claude # JSON to stdout
dotagents context --inline # also inline the on-demand .md files it points at
dotagents context --write-agent --agents codex # merge a managed block into <project>/AGENTS.md
[output]— positional destination. Default-(stdout); a path writes that file, for one agent (several agents need stdout or--format json).--write-agent— merge the context into each agent's own instruction file under the project root (not with-g), as a manageddotagents:contextblock refreshed in place: Claude.claude/CLAUDE.md, CodexAGENTS.md, GeminiGEMINI.md, Cursor.cursor/rules/dotagents.mdc(analwaysApply: truerule), Copilot.github/copilot-instructions.md, pi.pi/APPEND_SYSTEM.md(the file pi appends to its system prompt), Antigravity.agents/rules/dotagents.md. Prefer the hook where the harness has one; this is the static alternative. Mutually exclusive with[output]and--format json.--inline— also append the on-demand.mdfiles the sources reference (bare or backticked relative paths, and$<NAME>_OVERLAY_ROOT/.../<PROJECT_ROOT>/...references). Each reference resolves against the directory of the source that mentions it; a project's references also search its.agents, and a user or system source never resolves into the project. Off by default: the base rules say to read those only when a task needs them, and inlining every mention makes a very large session payload.--agents <a,b>— which agents to generate for (default: the active agent); names or harness ids (claude-code). Exits 2 when none is known.--format markdown|system-reminder|json— output shape.-g/--global— skip the project-level context files (the store is unaffected).--agents-dir <dir>— user store override for this run.
On stdout and in JSON, $<NAME>_OVERLAY_ROOT is expanded to the installed overlay's
directory, so a harness without the env still finds the file; --write-agent keeps
the variable, because its file may be committed. Skills are listed with the path of
their SKILL.md (JSON: a path key), a project skill winning over a same-named user
one.
Roots: the store is --agents-dir → $AGENTS_HOME → ~/.agents, and the project
root is $AGENTS_PROJECT_ROOT → $CLAUDE_PROJECT_DIR → the cwd. A SessionStart
hook runs this from wherever the session started, so pinning
AGENTS_PROJECT_ROOT is what keeps the assembled context the same in every
subdirectory.
env
Assembles the chained env layers — store by store (system, user, then the project's
.agents/), each store's overlays before the store itself — plus the standardized
identity vars, later-overrides-earlier, and prints them.
dotagents env --format export -g # shell-sourceable `export KEY='value'` lines
dotagents env --diff --format json # only vars that differ from the caller's env
--format— output syntax. Defaultautodetects the calling shell (via the parent-process chain) and emits sourceable output for it. An explicit--formatalways wins.--diff— emit only the change set vs. the caller's environment.--cache— replay the previous output while nothing it depends on changed (the environment, the working directory, the stores, the overlays, every env andlibfile), for at most five minutes; the per-command env loaders use it. The cache is owner-only, under<user store>/.cache/env/.--into <file>— write into<file>instead of stdout, as the one block this command owns there (between: dotagents-env-begin/: dotagents-env-end): an earlier block is replaced, anything else in the file is kept. The SessionStart hook writes$CLAUDE_ENV_FILEthis way, so the file stays one block however often the hook runs.exportformat only.-g/--global— skip the project-level env files (the store is unaffected).--agents-dir <dir>— user store override for this run.
| Format (aliases) | Output | Notes |
|---|---|---|
export (posix, sh, bash) |
export KEY='value' |
POSIX single quotes (' written '\''), so nothing in a value is expanded or run when sourced. |
dotenv (env) |
KEY=value |
For python-dotenv; quoted only when needed. Read it with interpolate=False to keep a ${X} in a value literal. |
powershell (pwsh, ps) |
${env:KEY} = 'value' |
Every single-quote character doubled. |
cmd (bat, batch) |
set "KEY=value" |
% doubled: run it as a batch file (dotagents env --format cmd > env.cmd && call env.cmd), with delayed expansion off. A newline becomes a space, and a " cannot be escaped. |
fish |
set -gx KEY 'value' |
\ and ' escaped. |
json |
an object | Sorted keys. |
ini |
KEY=value under [env] |
For Python's configparser; a value it cannot hold verbatim is named in a ; comment instead. |
yaml |
KEY: "value" |
Every value double-quoted, so none reads back typed. |
A variable an env layer unsets is emitted as a removal: unset K (export),
set -e K (fish), ${env:K} = $null (powershell), set "K=" (cmd), null (json,
yaml). dotenv and ini have no way to say "unset" and leave it out. On Windows the
POSIX formats (export, dotenv, fish) convert PATH-like values to /c/...
form and drop names that are not shell identifiers.
What is assembled, in order:
- Identity —
AGENTS_HARNESS,AGENTS_VENDOR,AGENT,AGENTS_MODELfor the harness running this process (none in a plain shell), never replacing a value already set. - Roots —
AGENTS_HOMEandAGENTS_PROJECT_ROOTname the stores this run actually walked, as absolute paths: an inherited value naming the same directory holds, any other is replaced (with-g, which walks no project,AGENTS_PROJECT_ROOTis only set when unset). Only when unset:AGENTS_PYTHON, the interpreterdotagentsitself runs under — the Python a shim or helper script should use, since a barepython/python3onPATHmay be a Store stub, an emulated build, a venv's, or missing — and one<NAME>_OVERLAY_ROOTper installed overlay, whereNAMEis the overlay's name upper-cased with-turned into_(my-ov→MY_OV_OVERLAY_ROOT). That is the same namecontextexpands as a<NAME_OVERLAY_ROOT>placeholder, so an env file and a context file refer to an overlay's install dir by one name. Whendotagentsruns from a.pyz,AGENTS_PYLIBnames that archive: the libraries it bundles (duho, pathapi) import from it, so an overlay built on them appends it tosys.pathwhen its own import fails. An installeddotagentssets nothing, since those libraries are already inAGENTS_PYTHON's site-packages. It is never put onPYTHONPATH, where it would shadow a project's own copy. PATH— every level'sbin/(each store's overlays, then the store) goes to the front in that order, including ones that do not exist yet, so anenv.pyand every subprocess can call an overlay's helpers by name. The order holds even when the calling shell already had some of them, and empty or relative inherited entries are dropped.PYTHONPATH— every level'slib/that exists goes to the front, highest precedence first, so every Python the session starts — a skill script, an overlay'sbin/launcher, anenv.py— canimportany overlay'slib/module, including another overlay's. The same list is published asAGENTS_PYTHONPATH(os.pathsep-joined), which is howenvknows whichPYTHONPATHentries are its own: after acdinto another project, the first project's libs leave. Alib/module comes before site-packages and the standard library, so it must not reuse a name it does not mean to replace.- The env files — first every level's
pre.env.py/pre.env(and the project'spre.local.env), then every level'senv.py/env(and the project'slocal.env), each evaluated against everything before it. A project root's own top-level files outside.agents/never run. - Proxy —
AGENTS_PROXYis set fromAGENTS_WEBFETCH_PROXY_URLor the globalHTTPS_PROXY/HTTP_PROXY/ALL_PROXYwhen unset, and each proxy variable that is set in one case is mirrored into the other.
env.py is executed with the Python in AGENTS_PYTHON (else dotagents' own),
as env.py --level <level> [--global], where <level> is system, user,
project or the overlay's name and --global says the project tiers are skipped
(--agent <level> is passed too, for older scripts). It inherits everything
assembled so far and prints its changes to stdout as JSON: one object, or one object
per line, merged in order. Each value must be a string; null unsets the variable;
any other type, a key that cannot be a variable name (empty, or holding = or NUL),
and a value holding NUL or bytes that are not UTF-8 are skipped with a warning naming
the key. A script that exits non-zero or prints something else contributes nothing,
with a warning; values are never logged.
A plain env file (env, pre.env, local.env, pre.local.env) is sourced by
bash with set -a, and whatever it changed, including unset, is taken; both
snapshots come from the same bash process. On Windows that is Git for Windows' bash
(not the WSL launcher); with no usable bash, plain env files are skipped with a
warning. A file whose source fails, or that calls exit, contributes nothing.
Warning
env output is sensitive by design — it prints resolved values. Treat the
output as secret. The command itself never logs DOTAGENTS_* / AGENTS_*
values, and its full (non---diff) output leaves inherited DOTAGENTS_*
values out.
path
The dirs env puts at the front of PATH — or with --lib, of PYTHONPATH —
highest precedence first. Nothing is run (no env file, no env.py), so it is a
cheap way to put the overlay bins on a shell's PATH:
export PATH="$(dotagents path --format posix):$PATH" # bash / Git Bash
dotagents path --lib --format json # the lib dirs, as JSON
--format list(default, one per line),native(os.pathsep-joined),posix(:-joined; on Windows each dir in its MSYS2 form,/c/...),json.- The bin list includes dirs that do not exist yet, as
envdoes; the lib list only existing ones. -g/--agents-dirmean what they mean forenv:-gleaves the project's dirs out,--agents-dirnames the user store.
findings
A per-scope findings queue: a finding is a problem execution discovered (the
config or the code caused a mistake or rework) recorded so triage can fold it into
a rule later, instead of an agent editing config mid-task. Each finding is one
markdown file, like an agent memory: a short frontmatter (name, description,
status, created) that becomes the index line, and a body with the details.
dotagents findings add "bare kb/X.md refs were never inlined" -b "what happened, evidence"
dotagents findings list # active findings: `name: description`
dotagents findings list --all --json # active + processed, machine-readable
dotagents findings show <name> # the whole file (--json for a structured form)
dotagents findings done <name> -r "fixed in _context; test added"
dotagents findings reopen <name>
dotagents findings remove <name> # an active finding recorded by mistake
dotagents findings index # regenerate INDEX.md after hand edits
dotagents findings index --requote # ...and quote frontmatter values YAML cannot read
dotagents findings path # where this scope's queue lives
- Scope: the project by default (
<project>/.agents/findings/), the user store with-g(<store>/findings/), or any directory with--dir(e.g.--dir ~/notes/findings). Roots resolve as for every other scope-aware command ($AGENTS_PROJECT_ROOT,$AGENTS_HOME). - The base config's own rules use it: a global-config miss is
dotagents findings add -g ..., triage readslist -g/show -gand closes withdone -g(see the installedAGENTS.md). The resolution is the record; dotagents keeps no separate design log. - Layout is the queue discipline: active findings at the top level;
doneappends a## Resolutionsection and moves the file toprocessed/(never deletes). The resolution is required — a processed finding without one is untriaged, not done.INDEX.md(active first, then processed, one line each) is regenerated by every mutating command. --body/--body-file(-= stdin, read as UTF-8) carry the details onadd;--resolution/--resolution-fileondone. A hand-written note without a frontmatter is still listed (name = file stem, description = its first heading or line) and gains one the first time the command rewrites it; a note that is not UTF-8 is skipped with a warning and never rewritten.- The frontmatter is YAML a viewer or another tool can parse: a value is written
bare when YAML reads it back as the same text, and double-quoted otherwise (a
description that opens with
[or a backtick, or holds:).index --requotedoes the same for findings an earlier version wrote, and for hand-written ones, changing only the lines that need it. addrefuses the namesindexandreadme(on a case-insensitive filesystem they areINDEX.md/README.md).doneandreopenrefuse to move onto an existing file, andremovedeletes active findings only: reopen a processed one first, so its resolution is not lost by accident.- The command is bundled with dotagents and discovered from the package itself;
a same-named
findings.pyin a scope'sdotagents/cmds/overrides it.
launch
Start an agent's command-line harness the way a session wired by init would
find the world already: the full env exported, and the context handed over
up front.
dotagents launch claude -- --model sonnet # everything after `--` is the agent's
dotagents launch # the active agent, as `context` picks it
dotagents launch codex --dry-run # print the command line and the exported names
dotagents launch -g gemini # user-store tiers only (the env/context meaning of -g)
What happens, in order:
- Environment — the same assembly as
dotagents envfor this scope (identity vars,AGENTS_HOME/AGENTS_PROJECT_ROOT/AGENTS_PYTHON, every<NAME>_OVERLAY_ROOT, thePATHandPYTHONPATHprepends, the env-file chain) is applied to thedotagentsprocess and handed to the child, so the harness and everything it spawns see it. - Context —
dotagents contextfor that agent (what its harness does not already load) is written to a file in the user store,.cache/launch/<agent>-<hash of the project root>.md(overwritten by the next launch;--dry-runuses a temp file), exported asAGENTS_CONTEXT_FILE, and passed the way the harness takes appended system-prompt text: Claude Code gets--append-system-prompt-file, pi gets--append-system-prompt(on POSIX; on Windows its npm.cmdshim cannot carry a multi-line argument, so it takes the static route below). A harness with no append flag (Codex, Gemini, Cursor, Copilot — their prompt-file options replace the built-in prompt, which is not the same thing) gets it the static way instead: merged as the manageddotagents:contextblock into its own instruction file in the project, exactly whatcontext --write-agentdoes — but only into a file git does not track, and not under-g: those files are usually committed, and the context carries your user store's private rules. Otherwise the context stays at$AGENTS_CONTEXT_FILEonly, with a warning;--write-agentmerges it anyway. The harness is resolved before anything is written, so a launch that fails with "not found" leaves the project untouched.--no-contextskips this;--inlinealso inlines the on-demand files the sources reference. - The harness — the agent's program (
claude,codex,gemini,cursor-agent,copilot,pi), resolved on the PATH from step 1 so a harness an overlay'sbin/provides is found, or--command <program>for one under another name. On Windows the current directory is never searched (aclaude.cmdat a repository's root is not the harness). dotagents' flags come first and the passthrough after, so yours win where the harness takes the last value. The exit code is the harness's (128+N when a signal N killed it). On Windows a.cmd/.batharness runs through cmd.exe, which would execute or expand& | < > ^ % ! "and cut an argument at a newline, so an argument holding one is refused rather than passed on rewritten.
--dry-run prints the command line, the names of the exported changes (never
their values) and, on an unset: line, the variables an env layer removed, and runs
nothing. Those removed variables never reach the harness. --agents-dir overrides the user store, as for
env and context. Like findings, the command is bundled and discovered from the
package; a same-named launch.py in a scope's dotagents/cmds/ overrides it.
audit — not a dotagents command
There is no dotagents audit. The dotagents source repo has its own
tools/audit.py, but every path it checks is a path in that repo
(src/dotagents/_overlay/…, tools/…), so it validates the repo's layout in CI —
it is not a validator for an installed ~/.agents and is deliberately not shipped
in the package or the .pyz.
Pre-push scanning (an overlay's command)
Scanning a repo for leaks before publishing it — machine paths, private plan names,
.agents/ refs, agent-session trailers in commit messages — enforces a convention
rather than dotagents' own mechanism, so the package ships no such command. The
opt-in engineering overlay does: dotagents leak-check <repo>. Its patterns for
what is personal to you stay in a machine-local file, never in a repository.
link-project / sync-project
The optional per-project private-store workflow. These are not dotagents
commands: they are supplied by the private-sync overlay, together with the logic
behind them, so a plain install carries no private-sync workflow at all. Install the
overlay into the user store first and they become available like any other
subcommand:
dotagents overlays add private-sync -g --repo "https://github.com/jose-pr/dotagents.git@repo#overlays"
dotagents link-project . # symlink this project's .agents into its store
dotagents link-project . --copy # real-dir copy (no-symlink systems)
dotagents sync-project -m "msg" # reconcile + hand off to the store's sync
dotagents sync-project --remote <url> -m init # one-command bootstrap
(They were called link / sync before; the names now say what they act on — a
project's .agents — and sync-project no longer reads like overlays sync.)
See Private sync for the full walkthrough.
about
dotagents about # dotagents-cli <version>, then duho, pathapi, ...
dotagents about --json # the same, plus where it runs from and the Python
One <distribution> <version> per line, the CLI first. From a built .pyz the
list is exactly what build-pyz vendored (it records the names and versions in
dotagents/_bundle.json before stripping the dist-info dirs the zipapp would
otherwise carry, so --extras uri shows up here as pathapi-uri and netimps).
From a plain install it is whichever of a fixed set is installed: duho, pathapi,
pathapi-uri, netimps, sftppath, objpath and repopath. A package outside
that set, such as a client the sftp or s3 extra pulls in (paramiko, boto3),
is not listed.
build-pyz
python -m dotagents build-pyz --out dist/dotagents.pyz
python -m dotagents build-pyz --extras uri,repo # also vendor what those extras install
python -m dotagents build-pyz --with-pip # also carry pip, for machines without it
Builds a self-contained zipapp from a source checkout, with duho and pathapi
vendored (plus what the dotagents-cli extras named by --extras install), so it runs with no
pip install. The repo's tools/ never ships in it. Flags:
--out <path>— where to write it (defaultdist/dotagents.pyz).--python <shebang>— the interpreter line embedded in it (default/usr/bin/env python3).--duho-version,--pathapi-version— the versions vendored (defaults: the floors the package declares).--pathlib-next-versionis the old name of the second.--extras <a,b>— dotagents-cli extras to vendor too (uri,http,s3,repo), each distribution they install at its floor, so the.pyzcan read those overlay sources; none by default. Everything is vendored as pure-Python wheels resolved for Python 3.9, so the file does not depend on the machine that built it;sftpneeds native modules and fails the build with a message saying so. The archive is compressed.--with-pip— also vendor the pinned pip wheel (about 1.8 MB), so the.pyzcan set up the scope venv where Python has no ensurepip and no index is reachable (see the scope venv).
Each release attaches a built one: https://github.com/jose-pr/dotagents/releases/latest/download/dotagents.pyz.