Changelog
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
0.8.1 - 2026-10-21
Added
- Overlay commands get a launcher on
PATH: each top-levelbin/<name>.py, and each[scripts]entry inoverlay.toml(name = "path"adds or replaces one,name = falseopts abin/<name>.pyout). The launchers live in the agents' venv, in<venv>/overlays/:<name>.exeon Windows, made with distlib'sScriptMakerso no command interpreter parses the arguments, and a/bin/shscript elsewhere. They run the script through$<NAME>_OVERLAY_ROOTwhen they run, so only a new interpreter needs them made again.initandoverlays add/sync/removekeep them current and delete those no overlay has; two overlays with one script name is an error naming both. dotagents envputs the launcher directory of the venvAGENTS_PYTHONnames first onPATH, ahead of the levels'bindirs, and takes any other venv's off it.- A project gets its own venv when one of its overlays has scripts.
- New dependency:
distlib>=0.4.0,<0.5, vendored in the.pyz.
0.8.0 - 2026-10-16
Added
overlays addtakes globs:overlays add '*' -ginstalls every overlay the repos offer, which for a store with a registry (<store>/dotagents.toml) is everything it names. A glob matching nothing is an error.-
tools/cloud-setup.shinstalls every overlay the store's registry names (overlays add '*') before falling back to installingprivate-syncalone. -
Each store has a Python venv,
<store>/.pyvenv/<X.Y.Z>-<os>-<arch>/, made from the Python that runs dotagents, where skills run.initandoverlays add/sync/removekeep it current: the user venv always has duho, and every overlay'srequirements.txtis installed (in one pip run, only when a file changed, before the overlay'ssetup.py). A project gets its own venv when one of its overlays ships requirements or alib/.--no-venvskips it for a run;AGENTS_NO_PYVENV=1turns it off..pyvenv/holds the agents' venvs only: a venv there that no sync completed is never used, and a project's own code keeps its venvs in the project tree. - The venvs layer project > user > system through a
.pthfile in each: the store's own overlay libs, then each lower store's venv packages and libs, then dotagents itself. A project venv imports the project's packages first, then the user's. envpointsAGENTS_PYTHONat the active venv and names it inAGENTS_PYVENV; overlaysetup.pyscripts run under it.contextprints it ("Python for skills"), and its JSON form carries it aspython. The managed block tells agents to run skill scripts with$AGENTS_PYTHON.- pip without ensurepip: a venv where the interpreter has no ensurepip is created
without pip, and pip runs from its wheel (25.3, the last for Python 3.9, checked
against a pinned sha256). The wheel is found in the store's cache, in a
.pyzbuilt with the newbuild-pyz --with-pip, inAGENTS_PIP_SOURCE(a directory of wheels, or any URI pathapi-uri reads) or pip'sfind-links, or on your index (PIP_INDEX_URL, or the mirror inpip.ini/pip.conf).AGENTS_PIP_SOURCEis also passed to pip as--find-linksfor the requirements.
Changed
overlays addno longer stops at the first overlay that fails. Each failure is logged, the overlays that require the failed one are skipped, every other overlay is installed, and the command exits 1 naming each failure. A failed overlay this run was installing for the first time is removed again, as before; the ones that installed stay.- With a scope venv,
envno longer puts the overlay libs it layers onPYTHONPATH: the venv's interpreter imports them, and no other Python in the session should. Without a venv,PYTHONPATHcarries them as before.AGENTS_PYTHONPATHstill lists every lib. - The base block's "Startup: annotate that you read
…" line names a store under the home directory from there (~/.agents/AGENTS.md), and any other store by its absolute path as before. A store kept in git and used on machines whose home directories differ no longer showsAGENTS.mdmodified on all but one of them.
Fixed
env --cacheon Windows with Python before 3.13 could miss a cache it had just written:time.time()there lags the file's own timestamp by up to one clock tick, and a cache file dated after "now" was treated as stale. A cache file dated up to 2 s in the future now counts as fresh.
0.7.6 - 2026-10-15
Added
- A
repoextra (repopath[uri]) for overlay sources atgithub:,gitlab:andrepo:URIs, which theuriextra no longer brings.
Changed
- dotagents depends on
pathapi(>=0.1.0,<0.2), the new name ofpathlib_next, instead ofpathlib_next. The.pyzvendors it;build-pyz --pathapi-versionnames its pin, and--pathlib-next-versionstill works. On Python 3.9 nothing else has to be installed for it. - The extras install pathapi's own distributions:
uriandhttpinstallpathapi-uri(the URI layer, imported asuripath, withfile:,http(s):,dav:,ftp:,zip:andtar:on the standard library);sftpaddssftppath[paramiko]ands3addsobjpath[s3]. Overlay sources are read as before. Agit:URI is not a scheme any more; a repository tree is arepo:URI. build-pyz --extrasvendors what the named extras install, each distribution at its floor, and refuses an extra it does not know.aboutlistspathapi,pathapi-uri,netimpsand the plugin distributions in place ofpathlib_next,uritoolsandrequests.
Removed
- The
.pyzno longer extracts the built-in command modules' sources to temporary files at startup: duho reads a command's flags and help through the zip importer when the module has no file on disk.
0.7.5 - 2026-10-14
Changed
main()runs throughduho.run(..., exit=False): it always returns the status instead of raisingSystemExitfor a message,--helpor a usage error (the console script,python -m dotagentsandinstall.pyexit with it as before, with the same statuses and the same text on standard error). Ctrl-C now ends with status 130 and no traceback, and a closed pipe on standard output ends with status 1, quietly.- The
duhofloor moves to>=0.7.5,<0.8(from>=0.6.0,<0.7); the vendored.pyzpin (build-pyz --duho-version) moves with it. No flag, exit status or output changes. - Command modules are loaded with duho's own
discover_commands(on_error=...)instead of a copy of its loop built on a private duho name. A module that fails to import is still named in a warning and skipped, and the listing order is unchanged. A command module may now import a sibling helper file by its bare name. kebab_namedelegates toduho.text.kebabcase; its results are unchanged.- Discovered commands are listed in discovery order with
discover_commands(sort=False)instead of being re-sorted here, and a command that cannot build its parser is found out withduho.runtime.build_command_parserinstead of a private duho helper. Output, exit statuses and which commands are skipped are unchanged.
0.7.4 - 2026-10-14
Added
findings index --requotequotes, in the findings already on disk, the frontmatter values YAML cannot read as they stand. Only those lines change: bodies, the other lines and line endings are kept.
Changed
- The private-sync guide describes the default ignore rules the overlay's
sync-projectnow writes, once, into the.gitignoreof a store kept in git:scratch/,tmp/,__pycache__/, hidden files and the machine-local overrides stay out of the private repository.
Fixed
findingswrites frontmatter any YAML parser reads. A description that opens with[or a backtick, or holds:, was written bare, and a markdown viewer or any other tool reading the frontmatter rejected the file. Such a value is now a double-quoted string; a plain one is still written bare, and a finding written before reads as it did.
0.7.3 - 2026-10-04
Changed
- The command guide and the shipped API header now say that every command except
env, built-in or discovered, is served as an MCP tool whendotagentsstarts withDOTAGENTS_MCP=stdio, and thatAGENT_HELP=1turns--helpinto a JSON document. The authoring guide asks overlays to ship command modules rather than loose scripts, and skills to name the tools they use.
0.7.2 - 2026-10-03
Added
git_ssl_revokein<store>/dotagents/config.tomlsets how the git clones and fetches of overlay repos check certificate revocation:best-effort(the default) skips a revocation list that is missing or unreachable,falseskips the check, as git on Linux does,trueenforces it. It is passed to every git call, so a stricter git configuration elsewhere no longer fails an https clone on Windows, and a clone that still fails on revocation says which setting to change. A git too old to knowbest-effort(it rejects the value) runs the call again without the setting.
0.7.1 - 2026-10-01
Added
- Your own additions to an installed overlay's file live at the same path under the
store root (
~/.agents/kb/RUST.mdfor the rust overlay'skb/RUST.md, or<project>/.agents/kb/RUST.md), whereoverlays syncnever touches them. The baseAGENTS.mdtells agents to read them alongside the overlay's file, anddotagents contextlists the ones it finds (JSON:local_additions).
0.7.0 - 2026-10-01
Changed
overlays sync, andoverlays addof an overlay already installed, make the install exactly what the source ships: changed files are replaced and files the source does not ship are removed, including ones edited or added in the install (each backed up under<store>/install_backup/first). What the.gitignore/.ignoreat the overlay's root match (read as git reads a.gitignore; one in a subdirectory is an ordinary file), and tool caches, are the install's own and left alone. Previously an edited file was kept unless--overwrite.--prune(onsync, and now onadd) clears what.gitignore/.ignoreprotect and the tool caches too, leaving exactly the upstream files.
Fixed
- A command module whose class declares no
_parsername_is named by its class in kebab case again (LeakCheck->dotagents leak-check). Since 0.6.2 (duho 0.6.0) such a command was only reachable asdotagents LeakCheck. - The Claude Code CwdChanged hook keeps one
AGENTS_PROJECT_ROOTpin in$CLAUDE_ENV_FILE, replacing the previous one, instead of appending a line on everycd(the file is inlined into every Bash command). Other lines in the file are kept. Re-rundotagents init -gto update an installed hook.
Removed
overlays sync --overwrite: replacing is whatsyncalways does now.
0.6.3 - 2026-09-30
Added
env --into FILEwrites the output into FILE as the one blockenvowns there, replacing the block an earlier run wrote and keeping every other line.- With
--diff --format export, aPATHthat is the caller's own with entries put in front is written asexport PATH='<entries>'"${PATH:+:$PATH}", so it stays short and keeps what the sourcing shell has. - When
dotagentsruns from a.pyz,envexportsAGENTS_PYLIB, the path of that archive. The libraries it bundles (duho, pathlib_next) import from it, so an overlay built on duho works even when the interpreter inAGENTS_PYTHONhas no duho installed. An installeddotagentssets nothing: its libraries are already importable fromAGENTS_PYTHON. The archive is never put onPYTHONPATH, where it would shadow a project's own copy; a consumer adds it tosys.pathitself when its import fails.
Fixed
- The Claude Code SessionStart hook no longer appends the whole environment to
$CLAUDE_ENV_FILEevery time it runs (startup, resume, compact, clear). Claude Code inlines that file into every Bash command, and once it passed about 8 KB, Git for Windows' bash truncated the command and every Bash call failed with an unterminated quote. The hook now usesenv --into, which keeps one block. Re-rundotagents init -gto update an installed hook.
0.6.2 - 2026-09-28
Added
- With the
duhofloor bump, everydotagentsinvocation now checksDOTAGENTS_MCP=stdioand serves its full command tree as an MCP server over stdio when set.envopts itself out of that tool surface (_mcp_ = False): its whole point is printing resolved, possibly secret values, which should never be a callable MCP tool. Every other command is unaffected, andenvstill works normally on the CLI.
Changed
- The
duhofloor moves to>=0.6.0,<0.7(from>=0.5.0,<0.6); the vendored.pyzpin (build-pyz --duho-version) moves with it, per the repo's own rule that the two must always agree. No other source change was needed.
0.6.1 - 2026-09-27
Fixed
- Re-running
initafter an upgrade from 0.5.x replaces theCwdChangedhooks and Codex'sPreToolUsehook that release wrote, instead of adding the new ones beside them (everycdran both, and the old one still wrote the unquoted project path). A hook an earlier release wrote is recognised by its label and a command that runs dotagents, readsAGENTS.mdorAGENTS_PROJECT_ROOT, or runs one of dotagents' hook scripts.
0.6.0 - 2026-09-25
Added
- The package ships
py.typedand declaresTyping :: Typed; Python 3.14 is a supported version, and the project metadata links the issue tracker. - A
yamlextra (pip install 'dotagents-cli[yaml]') installs PyYAML for YAML overlay-repo registries; the error for a YAML registry without it names the extra. dotagents env --cachereuses the previous output while the environment, working directory, stores, overlays and env and lib files are unchanged, for up to five minutes. The PowerShell and Codex env loaders use it, so a shell command no longer re-runs every env file.- A project-scope
initpoints pi at the project's.agents/AGENTS.mdthrough a managed block in.pi/APPEND_SYSTEM.md. dotagents._resourcesholds the bundled base overlay (BASE_ROOT), the block template constants andbase_agents_text; they are still importable fromdotagents.cli._common.Agent.include_entryandAgent.base_config_notedescribe whatinitwrites for each harness.dotagents pathprints the bin dirsdotagents envputs onPATH(--lib: the lib dirs it puts onPYTHONPATH), highest precedence first, as a list,os.pathsep-joined,:-joined POSIX (/c/...on Windows) or JSON, without running any env file.- An overlay's setup script is stopped after 300 seconds, or the manifest's
setup_timeout, oroverlays add/sync --setup-timeout;0means no limit. A stopped script fails the install like any other failure.
Changed
- The license metadata is the SPDX expression
MIT(PEP 639). Building from source needs hatchling 1.27 or later. install.pyinstalls its dependencies only into a virtual environment. Elsewhere it lists the alternatives (a venv,pip install dotagents-cli, the releasedotagents.pyz) and installs only with--bootstrap, which it still refuses for an externally managed (PEP 668) interpreter.- The root
build.pyhelper is removed; usepython -m dotagents build-pyz. Its bare-word argument parsing misread flags, and at the repository root it shadowedpython -m build. tools/cloud-setup.shoffers the store token only to the store's own remote, on its own git calls, and no longer writes a global github.com credential helper to~/.gitconfig.launchwrites the assembled context to one file per agent and project under<user store>/.cache/launch/, overwritten by the next launch, instead of a per-process temp directory that a killed session left behind.findings removedeletes only active findings; a processed finding keeps its resolution and must be reopened first.init,overlaysandfindingshonour a pinned$AGENTS_PROJECT_ROOTonly while the current directory is inside it. After acdout of it they use the project the current directory is in (the nearest directory up with a.gitor its own.agents) and warn; they used to write into the session's first project.envandcontextkeep the pin.contextoutput on stdout and in JSON, and the contextlaunchhands over, expand$<NAME>_OVERLAY_ROOTto the overlay's directory, so a harness without the dotagents env can follow overlay routing. A file written bycontext --write-agent, or merged bylaunch, keeps the variable, because it may be committed.- The skills listing names each skill's
SKILL.md, and JSON skill entries gain apathkey. - The base rule for a config miss ends at the findings queue: triage closes a
finding with
dotagents findings done -g <name> -r "<resolution>", and the resolution is the record. It no longer points atdotagents/DECISIONS.md, and it says where a finding that reports a fact rather than calling for a rule goes: akb/file routed from "Load on demand". An existing store picks the new wording up when its block is next rebuilt:dotagents overlays syncin a store with overlays installed,dotagents initin one without. - Existing stores keep the files an earlier
initwrote. Deletedotagents/DECISIONS.md,dotagents/README.md,dotagents/AGENTS.mdanddotagents/cmds/README.mdif you no longer want them, keeping any decisions you recorded wherever you keep notes. If you still keep a findings queue atdotagents/findings/(the queue moved tofindings/in 0.4.0), move it; until then everyfindingssubcommand reads it with--dir(dotagents findings list --dir <store>/dotagents/findings). - A project's root no longer contributes env files: a checkout's top-level
local.env/pre.local.envwere bash-sourced at every session start, so a repository that committed one ran its commands in your session. Move your local overrides to<project>/.agents/local.env(andpre.local.env), which still resolve. A project's.agents/itself stays trusted, like a harness's own.claude/settings.json; the install guide now describes what runs from where. - A project
initwrites a minimal block into<project>/.agents/AGENTS.md(a Startup line and empty sections for the project's overlays) instead of a copy of the user store's rules, which every session then read twice. Existing project blocks switch on the nextinitoroverlays add/remove/syncin that project. Anoverlays addinto a store with noAGENTS.mdcreates the block; it used to install the files and merge the routing nowhere. - On Windows, the Claude Code PowerShell-tool env loader is opt-in:
init --powershell-env-hook. It returnspermissionDecision: "allow", the only documented way to rewrite a command without prompting on every call, so it auto-approves every PowerShell tool call;init -gwired it silently. A plaininitremoves one an earlier release wired. - The system store is walked only when it exists and only administrators can
write it: on POSIX
/etc/agents(or$AGENTS_SYSTEM_ROOT) must be root-owned and not group- or world-writable; on Windows there is no default (/etc/agentswas drive-relative, and any local account could create it) and$AGENTS_SYSTEM_ROOTmust be an absolute path whose ACL grants write only to Administrators, SYSTEM or TrustedInstaller. A system store that fails the check is skipped with a warning; a missing one no longer puts itsbin/onPATH. overlays add,syncandremovelink new skills into Claude's skills directory and remove the ones that are gone, wheninithas already wired Claude for that store. On Windows, a skill is linked with a directory junction before falling back to a copy.init --dry-runreports what it would do ("would create …", "would refresh the block …"), including the wrapper scripts and the recorded base.dotagents envalso lists the overlay and storelib/directories it puts onPYTHONPATHinAGENTS_PYTHONPATH, and after acdinto another project it takes the previous project's libraries offPYTHONPATH.- Overlay setup scripts run with the scope's assembled
dotagents env(every overlay'slib/onPYTHONPATH,bin/onPATH), and command modules can import any overlay'slib/, even whendotagentsruns from a plain shell. - An
env.pyvalue must be a JSON string:nullunsets the variable, and any other type is skipped with a warning naming the key.dotagents envemits unset variables where the format has a way to (unset,set -e,$null,set "K=",null). env.pyis run asenv.py --level <level>;--agent <level>is still passed for existing scripts.- In the Python API,
get_environmentandget_diffreturn anEnvChanges(still a dict of the variables set) whoseremovednames the variables a layer unset, andget_env_from_pyno longer takesproject_root; itslevelandglobal_scopeare keyword-only. dotagents env --format yamldouble-quotes every value;iniis written for Python's configparser anddotenvfor python-dotenv.- Each installed overlay keeps a record of the repo it came from and the files
it installed.
overlays syncfetches it from that repo again, not from whichever repo now offers the name first;--reporeplaces the recorded source. overlays syncrefreshesoverlay.toml, reports files that differ from the source as kept, removes files the source dropped when they are unmodified (--prunefor edited ones), backs up whatever--overwriteor--prunereplaces, and installs requirements added upstream.overlays removerefuses an overlay another installed overlay requires, unless--force.overlays listandshowflag unmet requirements, andshownames the recorded source.- Overlay setup scripts get
AGENTS_HOMEset to the user store, plusAGENTS_SCOPE_ROOTandAGENTS_SCOPEfor the store they are installed into. - A dot in an overlay name normalizes to
-like an underscore; names must end in a letter or digit, and Windows device names are refused. - Overlays come only from
--repo, the environment repos and the stores' registries; the bundled-overlays source tier is gone. - Overlay manifests are read with
tomllib/tomliwhenever one is available. - The downloadable
dotagents.pyzis compressed: about 0.46 MB instead of 1.4 MB. build-pyzvendors only pure-Python wheels, resolved for Python 3.9, so the pyz no longer depends on the machine that built it. An extra that needs native modules (sftp) fails the build with a message saying so.- The package version is kept only in
dotagents.__version__;pyproject.tomlreads it from there. overlays add,removeandshowwithout an overlay name are a usage error (exit 2).tools/cloud-setup.shreadsAGENTS_OVERLAYS_REMOTEandAGENTS_OVERLAYS_REF; the oldDOTAGENTS_OVERLAYS_*names still work when the new ones are unset.- The findings index is rewritten atomically.
- A
---frontmatter block at the top of an overlay'sCONTEXT.mdis left out of the assembled context;overlay.tomlis the only manifest.
Removed
initwrites nodotagents/directory into a store: the base config is theAGENTS.mdmanaged block, plus the Claude include and the agents' hooks. Gone aredotagents/DECISIONS.md(the per-install design log),dotagents/README.md,dotagents/AGENTS.md, and the emptydotagents/cmds/with its README. A scope'sdotagents/cmds/is still where your own command modules go; create it when you add your first one (the docs' authoring guide shows how).init --agents codexno longer writes a static[shell_environment_policy]snapshot into~/.codex/config.toml: it pinned the projectinitran from into Codex's global config and could leave the file unparseable. Codex gets the env from itsPreToolUsehook, live. The nextinitthat wires Codex's hooks removes a block an earlier release wrote and leaves the rest of the file alone.Overlay.apply_toandOverlay.merge_rules_into; userecompose_overlay_block.Scope.system_store(useScope.system_root) andScope.cmds_dir.dotagents._scope.OverlaySource; usedotagents._sources.DirRepo.- The
src,base_agents_textandforceparameters ofAgent.write_base_config, and theforceparameter ofAgent.write_context. - The
dotagents.clire-exports ofAGENTS_DIR_ENVandBASE_ROOT; import them fromdotagents._scopeanddotagents._resources.
Fixed
- A session started in the home directory walks the user store once; it ran
every
env.pytwice and sent everyAGENTS.mdandCONTEXT.mdtwice.initandoverlaysrun from the home directory target the user store instead of writing a project block over it. contextorders overlay sources the way the managed block orders overlay rules: priority, then manifest name, then directory. An overlay with a very high priority no longer sorts after the store'sAGENTS.md.- The skills listing lets a project skill override a same-named user skill,
reads only the leading frontmatter, and joins a folded or literal
description instead of showing
>. context --inlineinlines files referenced through$<NAME>_OVERLAY_ROOT,${<NAME>_OVERLAY_ROOT},<NAME_OVERLAY_ROOT>and<PROJECT_ROOT>; resolves each source's references against its own directory; searches the project store for the project's references; and never resolves a user-store reference into the project.contextwith several agents and an output file is an error instead of keeping only the last agent.--agentsthat names no known agent exits 2, harness ids such asclaude-codeare accepted, and an empty result gets no system-reminder wrapper.--agents-dirafter--(an argument for the harnesslaunchruns) no longer changes which command modules are discovered, and a project store named with--agents-dirno longer replaces the user store's commands.- A command module that imports but whose parser cannot be built (an
unresolvable annotation,
X | Noneon Python 3.9, a duplicate flag, or a flag the umbrella owns) no longer breaks everydotagentsinvocation; it is skipped with a warning naming its file. - Inside the
.pyz, a new built-in command can no longer lose its flags and help to a hand-kept module list. findingsno longer fails on a note that is not UTF-8 (it is skipped and left untouched), a UTF-8 BOM no longer hides a finding's frontmatter, and--body-file -/--resolution-file -read stdin as UTF-8 instead of the Windows ANSI code page. Paths print as UTF-8, so a non-Latin-1 path no longer fails after the file was written.findings addrefuses the namesindexandreadme, which overwrote the finding with the index on a case-insensitive filesystem, anddoneandreopenno longer overwrite an existing file at the destination.launchexits 128+N for a harness killed by signal N, instead of 256-N.- On Windows,
launchrefuses arguments that cmd.exe would run or expand for a.cmdor.batharness (& | < > ^ % ! "or a newline) instead of passing them on rewritten. - The
init,overlayssubcommand,aboutandlaunchsummaries in--helpare whole sentences. - Private
*.local.*andCLAUDE*files in a source tree no longer reach the wheel, the sdist or a locally builtdotagents.pyz. tools/cloud-setup.shno longer deletes an existing~/.agentsthat is not a git checkout (it moves it aside and prints where), refuses to rewrite a~/.claude/settings.jsonit cannot parse, shows pip's errors, and removes its recovery hook once the store is cloned or pulled.- The docs site redeploys for each release and after a changelog-only change.
- A release refuses to publish when its tag does not match the version it built, and re-running a partially uploaded release no longer fails.
- The Windows PowerShell handlers that
initadds beside Claude Code's bash hooks decide whether to run the way Claude Code decides which shell to use. They checked forbashon PATH: a default Git install (onlyGit\cmdon PATH) got the context twice, and a box with WSL'sbash.exestub and no Git Bash got none. - The Claude Code and Codex hooks run
dotagentsby path instead of putting.agents/binand the store'sbinon PATH, whichenv --diffthen persisted into the session: after acdthe first project'sbinstayed ahead of everything. - The PowerShell env loader (
init --powershell-env-hook) runs only for PowerShell tool calls, not before every tool call, and readsdotagents envas UTF-8: a non-ASCII value arrived garbled. - Re-running
initkeeps keys you added to a dotagents hook entry (atimeout, say) and no longer deletes malformed hook entries from your settings. dotagents' hooks are labelleddotagents: ..., so an unrelated hook with the same generic label is no longer mistaken for one of them. - A
bin/dotagentswrapper run through a symlink finds its.pyzbeside the real file, anddotagents.cmdworks when the Python interpreter's path is not ASCII (a user folder with an accented name, say). - Re-running
initon a store with overlays installed keeps their rules and routing inAGENTS.md's managed block; it used to rewrite the block from the bare base until the nextoverlays sync. - A base chosen with
init --fromis recorded in<store>/dotagents/config.toml, and a later plaininitand everyoverlays add/remove/synccompose over it; they used to switch back to the bundled base. Delete the file'sbaseline to return to the bundled one. init --forcekeeps the original of every file it replaces. Adapters that write the same file in one run share oneinstall_backup/<timestamp>/, a backup is never overwritten, and a file outside the store is backed up underexternal/at its own path instead of over a same-named one. pi's ownAGENTS.mdis block-merged even under--force, like Claude's include.- Adding an overlay's routing no longer deletes the base block's line about a
named agent's
~/.agents/<agent>.md. - The managed-block merge ignores marker lines inside fenced code (an example of the markers in your notes no longer pairs with the real block), and finds a block behind a UTF-8 byte-order mark instead of adding a second one.
- A checkout's own top-level
dotagents/cmds/is no longer a command source; only the stores'dotagents/cmds/(and overlays'cmds/) are. initfrom a plain (pip) install writes<store>/bin/dotagents[.cmd]wrappers running"<python>" -m dotagents; it wrote none, and on Windows the PowerShell hooks called a<store>\bin\dotagents.cmdthat did not exist, so PowerShell sessions got no env and, without bash, no context. Wrappers left pointing at a.pyzare replaced, with a warning. The PowerShell hooks also fall back todotagentsonPATHand do nothing when there is none.contextfor Claude includes a project's committed rootAGENTS.mdwhen a CLAUDE file (such as the.claude/CLAUDE.mda projectinitwrites) stops Claude from reading it itself; it used to assume Claude always read it, so the file reached neither. Claude's AGENTS.md is counted as loaded only when a CLAUDE file@-includes it or none exists at or above its directory.- The Claude and Codex hooks put the project's
.agents/binonPATHas an absolute path. The relative.agents/binthey used was re-emitted into the session'sPATHbydotagents env, where it resolved against whatever directory a later command ran in. Re-rundotagents initto update the hooks. - Codex's SessionStart hook runs
dotagents context --agents codex; without the flag it assembled Claude's context and gave Codex nothing. It is now a small Python script, like the PreToolUse one: on native Windows the shell line ran as cmd.exe'sPATHbuiltin and did nothing. Both Codex hooks run under the interpreterinitran with, not a barepython3/python. - Codex's PreToolUse env loader no longer breaks every shell command on native
Windows: Codex runs PowerShell there, which cannot parse its POSIX prefix, so
the hook now passes commands through unchanged (Windows Codex sessions get the
context, not the live env). Re-run
dotagents init --agents codexto update the hooks. launchno longer merges your context into a harness's instruction file that git tracks, or under-g: for an agent with no append flag (Codex, Gemini, Cursor, Copilot, pi on Windows) it wrote the user store's rules into a usually committedAGENTS.md/GEMINI.md/.cursorrules. The context stays at$AGENTS_CONTEXT_FILEwith a warning;launch --write-agentmerges it anyway. The harness is resolved first, so a launch that fails with "not found" writes nothing.context --write-agentrefuses-g.- The Claude
CwdChangedhook quotes the directory it pins into$CLAUDE_ENV_FILE: a'in a directory name broke every later Bash command of the session, and a crafted name ran commands. Re-rundotagents init. env --format powershelldoubles every character PowerShell reads as a single quote ('and U+2018–U+201B): a’in a value ended the string, and the rest ran as code in the PowerShell loader. A variable whose name contains}or a backtick, which${env:…}cannot hold, is left out.- Plain env files under a path with non-ASCII characters,
$or backticks are sourced; they were silently skipped (and$/ backticks in the path expanded). envwithout--diffno longer prints inheritedDOTAGENTS_*values, the tool's own secrets.- A git overlay spec whose ref or location starts with
-is rejected: it was passed to git as an option (@--upload-pack=<command>ran the command). initwrites the store'sAGENTS.mditself, whatever agents are named; with--agents gemini,cursor,copilotit wrote none, and putGEMINI.md,.cursorrulesand.github/copilot-instructions.mdinside the store, where no harness looks. Gemini CLI now gets an@import of the store'sAGENTS.mdin~/.gemini/GEMINI.md(or the project'sGEMINI.md); Cursor and Copilot get the context throughcontext --write-agentorlaunch. An unknown--agentsname is an error instead of a warning.- Adapters are told the scope
initresolved instead of guessing it from$AGENTS_HOME:init -g --agents-dir <dir>now writes Claude's include and hooks to~/.claude, not beside<dir>. Claude's config goes to$CLAUDE_CONFIG_DIRwhen that is set. - A project-scope
initno longer edits Codex's or Antigravity's global hook config (it turned dotagents on in every workspace); runinit -gfor them. Antigravity's hook runs under the interpreterinitran with. context --write-agent/launchwrite Cursor's context to.cursor/rules/dotagents.mdcwithalwaysApply: true(.cursorrulesis legacy), and Cursor's and Copilot's own reading of the project'sAGENTS.mdis no longer sent twice.- Claude's
@imports are followed the way Claude reads them: anywhere on a line outside code spans and fences, at most four hops deep, and in the CLAUDE files of the project root's ancestors too, so neither an inline import is sent twice nor a fenced example dropped. envstamps no agent identity in a plain shell: a repository with anAGENTS.mdmade it exportAGENT=codexto any shell that sourced it.- The Claude include is skipped when the entry file already includes the same
file under another spelling (
@~/.agents/AGENTS.md, an absolute path). - A project
initwarns when it writes the Claude include (or Gemini's) into a file git does not ignore. tools/cloud-setup.shinstallsdotagents-cliby default; thedotagentsname it used is not this project on PyPI.overlays add/syncnever delete a skill you placed or edited in the sharedskills/dir. A same-named skill already there is kept, with a warning; a published copy you edited is kept onsyncunless you pass--overwrite, while an untouched copy still follows its overlay (the digest of each copy is recorded inskills/.dotagents-published.json).- An overlay installed from a whole repository no longer copies the clone's
.git, nor tool caches (__pycache__,.mypy_cache,node_modules, …), into the store.overlays removedeletes read-only files (git objects on Windows) and unlinks a symlinked overlay instead of failing half-way; an overlay that already carries a.gitcopy is removed cleanly. dotagentswith no subcommand logs its hint instead of raising when no parser has been built.dotagents envputs the dotagentsbindirectories at the front ofPATHin their precedence order even when the calling shell already had some of them, and drops empty and relativePATHentries, which resolved against whatever directory a later command ran in.init --fromaccepts the forms the docs list: a dotagents checkout, afile:,http(s):,sftp:,s3:orzip:URI, and a git repository (repo[@ref][#path]). Remote sources are fetched into the overlay cache, failures are one-line errors instead of tracebacks, and the recorded base never stores credentials from the URL.- Writing
settings.jsonorhooks.jsonkeeps a symlinked file a symlink and updates its target, and keeps the file's permissions. initaccepts asettings.jsonsaved with a UTF-8 byte-order mark, and a corrupt settings file stopsinitbefore it writes anything instead of leaving a half-initialised store.- On Windows,
dotagents launchnever runs a harness from the current directory (aclaude.cmdat a repository's root, say). dotagents launchno longer passes the harness a variable an env layer unset;--dry-runlists them on anunset:line.- A skill copied into Claude's skills directory is refreshed when the overlay's skill changes (it was reported as a conflict and stayed stale), and removing an overlay no longer leaves a dangling link or stale copy of its skills.
- A project-scope
initwarns when skills it links into.claude/skills/are not gitignored. - On Python 3.9 and 3.10,
tomliis installed as a dependency and bundled in the pyz, so overlay manifests and TOML registries are read by a real TOML parser. - The pyz no longer ships pip's
bin/launchers, which carried the builder's interpreter path, and filesbuild-pyzgenerates are LF on every OS. - Plain env files are sourced by Git's bash on Windows, not the WSL launcher,
which dropped them whenever WindowsApps came first on
PATH. - Git Bash's own rewriting of
PATH,HOME,TEMPand similar variables is no longer reported as an env file's changes, and aPATHan env file extends comes back in Windows form. - A malformed
env.py(output that is not UTF-8, a key with=, a value with NUL, or any exception) no longer crashes the whole env assembly. dotagents env --agents-diremits the store it actually walked asAGENTS_HOME, and emitted roots are always absolute.- The export format uses plain POSIX single quotes for every value, including ones with control characters.
PATH-shaped values are converted between Windows and POSIX form only on Windows, so pwsh on Linux keeps itsPATH.- Values in the yaml, ini and dotenv formats read back as given.
- A login fish shell is detected on macOS.
- On Windows, the system-store permission check works when dotagents runs from PowerShell 7, and runs System32's PowerShell rather than one found in the current directory.
- A repo that cannot be loaded is an error instead of "overlay not found":
syncno longer skips with success,addno longer installs without a dependency, andlistwarns per broken repo. A malformed or non-UTF-8 registry gives a clean error instead of a traceback. - Credentials in an http(s) registry URL no longer appear in a traceback; they
are sent as Basic auth and never forwarded on a redirect. URL query tokens
are masked in messages, cache directory names never contain credentials,
and cached git checkouts do not store them in
.git/config. - The source cache under the user store is ignored by git, so a store kept in git never records a cached checkout.
- An
@in a local path or non-git URL is kept; a#in a store path no longer breaks registry loading; relative git entries and relative entries of s3, github and zip registries resolve beside their registry; the standard-library http fetch honours#path. - Manifest escapes, underscored integers and
[table]sections are read correctly;requiresnames andrulespaths can no longer point outside the overlay; an unreadable rules file is a warning instead of a crash that blocked every lateraddandsync. overlays addrolls back a new install whose setup fails and updatesAGENTS.mdfor what did install;overlays removeunpublishes skills only after the overlay is deleted and always updatesAGENTS.md.overlays addandoverlays syncrefuse a source inside the store's ownoverlays/directory, where a laterremovewould delete the only copy.- An
overlaysdry run says it would recompose or createAGENTS.mdinstead of reporting it as done. - On Windows,
initnames the PowerShellPreToolUsehook only when--powershell-env-hookwired it. - Overlay routing lands before the managed block's real end marker even when the base template shows the marker inside a code fence.
0.5.1 - 2026-09-12
Added
dotagents about:dotagents-cli <version>and then, one per line, every package bundled with it and its version -- from a.pyzexactly whatbuild-pyzvendored (recorded indotagents/_bundle.jsonat build time), from a plain install the dependencies present.--jsonadds where it runs from and the Python.- Overlay sources and repos can be any path
pathlib_nextspeaks. With theuriextra (dotagents-cli[uri];[http],[sftp],[s3]add the schemes' clients) anhttp(s)://directory listing, ansftp,s3,davorgithubtree is a directory of overlays or one overlay, a file is a registry, an archive (zip:<archive-uri>!/<inner>,tar:,archive:, the archive local or at a URL) is a tree too, and a relative registry entry resolves against the registry's URL; remote content is materialized into<user store>/.cache/overlays/uri/once per run.file://is a local path. Without the extra anhttp(s)://registry file still works through the standard library, and anything else names the extra it needs.build-pyz --extras uri,httpvendors the extra into the zipapp.GitCacheis nowSourceCache.
Changed
- The base config's first line, "Startup: annotate that you read
…", names the actual path of theAGENTS.mdit is in (initrenders it per store, and everyoverlays add/remove/syncrecompose keeps it), not a literal~/.agents/AGENTS.mdthat a relocated store or a project's own file never was. The base overlay isdotagents/only: the block template isdotagents/templates/AGENTS.md,<store>/dotagents/AGENTS.mdis the working notes for that directory (read when working in it, not by default),<store>/dotagents/README.mdthe user-facing one, andinitno longer writes a<store>/CLAUDE.mdor a store-root README (no harness read the former; the includeinitwrites into~/.claude/CLAUDE.mdis the real one). Existing stores keep the files they have. - The example overlays' branch is
repo(wasoverlays); its layout is the same,overlays/<name>at the branch root. Specs that named the branch change one word:https://github.com/jose-pr/dotagents.git@repo#overlays/net. The cloud bootstrap'sDOTAGENTS_OVERLAYS_REFdefault follows.
0.5.0 - 2026-09-10
Added
- Overlays can come from git repositories and registries.
overlays add/sync/list/showresolve a name against repos in order, the first that offers it winning:--repo <spec>(repeatable),$AGENTS_OVERLAYS_REPO_<KEY>(one repo per variable, by KEY),$AGENTS_OVERLAYS_REPO(the default repo), the project store'sdotagents.{json,toml,yaml,yml}, the user store's, then the bundledoverlays/. A repo is always a collection of overlays: a directory (each subdirectory an overlay) or a registry file mapping names (or aliases) to sources; it lives at a local path, anhttp(s)://URL (a registry, fetched) or inside a git repository named<repo>[@<branch|tag|commit>][#<path>], the path picking the directory or registry file in the checkout. A source is one overlay: a local directory, or a git spec whose path is the overlay's root (no path: the repository root is the overlay); a relative path is relative to the registry file, and for a registry inside a git checkout it names the same repository at the same ref. Checkouts are cached under<user store>/.cache/overlays/and refreshed on every use; credentials in a repo URL never reach a log line. dotagents launch [<agent>] [-- <the agent's arguments>], a bundled command module likefindings: starts an agent's CLI (claude,codex,gemini,cursor-agent,copilot,pi, or--command <program>) with the fullenvassembly exported to the process and the child, and the assembledcontextwritten to a file (exported asAGENTS_CONTEXT_FILE) and handed over as appended system-prompt text where the harness takes one (Claude Code:--append-system-prompt-file), or merged into the harness's own instruction file as the manageddotagents:contextblock otherwise. The program is resolved on the exported PATH, so a harness an overlay'sbin/provides is found;--dry-runprints the command line and the exported names;--no-contextand--inlineshape the context step; the exit code is the harness's. Adapters gainedlaunch_commandandlaunch_context_args().- The
piagent (pi.dev,@earendil-works/pi-coding-agent): detected by its documentedPI_CODING_AGENTmarker, model fromPI_MODEL, no vendor (it is multi-provider). Its config dir ($PI_CODING_AGENT_DIR, else~/.pi/agent) and the project'sAGENTS.md/CLAUDE.mdcount as loaded by the harness;context --write-agentandlaunchhand the context over through.pi/APPEND_SYSTEM.md, the file pi appends to its system prompt, andlaunchuses--append-system-promptdirectly on POSIX.init -gputs a managed pointer to the store'sAGENTS.mdinto the config dir'sAGENTS.md(pi has no include syntax), never a copy of the block. envexportsAGENTS_PYTHON: the interpreterdotagentsitself is running under (sys.executable), seeded only if unset like the scope roots. Shims and helper scripts now have a default Python they can trust, where a barepython/python3on PATH may be a Store alias stub, an emulated build, a venv's, or missing.
Changed
overlays addno longer re-installs and re-runs the setup of a requirement the scope already has (in its own store, or the user store when adding to a project): it is satisfied and left alone, and the log says so. Each missing requirement is still installed and set up before the overlay that needs it; a name asked for explicitly is (re)installed and set up as before.
Removed
overlays --sourceand$AGENTS_OVERLAYS_SRC: a source directory is just a repo now —--repo <dir>/$AGENTS_OVERLAYS_REPO=<dir>.- The one-release
DOTAGENTS_*fallbacks:DOTAGENTS_AGENTS_DIR,DOTAGENTS_OVERLAYS_SRC,DOTAGENTS_CMDS_PATH, and the duplicateDOTAGENTS_AGENTS_DIR/DOTAGENTS_OVERLAY_DIRan overlay's setup script received. Only theAGENTS_*names are read or set. - The extensionless POSIX
setupscript an overlay could ship:setup.pyis the one form, run under the interpreter running dotagents on every platform. - The precursor's lowercase
session_starthook key is no longer migrated.
Fixed
initno longer registers the PowerShell hook variants on POSIX hosts, and removes the ones an earlierinitwrote there. Ashell: powershellentry on Linux or macOS is not harmless: Claude Code runs the text as bash and it fails with a syntax error on every session (measured in WSL). Linux and macOS get the bash handler only; Windows keeps both, and the PowerShell one still selects itself only whenbashis absent. Re-runninginit -gon an existing POSIX install converges.- Command discovery is resilient per module, not per directory: one broken
file in a
cmds/dir (a syntax error, an import-time exception, or asys.exit()— which is not anExceptionand used to escape the guard and kill every invocation,init -gfor a store that did not exist yet included) is skipped with a warning that names the file, and its siblings still load.
0.4.0 - 2026-09-09
Added
dotagents findings— a per-scope findings queue, shipped as the one bundled command module (_overlay/dotagents/cmds/findings.py). A finding is one markdown file shaped like an agent memory (frontmattername/description/status/createdfor the index line, details in the body) under<scope-root>/findings/: the project's.agents/findings/by default, the user store's with-g, anywhere with--dir.addrecords one (--body/--body-file,-= stdin),listprints the active ones (--all,--processed,--json),showprints one (--json),doneappends a required## Resolutionand MOVES the file toprocessed/(never deletes),reopenmoves it back,removedeletes a mistaken one,indexregeneratesINDEX.md(active first, then processed — every mutating command rewrites it),pathprints the queue's location. Hand-written notes without a frontmatter are listed too and gain one when first rewritten. Files are written LF-only on every platform.envprepends every level's existinglib/dir toPYTHONPATH, the way it already prepends every level'sbin/toPATH— overlays first, then the store, then the project's.agents/, never the project root — and does so before the env-file chain, so anenv.py(and every subprocess that inherits the env) canimportan overlay'slib/module. Only dirs that exist are added, andPYTHONPATHis untouched when there are none. The formatter already converts any*PATHvariable between Windows and POSIX forms, soPYTHONPATHgets the same treatment asPATH.envemits one<NAME>_OVERLAY_ROOTper installed overlay (the overlay's install dir), seeded before the env-file chain like the two scope roots and, like them, only if unset.NAMEis the overlay's directory name upper-cased with every non-alphanumeric character turned into_(my-ov.v2→MY_OV_V2_OVERLAY_ROOT). This is the same namecontextalready expanded as a<NAME_OVERLAY_ROOT>placeholder; the two now share one naming function, so an env file and a context file name an overlay's install dir identically. The variable is derived from the same normalized nameoverlays addinstalls under (Overlay.normalize_name), so the var foroverlays/<n>/is alwaysOverlay.root_var_for(n).
Changed
- The overlay-root note
overlays addwrites above routing lines cites$ENGINEERING_OVERLAY_ROOTas its example; the docs and CI install theengineeringoverlay, which now carries the flows, references, and tools that were separate overlays (each is a category dir under it, never<name>/<name>/). - The contract-A walk is
Scope.paths(*names, include_missing=False); the_resolvemodule (and its API docs page) is gone, itsLEVEL_NAMESlive in_scope. - The system store is a store like the others.
Scope.system_root(/etc/agents, or$AGENTS_SYSTEM_ROOT) headsScope.stores, and the contract-A walk goes store by store -- system, user, project -- each store's overlays first, then the store itself, then the project root; the hardcoded/etc/agentstier and its odd position (after the user store's overlays, before the user store) are gone. A system overlay is shadowed by a same-named user or project one.overlays listprints oneinstalled (<level>)block per store that has anything, most specific first. - The library walk takes one object.
Scopenow carries the whole picture (user_root,project_root,stores,overlays,files(*names)), andget_file_paths,get_environment,get_diff,resolve_env_files,get_bin_paths,get_lib_paths,get_overlay_roots,assemble_contextandassemble_context_datatake aScopeinstead of the(agents_dir, project_root, global_scope)keyword triple ported from the precursor.Scope.of(agents_dir=, project_root=, global_scope=)builds one from resolved parts. No compatibility form: the CLI is the public surface. - One overlay name, two scopes = one overlay, the project's. A project
session uses the user store's overlays AND the project's (
<project>/.agents/ overlays/); an overlay installed in both under the same name is the project's copy only, which shadows the store's for bin, lib, env files, cmds,CONTEXT.mdand the<NAME>_OVERLAY_ROOTvar (a store root the session had already pinned is re-pointed).-gsees the user store alone.Overlay.installed(*stores)is the one discovery behind the contract-A walk,env,contextandoverlays list/show; the_scopediscover_overlayspass-through is gone (Overlay.discover(root)is the single-root form).overlays listin a project scope prints bothinstalled (project)andinstalled (user), marking shadowed store copies;overlays showfalls back to the user store's copy before the source. - Overlay routing lines refer to overlay files through
$<NAME>_OVERLAY_ROOT(the variableenvexports per installed overlay), not a hard~/.agents/path; when a merged block carries such lines,AGENTS.mdgets one line above them saying what the token is and how to resolve it. The example overlays on theoverlaysbranch follow this convention and ship no setup scripts any more (PATH, PYTHONPATH and the root var areenv's job; an overlay's own env belongs in itsenv.py).tools/cloud-setup.shfinds the private-sync settings snippet inside the installed overlay. - The base overlay's findings workflow now goes through the command. The
managed
AGENTS.mdrule ("Global-config misses"),dotagents/DECISIONS.md("How findings become config") and the skeleton README saydotagents findings add -g ...to record a miss,list -g/show -gto triage anddone -g <name> -r ...to close one, instead of "drop a note in~/.agents/dotagents/findings/". With that, the user store's queue is~/.agents/findings/(the command's-gdefault), no longer~/.agents/dotagents/findings/. An existing queue at the old path keeps working with--dir ~/.agents/dotagents/findings, or move it once:mv ~/.agents/dotagents/findings ~/.agents/findings. Re-rundotagents initto refresh the managed block. dotagents._overlaysis now built around anOverlayclass — one overlay is one directory, and everything that depends on a single overlay is a method or property on it:.path/.name/.normalized_name/.root_var/.is_valid/.manifest_path,.read_manifest()/.priority/.sort_key,.find_setup_script()/.run_setup(...),.files()/.rule_blocks(...)/.apply_to(...)/.install_to(...)/.merge_rules_into(...). The name rules are static methods (Overlay.is_valid_name/.normalize_name/.root_var_for), so a caller holding only a name uses the same rule as one holding a directory;Overlay.discover(root)is the single discovery routine that_scope, the contract-A resolver andenvall share, andOverlay.sort_by_prioritythe single merge order. The module-level functions they replace (read_manifest,find_setup_script,run_overlay_setup,overlay_files,rule_blocks,apply_overlay,install_overlay_dir,merge_overlay_rules,overlay_sort_key,sort_overlays_by_priority,normalize_name,overlay_root_var) are gone, as are_scope.is_valid_overlay_nameand_scope.normalize_overlay_name.recompose_overlay_blockstays a module function (it works over a set of overlays) and now acceptsOverlayinstances or directories._overlaysis a private module, so no public version signal.initno longer copies bundled command modules (*.py) from the package'sdotagents/cmds/into the store — only the README. The bundled dir is always a discovery source, so a copy added nothing, and being create-if-absent it would have pinned the first-installed version offindingsand shadowed every later one. A same-named module dropped into a scope'sdotagents/cmds/still overrides the bundled one.
Fixed
- One broken command module no longer breaks every
dotagentscall. Discovery skips a source that fails to import for any reason (aSyntaxError, an exception at import time) with a warning naming it. It caughtImportErroronly, and duho propagates the rest on purpose, so a typo in~/.agents/dotagents/cmds/foo.pymadeenv,context,initand even--versiontraceback, i.e. the hooks delivered nothing. - No more temp-directory litter from the
.pyz. Everything a zipapp run extracts (package data, the repointed module sources) lives under one per-process scratch directory removed at exit; amkdtempper item with no cleanup had left 632dotagents-*directories in one machine's%TEMP%. overlays addinstalls a source dir spelledmy_overlay(it normalized tomy-overlayand looked only that up), validates and resolves EVERY name against the source before touching anything (add good badused to installgood, run its setup, then fail), installs each manifest'srequiresfirst (transitively;--no-requiresto skip; a missing requirement warns, a cycle errors), publishes skills from the INSTALLED copy (a symlink into the source dies with a temporary checkout and could never be matched byremove), and reports the setup script on a dry run.overlays removenormalizes names (add My_Ov+remove My_Ovsaid "not installed") and recomposes the managed block over the overlays that remain, so an overlay's rules and routing leaveAGENTS.mdwith it; the warning that pointed at the removeddotagents installis gone.overlays synchonours--copy(declared and ignored) and gains--overwrite, which replaces installed files whose content differs from the source; without it a sync never updated an upstream change to an existing file.- New
overlays show <name>describes an overlay (installed copy first, else the source): description, priority, requires, routing, rules, setup script, skills, file count, root var;--json. - The manifest reader strips trailing
#comments quote-aware and finds an array's closing]by scanning, sorouting = ["a"] # noteno longer swallows the NEXT array (a rules path became a routing line), an indented]no longer yields[],'single-quoted'strings parse, andpriority = 5 # lowis 5, not 500.descriptionandrequiresare read. _compose_blockwith a base that has no## Load on demandheading appends the overlay rules at the end of the block, as its warning always claimed; they were dropped.build-pyzoutside a source checkout is a clear error instead of aFileNotFoundError.dotagents,overlaysandfindingsinvoked with no subcommand print their help and exit 2 instead of logging a hint and exiting 0.--agents-dir Xon the command line is honoured by command discovery, so the store the command is about to use is the one whosecmds/are found.findings addrejects a name already carried by another note's frontmatter (getmatches frontmatter names first, sodonewould have processed the wrong file).- Two overlay dirs with the same manifest
namesort deterministically (dir name is the final tiebreaker). - SessionStart context is no longer injected twice. The PowerShell
variants of Claude's
SessionStart/CwdChangedhandlers run only whenbashis not on PATH. Both handlers fire on every session, and on a Windows box that has both Git Bash and PowerShell both succeeded, so the same payload landed twice at every session start (two identical 100 KB payloads, measured). - The Codex
PreToolUseenv-loader prefix is quoted correctly: inside"$(...)"its\"were literal quote characters, soPATHbecame".agents/bin:...:<last>"with the quotes, the project.agents/binwas never found and the last original entry was broken. The rewritten command is now executed in bash by a test. - Hook merging keeps a user's hook that shares a matcher-object with an older
shape of ours (the whole object used to be dropped), and a revised
shell/matcher/commandWindows/ status on an unchanged command text now reaches existing users instead of being kept as-is. - Claude's skills link is per skill into
<config>/skills/<name>: linking the whole directory failed with "conflict" for anyone who already had their own~/.claude/skills, so overlay skills never reached them. A same-named skill the user placed there stays. - Every hook command resolves the store as
$AGENTS_HOMEwhen set (bash:${AGENTS_HOME:-$HOME/.agents}), so a custom store gets hooks that can finddotagents; the PowerShell SessionStart variant prefers the project's own.agents\binlike the bash one. The PowerShell tool env-loader runsenv --diffinstead of re-assigning the whole environment on every call. - The bash
CwdChangedhandler re-pinsAGENTS_PROJECT_ROOTinto$CLAUDE_ENV_FILEwhen the new directory carries a.agents/; the SessionStart pin is only-if-unset, so acdinto another project used to keep the first project's root for the rest of the session. - The Antigravity hook pins the project root from
workspacePaths(cwd andAGENTS_PROJECT_ROOTof thedotagents contextspawn), as its docstring already claimed, honours$AGENTS_HOMEwhen locatingdotagents, and injects nothing when the assembly exits non-zero. Its constant isPREINVOCATION_HOOK_SCRIPT(it wires aPreInvocationhook). - Unpublishing an overlay's skills compares file CONTENT, not just names: a copy the user had edited (same file set, different bytes) was deleted as the overlay's.
- The store's rules now reach Claude on a fresh install.
initwrites the@include where Claude Code reads it --~/.claude/CLAUDE.mdfor the user store,<project>/.claude/CLAUDE.mdfor a project -- as an appended managed block, skipped when the include line is already there by hand. Before, it wrote<store>/CLAUDE.md(which Claude never reads) whilecontextsubtracted~/.agents/AGENTS.mdas "already loaded" on a static assumption, so nothing delivered the base rules.contextnow subtracts exactly what the harness's entry files actually@-include (Agent.loaded_paths), recursively, on this machine. contextno longer inlines every.mdfile the sources mention. Inlining is opt-in (--inline/inline=True): the base rules say to read those files only when a task needs them, and inlining every mention made a 100 KB SessionStart payload, most of it a changelog and an API header that happened to be named in prose. With--inline, sources and harness-loaded files are never inlined a second time, and placeholders inside inlined files expand.- Overlay priority ordering and
<NAME_OVERLAY_ROOT>placeholder expansion incontextnever worked: the resolver labels an overlay entry with the overlay's name and the code compared it to the literal"overlay". Every installed overlay now gets a placeholder (matchingenv), not only one that ships aCONTEXT.md. context --write-agentmerges the context into the harness's own instruction file under the PROJECT root -- Claude.claude/CLAUDE.md, CodexAGENTS.md, GeminiGEMINI.md, Cursor.cursorrules, Copilot.github/copilot-instructions.md, Antigravity.agents/rules/dotagents.md-- as a manageddotagents:contextblock that is refreshed in place. It used to overwrite a file under the user STORE; for Codex that was<store>/AGENTS.md, a context source, so every run re-inlined the previous run's output.--formatis validated, and--write-agentrefuses--format jsonor an output path instead of silently ignoring one.- Project-scope overlays (
<project>/.agents/overlays/, whereoverlays addinstalls by default) are now part of the contract-A walk: theirbin,lib, env files,cmdsandCONTEXT.mdresolve, after the user store's, and each gets a<NAME>_OVERLAY_ROOT(a project overlay wins a name clash). Nothing consumed them before except theAGENTS.mdrecompose. - Managed-block markers must be a line of their own: a prose MENTION of the
markers (the base
AGENTS.mdcarries one) was matched as the block, so the sentence around it was replaced and a second block appeared. A begin marker with no end after it is refused with a clear error instead of gaining a second block; a--frombase without markers is a usage error, not a traceback. - Every managed file (
AGENTS.md, the includes,settings.json,hooks.json, context targets) is written LF-only on every platform;settings.json/hooks.jsonare written atomically and keep non-ASCII values as-is. - The user scope is recognized by the configurable store (
$AGENTS_HOME), not the literal~/.agents:init -gwith a custom store used to wire Claude's hooks into<store-parent>/.claude/settings.local.json, which nothing reads. CODEX_HOMEno longer marks a running Codex session (it is the user's persistent state-dir override, exported from a shell profile); theCODEX_SANDBOX*vars still do, andCODEX_HOMEstill locates the config.- An unknown
--agentsname no longer overrides a pinned identity inenv. - An overlay cannot be named like a contract-A level (
user,project,system,project-root,default,overlay); its dir name is its level label in the walk and would collide with the per-level filename keys. - A directory named
env(a common virtualenv name) is no longer "sourced" as an env file; only regular files resolve. - Sourcing a plain env file that fails (missing, a directory, a syntax error)
now contributes nothing and warns, as documented. The bash command was a
;list, soenv -0ran regardless and reported rc 0 with whatever had been assigned before the error. - Bash's own
PWD(in/c/...form),OLDPWD,SHLVLandMSYSTEM*are no longer reported as a sourced file's changes; a non-UTF-8 byte in a sourced value no longer aborts the whole assembly. env --format autoreturnedcmdwhen invoked through thedotagents.cmdwrapper from PowerShell (the wrapper cannot exec, socmd.exesits between Python and the shell); acmdwhose own parent is a shell is now skipped.envformats:fishescapes backslashes;cmddoubles%and flattens newlines;yamlquotes values a reader would type (true,123,null,1e3, leading indicators);powershell/cmdno longer mangle a forward-slash native path (C:/a/toolsbecameC;A:\tools); output is written as UTF-8 bytes so a non-Latin-1 value cannot crash on a cp1252 console.init -g,overlays ... -gand every otherresolve_scopecaller honour$AGENTS_HOME(the varenvitself emits) instead of the literal~/.agents, and--agents-diroverrides the store in the project scope too (it was silently ignored without-g).resolve_user_storemoved to_scope(still re-exported fromdotagents.cli); thefindingscommand's local workaround is gone.overlays list/overlays addapply the one overlay-name rule when listing a source (__pycache__,2fast, dotdirs are no longer "available"), and a bad source path names whether it came from--sourceor the env var.findings list/showwrite UTF-8 bytes, so a description with a non-Latin-1 character no longer raises on a cp1252 console.- An
env.pymay now print its changes as one JSON object per line, merged in order (a later line wins), as well as a single object. Each overlay'ssetup.pyappends its own managed block to the store'senv.pyand each block prints its own object, so a store with two such overlays (e.g.net+private-sync) emitted two lines — and the single-object reader rejected the whole output, silently dropping both overlays' vars. A line that is not a JSON object still voids the whole script's contribution, so a half-applied change set is impossible. init --agents codexrun from a session whose environment already carried dotagents' identity vars (which its own env-loader hook exports —AGENT=claude-codeand friends in every command a Claude session runs) wrote a Codex env block with no identity at all: the identity stamp never overrides a value already present, so every key counted as "already set", to Claude's values. When the agent is named explicitly the identity is now that agent's, overriding the base env; without an explicit agent a pinned value is respected as before.- The built
.pyzlost the help text of the umbrella's own--cmdspathflag (every subcommand's help survived). The zipapp source-repoint shim covered eachdotagents.cli.<x>command module but not thedotagents.clipackage itself, and it resolved a package's source ascli.pyinstead ofcli/__init__.py, so the umbrella kept its zip-internal__file__and duho fell back to a bare flag. Both fixed; the top-level help in a.pyznow matches a plain install. context's<NAME_OVERLAY_ROOT>placeholder now also maps.(and any other non-alphanumeric character) in an overlay name to_, not only-— the name has to be a legal shell variable to be emitted byenv, and the placeholder follows the same rule. Overlays whose names contain only letters, digits,_and-are unaffected.
Security
env --format export(the form the SessionStart hook writes into$CLAUDE_ENV_FILE) now single-quotes every value ('→'\'', control characters via bash's$'...'). Values were JSON-quoted, i.e. inside DOUBLE quotes, so$(...), backticks and$VARin any env value were executed or expanded when the file was sourced, while\nand non-ASCII (\u00e9) arrived as literal escape text.- The env chain no longer executes or sources a project's own top-level
env.py/env: only<project>/.agents/*and the user-locallocal.env/pre.local.envrun at the project levels. Every session start ran the chain, so opening a session in a cloned repository with a top-levelenv.pywas code execution from that checkout.
0.3.4 - 2026-08-16
Changed
- Raise the dependency floors and scope them to a minor series —
duho>=0.5.0,<0.6(was>=0.4.0) andpathlib_next>=0.9.0,<0.10(was>=0.8.0). Both are pre-1.0, where a minor bump is the signal that the documented API broke, so the ceiling is what keeps the next one from arriving unannounced. Neither floor sits above its.0patch: the suite and a CLI smoke pass at exactly duho 0.5.0 and pathlib_next 0.9.0, so nothing here needs an API added later in either series. The one 0.5.0 behavior change that reaches this CLI's surface is list-typed option fields taking one value per occurrence instead ofnargs="*"—--agentsand--cmdspathare unaffected, since the documented forms are the comma list (--agents a,b, split by the command itself) and the repeated flag, both of which behave the same either way. Exercised end to end against duho 0.5.4 / pathlib_next 0.9.2 on Python 3.9 and 3.14. The[uri]/[http]/[sftp]/[s3]extras stay unversioned passthroughs topathlib_next's own extras. build-pyz's vendored pins moved with those floors — the.pyznow bundles duho 0.5.0 and pathlib_next 0.9.0 (was 0.4.0 / 0.8.0), the minimum the package claims to support rather than the latest patch. These pins are a second copy of the dependency versions and had drifted a full minor series behind, so the shipped zipapp bundled versionspip install dotagents-cliwould have refused. The rebuilt.pyzkeeps full flag/help/positional fidelity through the zipapp shim.
0.3.3 - 2026-08-16
Added
envandcontextgained--agents-dir(from the sharedDotAgentsArgsbase) to override the store for one run. Their-g/--globalkeeps its existing, narrower meaning here — skip the project-level files — and now says so in--help.
Removed
build-pyzno longer bundles the repo'stools/asdotagents/_toolsinside the built.pyz, and the--tools-dirflag is gone with it. Nothing read_tools: the compiledauditwrapper and a personal scanner's wrapper that shelled out to it no longer exist. Every shipped artifact carried the dead weight whiletools/audit.pyclaimed it was "not shipped in the.pyz" — now true.dotagents._sync— apathlib_next.PathSyncerwrapper that existed only to back theinstallsubcommand's backup/copy report.installwas removed in 0.3.x; the module has had no callers since, no tests, and a return annotation that disagreed with what it returned.pathlib_nextremains a dependency (duho, and--fromURI support). Its API-reference page went with it.
Fixed
- A cluster of "docs say X, tree does Y" corrections —
tools/audit.pyno longer claims to ship as a bundledauditcommand module (it is repo CI tooling and there is nodotagents audit) and its--roothelp names the real default;install.py's usage line drops the removedinstalland the never-existingaudit; the README'stools/row says where a personal command module actually lives; the API header names the real_overlaysexports (install_overlay_dir/apply_overlay/run_overlay_setup) and the real package-data dirs (_overlay/_overlays_src, never askeleton/); and_merge._extract_block's docstring now says it returns the block including its marker lines, which is what it has always done and what callers rely on. stamp_identityno longer pretends to emitAGENTS_AGENT. The line sourced the value from$AGENTS_AGENTand only assigned when that same key was unset, so it could never emit anything — while the docstring and the shipped API header both advertised the var. Line removed; the API header's "emitted by the identity/env layer" list now drops bothAGENTS_AGENTandAGENTS_CODE_SESSION_ID(the latter was deliberately never emitted) and says so explicitly, so nothing branches on a var that never arrives.dotagents envanddotagents contextnow resolve their roots instead of hardcoding them. Both tookPath.cwd()as the project root and~/.agentsas the user store, so$AGENTS_PROJECT_ROOT(or the agent-native$CLAUDE_PROJECT_DIR) and$AGENTS_HOMEwere ignored by the two commands that most needed them — aSessionStarthook runsdotagents contextfrom wherever the session happens to start, so a pinned project root was silently dropped and a relocated store was never read. They now use_scope.project_root_default()and the newdotagents.cli.resolve_user_store()(--agents-dir→$AGENTS_HOME→ legacy$DOTAGENTS_AGENTS_DIR→~/.agents), matching what the package's ownresolve_scopedocstring and command discovery already promised. Behavior is unchanged when none of the vars are set.
0.3.2 - 2026-07-25
Added
AntigravityAgent— context injection for Google's Antigravity CLI/IDE (a separate product from Gemini CLI, despite sharing the~/.gemini/namespace for some files). Antigravity's hooks have noSessionStartequivalent — onlyPreToolUse/PostToolUse/PreInvocation/PostInvocation/Stop— so aPreInvocationhook gated oninvocationNum == 0behaves like a one-shot context load instead of resending it every model turn. Wires into~/.gemini/config/hooks.json. No detection marker exists for Antigravity, so it's explicit---agents antigravity-only, never auto-detected.- Codex gets a
PreToolUseenv hook, closing the one gap Codex had versus Claude: Codex has no per-session env-persistence mechanism at any hook event, so a deployed script prepends a guarded env-loader to everyBashtool call viaupdatedInput.command— the same rewrite mechanism Claude's ownPreToolUsehook uses, confirmed directly against Codex's docs. initwires a PowerShellPreToolUseenv hook on Windows, closing a real gap:$CLAUDE_ENV_FILEonly reaches Claude's Bash tool ($env:CLAUDE_ENV_FILEis empty inside a live PowerShell tool call, confirmed directly) — a fresh PowerShell tool call gets none of the SessionStart env. The hook prepends a guarded env-loader to a PowerShell tool call's own command, shipped as an inline command string (never a.ps1file — a script file is subject to PowerShell's execution policy, and dotagents has no code-signing certificate; the inline form runs even underRestricted).SessionStart/CwdChangednow register two handlers each — a bash-syntax one and an explicitshell: "powershell"one — because Claude's own hooks.md says the shell "defaults to bash, or to powershell on Windows when Git Bash isn't installed": bash syntax fed topowershell -Commandon such a machine is a hard parse error, silently losing both env and context for the whole session.DotAgentsArgs(dotagents.cli, re-exported for overlay-shipped commands) — one shared-g/--global+--agents-dirbase class.initand all fouroverlayssubcommands now inherit it instead of independently redeclaring the same fields, so scope resolution can't silently drift between commands (one previously defaultedagents_direagerly toPath.home() / ".agents"; harmless in practice, but needless).
Fixed
dotagents env --format powershell/cmdleft an already-POSIXPATHunconverted, the mirror of the export/dotenv/fish fix below going the other direction. Found live: run from a genuine Windows PowerShell terminal whose own inheritedPATHalready held WSL/MSYS-mount-style entries (/mnt/c/Program Files/...), the emitted${env:PATH} = '...'was syntactically valid PowerShell but a single opaque colon-joined string, not the;-split list PowerShell's own PATH lookup needs — every subsequent bare-command lookup broke for that session. Also handles the MIXED case (dotagents' own native bin dirs prepended onto an already-POSIX inherited PATH, one string with both separators at once), caught by sourcing real output into a live PowerShell session and watchinggit.exefail to resolve before the second fix. A WSL-only segment with no Windows equivalent (/usr/bin) is dropped rather than mangled into a broken relative path.- the built
.pyzdegraded-gon any discovered command inheriting a dotagents-defined base class (first hit byDotAgentsArgsabove) — duho's AST introspection walks the full MRO for a command's flags, but the zipapp source-repoint shim only ever covered built-in command modules, not a base class's own module (dotagents.cli._common).--globaldegraded to the name-derived--global-scopeand-gvanished silently. Fixed, with new CI coverage asserting the exact short flag survives a real built pyz. harness_loadsrelative entries matched by bare filename, not full path — a relative entry like Codex's"AGENTS.md"wrongly suppressed ANY file sharing that basename anywhere on disk, including the unrelated~/.agents/AGENTS.mduser-store file Codex's harness never reads.dotagents context --agents codexemitted an emptysources: []even with real content present. Now resolved againstproject_rootand compared by full path, like the absolute (~/,/) forms already were.dotagents context --format jsoncrashed withUnicodeEncodeErroron any character outside Latin-1 (a bareprint()encoding with the console's codepage) — the same class of bug already fixed for the markdown path, just never covered for JSON.- PowerShell format uses
${env:NAME}(curly-brace form), not the bare$env:NAMEsigil — a handful of real Windows env vars have parens in their names (ProgramFiles(x86)), and$env:FOO(X86) = ...is a PowerShell parse error; the curly-brace form is valid for every name.
0.3.1 - 2026-07-24
Fixed
dotagents env --format export/dotenv/fishnow emit a POSIX PATH on Windows, instead of the OS-nativeC:\...;C:\...form. This is the exact command the ClaudeSessionStarthook appends into$CLAUDE_ENV_FILE, which Claude sources before every subsequent Bash tool call in the session — an unconverted PATH broke command lookup (git,grep,head,python, ...) for the rest of the session once poisoned, not just once. PATH-shaped values are now converted per segment: backslash to forward-slash,;to:, and a drive letter to its MSYS mount point (C:/...->/c/...— required for PATH lookups specifically; slash direction alone does not work in MSYS2/Cygwin bash).PATHEXTis dropped (no POSIX meaning). A handful of real Windows env vars with parentheses in their names (ProgramFiles(x86)) are also dropped for these formats —export FOO(X86)=...is a bash syntax error, not a bad value, and aborts sourcing the rest of the file.powershell/cmd/json/ini/yamlare unaffected.- A UNC PATH segment (
\\server\share\...) no longer collapses to a single leading slash (/server/share/...) during the POSIX conversion above — MSYS requires the double-slash UNC root (//server/share/...) to resolve it.
Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0).
0.3.0 - 2026-07-24
Added
initwires agent hooks and links the shared skills dir, for each active agent with a published hook schema (today: Claude and Codex).--no-hooksskips it.
Claude (~/.claude/settings.json) gets SessionStart, which appends
dotagents env --diff --format export to $CLAUDE_ENV_FILE and then runs
dotagents context — Claude sources that file before each Bash command and injects
the hook's stdout into the session context, so both the env layers and the assembled
context reach the session automatically. It also gets CwdChanged, surfacing a
directory's AGENTS.md. The env redirect appends and is guarded against an
unset variable, both as the hooks docs require.
Codex (~/.codex/hooks.json, or $CODEX_HOME) gets SessionStart running
dotagents context; its hook JSON is structurally identical to Claude's. We write
hooks.json rather than touching your config.toml for hooks.
Codex's env arrives differently: it has no $CLAUDE_ENV_FILE equivalent, reads no
.env files, and has no event that fires before config load, so
dotagents init --agents codex writes a # dotagents:begin/end managed block
containing [shell_environment_policy].set into config.toml. Only on an
explicit --agents — this edits your main config with values that go stale, so
auto-detection never triggers it. The block is appended and refreshed in place
(everything outside the markers is untouched) and set merges rather than
replaces. The values are a static snapshot: re-run init after changing your env
layers. Identity vars describe the target agent, so initializing from Claude still
writes AGENT = "codex"; PATH is excluded, since set overrides per subprocess
and a baked-in PATH would replace the inherited one.
The merge is additive and idempotent: unrelated keys and hooks you wrote yourself survive verbatim, malformed entries are dropped rather than raising, and re-running writes nothing.
init also links <scope>/skills/ into the agent's config dir, closing the last
mile for overlay-published skills — publishing only helps if the agent reads that
directory. Symlink where the OS permits, copy otherwise (a copy is a snapshot;
re-run init to refresh).
- The personal leak scanner (then in tools/) now also scans commit messages (current branch history)
for agent-session trailers/URLs — a Claude-Session: trailer or claude.ai/code/session
link — and exits 1 on any hit. The trailer is auto-added by the agent harness and
exposes a session id in public history if it slips through; the pre-existing tracked-file
scan didn't cover commit messages. flows/REPO.md release discipline documents the check
and the git filter-branch --msg-filter remediation for one that already landed.
- tools/cloud-setup.sh step 5 wires hooks/settings.snippet.json into the
user-level ~/.claude/settings.json (idempotent JSON merge, preserves existing
settings/hooks). A fresh cloud container has no settings file and nothing else
created one, so the SessionStart pull/link and Stop sync-back hooks never ran —
the private repo went stale and session changes were silently never pushed back.
kb/PRIVATE_SYNC.md documents the auto-wiring (manual merge still applies on
local machines).
Changed
-
BREAKING —
dotagents link/dotagents syncare gone from the CLI. They are the private-sync workflow's commands, not dotagents' core, so they moved — together with the logic behind them (src/dotagents/_link.py) — into the opt-inprivate-syncoverlay, and were renamed to say what they act on:dotagents link-project . # was: dotagents link . dotagents sync-project -m "msg" # was: dotagents sync -m "msg"
Install the overlay to get them back: dotagents overlays add private-sync --source
<overlays-checkout>. A plain dotagents now ships no private-sync workflow at all;
its whole command surface is init / build-pyz / context / env / overlays,
and everything else is discovered from an overlay or from your own cmds/ modules.
tools/cloud-setup.sh installs the overlay before linking, so the cloud bootstrap
is unaffected.
- The bundled dotagents/cmds/ directory now ships no command module of its own, but
init still creates it: it is the documented drop-in point for your own commands
(a README.md beside it explains the shape and the precedence rules).
- Move the cloud bootstrap from overlays/private-sync/hooks/cloud-setup.sh to
top-level tools/cloud-setup.sh (public, required tooling) so a fresh cloud container
can fetch-and-run it from the public repo instead of pasting its contents — the web
environment setup-script field becomes a one-liner
(curl -fsSL …/tools/cloud-setup.sh | sh) that stays current on every container start.
Docs (README, kb/PRIVATE_SYNC.md) updated to the download bootstrap.
Fixed
dotagents link/syncnever adopt or copy back a<project>/.agentsthat is itself a git checkout (.gitpresent — dir, or file for worktrees). A hosted-runner session that lists the agents repo as a source gets it cloned to<project>/.agentsby the harness; first-link adoption then moved that entire checkout —.git, foreign proxy remote, session branch — into~/.agents/projects/<name>/, nesting a repo inside the private repo, which a later sync'sgit add -Awould push as a bare gitlink (andsync's copy-back had the same swallow, withoverwrite=True). Both paths now log a skip and leave the checkout in place;link --forcekeeps an escape hatch that backs the checkout up to.agents.bak*(git state intact) and links the store.dotagents syncnow authenticates the private repo directly against github.com whenDOTAGENTS_AGENTS_TOKENis set — and on a hosted runner that rewrites github traffic to a scoped in-session proxy, bypasses the rewrite — so a standalonedotagents syncno longer 403s. Previously only the private-sync Stop hook worked (it sources_agents-git-auth.sh); a direct CLI run had no bypass, so its pull failed (could not read Password) and its push returned HTTP 403 through the proxy. The CLI now ports that logic: a per-command-ccredential helper in a normal environment, or an isolatedGIT_CONFIG_GLOBAL(identity + CA bundle preserved) that skips the rewrite when one is active. The token is still read from the environment at auth time and never written to.git/config.tools/cloud-setup.shno longer lets a single container-start clone failure permanently disable the environment. The clone often loses a race with egress/proxy readiness; previously itexit 0'd on the first failure, skipping the hook-wiring step — so the SessionStart hook (which can itself re-clone) was never registered and nothing ever recovered. Now the clone retries with backoff (5 attempts), and if it still fails the script persists a copy of itself and wires a SessionStart recovery hook that re-runs the bootstrap next session (egress is up by then); the first successful run merges the private-sync hooks and removes the recovery hook.tools/cloud-setup.shalso wires that recovery hook whenDOTAGENTS_AGENTS_REMOTEis unset at setup time, not only on clone failure. Hosted runners often expose the remote/token secrets to session processes but not to the setup-script phase, so the first bootstrap had no remote to clone and its no-remote branch justexit 0'd, leaving nothing to retry — the environment stayed dead every session (observed: setup ran the correct one-liner and emitted only the banner +skippingline, ~154 bytes, no clone). The branch now persists the recovery hook like the exhausted-clone path, so the next session — where the secret is present — clones and self-removes the hook. A genuinely remote-less environment just re-skips each session (idempotent; the hook never duplicates). (Durable fix is still to expose the secrets to the Setup Script phase so the first container succeeds.).gitignoretemplates anddotagents linknow use a slashless.agentsinstead of.agents/.linkcreates.agentsas a symlink, which git treats as a file, so the directory-only.agents/pattern never actually ignored it — the link showed up as untracked in every project._gitignore_excludes_agentsis now symlink-aware (a bare.agents/no longer counts as excluding a symlinked link, so the WARN fires), and the reference template, REPO.md guidance, and the starter_overlay/AGENTS.mdLeakage rule all recommend.agents.- Recommend
curl … -o file && sh fileovercurl … | shfor the setup-script field (README,kb/PRIVATE_SYNC.md,tools/cloud-setup.shheader). With a pipe the field's exit code issh's (0 on empty stdin), so a failed fetch at container start is silently reported as success;&&propagates the fetch failure to the setup log. tools/cloud-setup.shprintsstarting/donebanners (so a setup-script log proves whether it executed — a blank log means the field never invoked it, a config issue) andmkdir -p "$HOME"beforegit config --global(which fails if HOME isn't created yet in some setup contexts).
0.2.0 - 2026-07-19
Added
- Private-agents git sync —
dotagents linksymlinks a project's.agentsto a per-project store under the global~/.agents/projects/<name>(basename-keyed, so a local and a cloud checkout converge on the same store), adopting an existing real.agents/into an empty store on the first link;--copymirrors it as a real dir for no-symlink environments (with automatic fallback),--forcehandles conflicts.dotagents syncrunsgit pull --rebase/commit/push on the private repo, copies a copy-mode project's.agentsback into its store first (--project), and bootstraps a fresh repo in one command (--remote). Logic insrc/dotagents/_link.py; the model keeps per-user config and every project's private.agentsin one private repo while the public project repos track none of it (the Leakage rule already.gitignores.agents/). overlays/private-sync/overlay —kb/PRIVATE_SYNC.md(the model, commands, first-time + cloud setup, auth, gotchas) plushooks/private-sync-{start,stop}.sh(SessionStart clone/pull + link, Stop sync-back) and asettings.snippet.jsonfor~/.claude/settings.json, so cloud sessions link and sync automatically. Cloud auth is a fine-grained PAT viaDOTAGENTS_AGENTS_TOKEN, wired through a git credential helper that reads it from the environment (never persisted to.git/config);hooks/_agents-git-auth.shauto-detects a hosted-runnergithub.com→in-session-proxyinsteadOfrewrite and bypasses it (isolated git config) so token auth reaches the real github.com for a private repo outside the session's scope.hooks/cloud-setup.shis a self-contained container-start bootstrap (inlines auth + bypass, so it runs before~/.agentsexists) that clones/pulls the repo, installs the CLI, and links the project — for the web environment's setup-script field, solving the first-clone chicken-and-egg the SessionStart hook can't.- Installable
dotagentsCLI package (src/dotagents/, built onduhofor the argument surface andpathlib_nextfor copy/URI handling) exposinginit(lay down the neutral base overlay),install(base plus opt-in overlays via repeatable--overlays <path>, copied additively),audit(wrapstools/audit_config.py), andbuild-pyz(vendors pinnedduho/pathlib_nextviapip install --target+zipappinto a self-contained, downloadabledotagents.pyz).init'sAGENTS.md/CLAUDE.mdare merged as a marker-delimited managed block so re-running never clobbers customizations outside the block.install --bin-dirwritesdotagents/dotagents.cmdwrappers.install.pyis a thin shim overdotagents.cli.main(). - Config content is a neutral base overlay (
src/dotagents/_overlay/— theAGENTS.mdscaffolding + design-log conventioninitwrites) plus opt-in overlays (overlays/<name>/):flows(PLAN/EXEC/REVIEW/REPO + MODELS),recovery,references,python/node/rust,agents,tools. Each carries anoverlay.tomlmanifest for a futuredotagents overlayssubcommand. - Repo layout: the CLI in
src/dotagents/, config overlays inoverlays/, required tooling in top-leveltools/(audit_config.pyand a personal leak scanner); repo root holds the installer, CI, repo-development directives, and the tracked, sanitized.agents/design log (index + per-decision files) + plans.audit_config.pyhas--repo-hygiene(scans tracked files for personal/machine-specific leftovers).
Changed
- Migrate the CLI to
duho>=0.3.3(was>=0.1.1). duho'sArgs/Cmdsplit means commands are nowclass X(LoggingArgs, Cmd)with a__call__entrypoint (was a bareLoggingArgswith__run__) and the umbrella root isclass Dotagents(LoggingArgs, Cli). Field declarations (annotation + help string + flags tuple) are unchanged. Bumped thebuild-pyzvendoredduhodefault to 0.3.3.
Fixed
- Restore full flag/help fidelity in the built
dotagents.pyzunder duho 0.3.3. duho discovers each field's flags + help by AST-parsing its module source, and inside a zipapp the zip-internal__file__isn't readable — degrading--fromto--from-, thelinkpositional to--path, and dropping help text.cli.mainnow repoints the affected module sources (dotagents.cli,duho.presets) to extracted temp files before dispatch; a no-op for a plain install.