Skip to content

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-level bin/<name>.py, and each [scripts] entry in overlay.toml (name = "path" adds or replaces one, name = false opts a bin/<name>.py out). The launchers live in the agents' venv, in <venv>/overlays/: <name>.exe on Windows, made with distlib's ScriptMaker so no command interpreter parses the arguments, and a /bin/sh script elsewhere. They run the script through $<NAME>_OVERLAY_ROOT when they run, so only a new interpreter needs them made again. init and overlays add / sync / remove keep them current and delete those no overlay has; two overlays with one script name is an error naming both.
  • dotagents env puts the launcher directory of the venv AGENTS_PYTHON names first on PATH, ahead of the levels' bin dirs, 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 add takes globs: overlays add '*' -g installs 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.sh installs every overlay the store's registry names (overlays add '*') before falling back to installing private-sync alone.

  • Each store has a Python venv, <store>/.pyvenv/<X.Y.Z>-<os>-<arch>/, made from the Python that runs dotagents, where skills run. init and overlays add / sync / remove keep it current: the user venv always has duho, and every overlay's requirements.txt is installed (in one pip run, only when a file changed, before the overlay's setup.py). A project gets its own venv when one of its overlays ships requirements or a lib/. --no-venv skips it for a run; AGENTS_NO_PYVENV=1 turns 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 .pth file 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.
  • env points AGENTS_PYTHON at the active venv and names it in AGENTS_PYVENV; overlay setup.py scripts run under it. context prints it ("Python for skills"), and its JSON form carries it as python. 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 .pyz built with the new build-pyz --with-pip, in AGENTS_PIP_SOURCE (a directory of wheels, or any URI pathapi-uri reads) or pip's find-links, or on your index (PIP_INDEX_URL, or the mirror in pip.ini / pip.conf). AGENTS_PIP_SOURCE is also passed to pip as --find-links for the requirements.

Changed

  • overlays add no 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, env no longer puts the overlay libs it layers on PYTHONPATH: the venv's interpreter imports them, and no other Python in the session should. Without a venv, PYTHONPATH carries them as before. AGENTS_PYTHONPATH still 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 shows AGENTS.md modified on all but one of them.

Fixed

  • env --cache on 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 repo extra (repopath[uri]) for overlay sources at github:, gitlab: and repo: URIs, which the uri extra no longer brings.

Changed

  • dotagents depends on pathapi (>=0.1.0,<0.2), the new name of pathlib_next, instead of pathlib_next. The .pyz vendors it; build-pyz --pathapi-version names its pin, and --pathlib-next-version still works. On Python 3.9 nothing else has to be installed for it.
  • The extras install pathapi's own distributions: uri and http install pathapi-uri (the URI layer, imported as uripath, with file:, http(s):, dav:, ftp:, zip: and tar: on the standard library); sftp adds sftppath[paramiko] and s3 adds objpath[s3]. Overlay sources are read as before. A git: URI is not a scheme any more; a repository tree is a repo: URI.
  • build-pyz --extras vendors what the named extras install, each distribution at its floor, and refuses an extra it does not know.
  • about lists pathapi, pathapi-uri, netimps and the plugin distributions in place of pathlib_next, uritools and requests.

Removed

  • The .pyz no 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 through duho.run(..., exit=False): it always returns the status instead of raising SystemExit for a message, --help or a usage error (the console script, python -m dotagents and install.py exit 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 duho floor moves to >=0.7.5,<0.8 (from >=0.6.0,<0.7); the vendored .pyz pin (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_name delegates to duho.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 with duho.runtime.build_command_parser instead of a private duho helper. Output, exit statuses and which commands are skipped are unchanged.

0.7.4 - 2026-10-14

Added

  • findings index --requote quotes, 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-project now writes, once, into the .gitignore of a store kept in git: scratch/, tmp/, __pycache__/, hidden files and the machine-local overrides stay out of the private repository.

Fixed

  • findings writes 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 when dotagents starts with DOTAGENTS_MCP=stdio, and that AGENT_HELP=1 turns --help into 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_revoke in <store>/dotagents/config.toml sets 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, false skips the check, as git on Linux does, true enforces 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 know best-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.md for the rust overlay's kb/RUST.md, or <project>/.agents/kb/RUST.md), where overlays sync never touches them. The base AGENTS.md tells agents to read them alongside the overlay's file, and dotagents context lists the ones it finds (JSON: local_additions).

0.7.0 - 2026-10-01

Changed

  • overlays sync, and overlays add of 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 / .ignore at 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 (on sync, and now on add) clears what .gitignore / .ignore protect 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 as dotagents LeakCheck.
  • The Claude Code CwdChanged hook keeps one AGENTS_PROJECT_ROOT pin in $CLAUDE_ENV_FILE, replacing the previous one, instead of appending a line on every cd (the file is inlined into every Bash command). Other lines in the file are kept. Re-run dotagents init -g to update an installed hook.

Removed

  • overlays sync --overwrite: replacing is what sync always does now.

0.6.3 - 2026-09-30

Added

  • env --into FILE writes the output into FILE as the one block env owns there, replacing the block an earlier run wrote and keeping every other line.
  • With --diff --format export, a PATH that is the caller's own with entries put in front is written as export PATH='<entries>'"${PATH:+:$PATH}", so it stays short and keeps what the sourcing shell has.
  • When dotagents runs from a .pyz, env exports AGENTS_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 in AGENTS_PYTHON has no duho installed. An installed dotagents sets nothing: its libraries are already importable from AGENTS_PYTHON. The archive is never put on PYTHONPATH, where it would shadow a project's own copy; a consumer adds it to sys.path itself when its import fails.

Fixed

  • The Claude Code SessionStart hook no longer appends the whole environment to $CLAUDE_ENV_FILE every 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 uses env --into, which keeps one block. Re-run dotagents init -g to update an installed hook.

0.6.2 - 2026-09-28

Added

  • With the duho floor bump, every dotagents invocation now checks DOTAGENTS_MCP=stdio and serves its full command tree as an MCP server over stdio when set. env opts 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, and env still works normally on the CLI.

Changed

  • The duho floor moves to >=0.6.0,<0.7 (from >=0.5.0,<0.6); the vendored .pyz pin (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 init after an upgrade from 0.5.x replaces the CwdChanged hooks and Codex's PreToolUse hook that release wrote, instead of adding the new ones beside them (every cd ran 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, reads AGENTS.md or AGENTS_PROJECT_ROOT, or runs one of dotagents' hook scripts.

0.6.0 - 2026-09-25

Added

  • The package ships py.typed and declares Typing :: Typed; Python 3.14 is a supported version, and the project metadata links the issue tracker.
  • A yaml extra (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 --cache reuses 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 init points pi at the project's .agents/AGENTS.md through a managed block in .pi/APPEND_SYSTEM.md.
  • dotagents._resources holds the bundled base overlay (BASE_ROOT), the block template constants and base_agents_text; they are still importable from dotagents.cli._common.
  • Agent.include_entry and Agent.base_config_note describe what init writes for each harness.
  • dotagents path prints the bin dirs dotagents env puts on PATH (--lib: the lib dirs it puts on PYTHONPATH), 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, or overlays add / sync --setup-timeout; 0 means 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.py installs its dependencies only into a virtual environment. Elsewhere it lists the alternatives (a venv, pip install dotagents-cli, the release dotagents.pyz) and installs only with --bootstrap, which it still refuses for an externally managed (PEP 668) interpreter.
  • The root build.py helper is removed; use python -m dotagents build-pyz. Its bare-word argument parsing misread flags, and at the repository root it shadowed python -m build.
  • tools/cloud-setup.sh offers 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.
  • launch writes 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 remove deletes only active findings; a processed finding keeps its resolution and must be reopened first.
  • init, overlays and findings honour a pinned $AGENTS_PROJECT_ROOT only while the current directory is inside it. After a cd out of it they use the project the current directory is in (the nearest directory up with a .git or its own .agents) and warn; they used to write into the session's first project. env and context keep the pin.
  • context output on stdout and in JSON, and the context launch hands over, expand $<NAME>_OVERLAY_ROOT to the overlay's directory, so a harness without the dotagents env can follow overlay routing. A file written by context --write-agent, or merged by launch, keeps the variable, because it may be committed.
  • The skills listing names each skill's SKILL.md, and JSON skill entries gain a path key.
  • 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 at dotagents/DECISIONS.md, and it says where a finding that reports a fact rather than calling for a rule goes: a kb/ file routed from "Load on demand". An existing store picks the new wording up when its block is next rebuilt: dotagents overlays sync in a store with overlays installed, dotagents init in one without.
  • Existing stores keep the files an earlier init wrote. Delete dotagents/DECISIONS.md, dotagents/README.md, dotagents/AGENTS.md and dotagents/cmds/README.md if you no longer want them, keeping any decisions you recorded wherever you keep notes. If you still keep a findings queue at dotagents/findings/ (the queue moved to findings/ in 0.4.0), move it; until then every findings subcommand 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.env were 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 (and pre.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 init writes 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 next init or overlays add / remove / sync in that project. An overlays add into a store with no AGENTS.md creates 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 returns permissionDecision: "allow", the only documented way to rewrite a command without prompting on every call, so it auto-approves every PowerShell tool call; init -g wired it silently. A plain init removes 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/agents was drive-relative, and any local account could create it) and $AGENTS_SYSTEM_ROOT must 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 its bin/ on PATH.
  • overlays add, sync and remove link new skills into Claude's skills directory and remove the ones that are gone, when init has already wired Claude for that store. On Windows, a skill is linked with a directory junction before falling back to a copy.
  • init --dry-run reports what it would do ("would create …", "would refresh the block …"), including the wrapper scripts and the recorded base.
  • dotagents env also lists the overlay and store lib/ directories it puts on PYTHONPATH in AGENTS_PYTHONPATH, and after a cd into another project it takes the previous project's libraries off PYTHONPATH.
  • Overlay setup scripts run with the scope's assembled dotagents env (every overlay's lib/ on PYTHONPATH, bin/ on PATH), and command modules can import any overlay's lib/, even when dotagents runs from a plain shell.
  • An env.py value must be a JSON string: null unsets the variable, and any other type is skipped with a warning naming the key. dotagents env emits unset variables where the format has a way to (unset, set -e, $null, set "K=", null).
  • env.py is run as env.py --level <level>; --agent <level> is still passed for existing scripts.
  • In the Python API, get_environment and get_diff return an EnvChanges (still a dict of the variables set) whose removed names the variables a layer unset, and get_env_from_py no longer takes project_root; its level and global_scope are keyword-only.
  • dotagents env --format yaml double-quotes every value; ini is written for Python's configparser and dotenv for python-dotenv.
  • Each installed overlay keeps a record of the repo it came from and the files it installed. overlays sync fetches it from that repo again, not from whichever repo now offers the name first; --repo replaces the recorded source.
  • overlays sync refreshes overlay.toml, reports files that differ from the source as kept, removes files the source dropped when they are unmodified (--prune for edited ones), backs up whatever --overwrite or --prune replaces, and installs requirements added upstream.
  • overlays remove refuses an overlay another installed overlay requires, unless --force. overlays list and show flag unmet requirements, and show names the recorded source.
  • Overlay setup scripts get AGENTS_HOME set to the user store, plus AGENTS_SCOPE_ROOT and AGENTS_SCOPE for 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/tomli whenever one is available.
  • The downloadable dotagents.pyz is compressed: about 0.46 MB instead of 1.4 MB.
  • build-pyz vendors 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.toml reads it from there.
  • overlays add, remove and show without an overlay name are a usage error (exit 2).
  • tools/cloud-setup.sh reads AGENTS_OVERLAYS_REMOTE and AGENTS_OVERLAYS_REF; the old DOTAGENTS_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's CONTEXT.md is left out of the assembled context; overlay.toml is the only manifest.

Removed

  • init writes no dotagents/ directory into a store: the base config is the AGENTS.md managed block, plus the Claude include and the agents' hooks. Gone are dotagents/DECISIONS.md (the per-install design log), dotagents/README.md, dotagents/AGENTS.md, and the empty dotagents/cmds/ with its README. A scope's dotagents/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 codex no longer writes a static [shell_environment_policy] snapshot into ~/.codex/config.toml: it pinned the project init ran from into Codex's global config and could leave the file unparseable. Codex gets the env from its PreToolUse hook, live. The next init that wires Codex's hooks removes a block an earlier release wrote and leaves the rest of the file alone.
  • Overlay.apply_to and Overlay.merge_rules_into; use recompose_overlay_block.
  • Scope.system_store (use Scope.system_root) and Scope.cmds_dir.
  • dotagents._scope.OverlaySource; use dotagents._sources.DirRepo.
  • The src, base_agents_text and force parameters of Agent.write_base_config, and the force parameter of Agent.write_context.
  • The dotagents.cli re-exports of AGENTS_DIR_ENV and BASE_ROOT; import them from dotagents._scope and dotagents._resources.

Fixed

  • A session started in the home directory walks the user store once; it ran every env.py twice and sent every AGENTS.md and CONTEXT.md twice. init and overlays run from the home directory target the user store instead of writing a project block over it.
  • context orders 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's AGENTS.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 --inline inlines 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.
  • context with several agents and an output file is an error instead of keeping only the last agent. --agents that names no known agent exits 2, harness ids such as claude-code are accepted, and an empty result gets no system-reminder wrapper.
  • --agents-dir after -- (an argument for the harness launch runs) no longer changes which command modules are discovered, and a project store named with --agents-dir no longer replaces the user store's commands.
  • A command module that imports but whose parser cannot be built (an unresolvable annotation, X | None on Python 3.9, a duplicate flag, or a flag the umbrella owns) no longer breaks every dotagents invocation; 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.
  • findings no 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 add refuses the names index and readme, which overwrote the finding with the index on a case-insensitive filesystem, and done and reopen no longer overwrite an existing file at the destination.
  • launch exits 128+N for a harness killed by signal N, instead of 256-N.
  • On Windows, launch refuses arguments that cmd.exe would run or expand for a .cmd or .bat harness (& | < > ^ % ! " or a newline) instead of passing them on rewritten.
  • The init, overlays subcommand, about and launch summaries in --help are whole sentences.
  • Private *.local.* and CLAUDE* files in a source tree no longer reach the wheel, the sdist or a locally built dotagents.pyz.
  • tools/cloud-setup.sh no longer deletes an existing ~/.agents that is not a git checkout (it moves it aside and prints where), refuses to rewrite a ~/.claude/settings.json it 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 init adds beside Claude Code's bash hooks decide whether to run the way Claude Code decides which shell to use. They checked for bash on PATH: a default Git install (only Git\cmd on PATH) got the context twice, and a box with WSL's bash.exe stub and no Git Bash got none.
  • The Claude Code and Codex hooks run dotagents by path instead of putting .agents/bin and the store's bin on PATH, which env --diff then persisted into the session: after a cd the first project's bin stayed ahead of everything.
  • The PowerShell env loader (init --powershell-env-hook) runs only for PowerShell tool calls, not before every tool call, and reads dotagents env as UTF-8: a non-ASCII value arrived garbled.
  • Re-running init keeps keys you added to a dotagents hook entry (a timeout, say) and no longer deletes malformed hook entries from your settings. dotagents' hooks are labelled dotagents: ..., so an unrelated hook with the same generic label is no longer mistaken for one of them.
  • A bin/dotagents wrapper run through a symlink finds its .pyz beside the real file, and dotagents.cmd works when the Python interpreter's path is not ASCII (a user folder with an accented name, say).
  • Re-running init on a store with overlays installed keeps their rules and routing in AGENTS.md's managed block; it used to rewrite the block from the bare base until the next overlays sync.
  • A base chosen with init --from is recorded in <store>/dotagents/config.toml, and a later plain init and every overlays add / remove / sync compose over it; they used to switch back to the bundled base. Delete the file's base line to return to the bundled one.
  • init --force keeps the original of every file it replaces. Adapters that write the same file in one run share one install_backup/<timestamp>/, a backup is never overwritten, and a file outside the store is backed up under external/ at its own path instead of over a same-named one. pi's own AGENTS.md is 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.
  • init from 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.cmd that did not exist, so PowerShell sessions got no env and, without bash, no context. Wrappers left pointing at a .pyz are replaced, with a warning. The PowerShell hooks also fall back to dotagents on PATH and do nothing when there is none.
  • context for Claude includes a project's committed root AGENTS.md when a CLAUDE file (such as the .claude/CLAUDE.md a project init writes) 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/bin on PATH as an absolute path. The relative .agents/bin they used was re-emitted into the session's PATH by dotagents env, where it resolved against whatever directory a later command ran in. Re-run dotagents init to 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's PATH builtin and did nothing. Both Codex hooks run under the interpreter init ran with, not a bare python3 / 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 codex to update the hooks.
  • launch no 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 committed AGENTS.md / GEMINI.md / .cursorrules. The context stays at $AGENTS_CONTEXT_FILE with a warning; launch --write-agent merges it anyway. The harness is resolved first, so a launch that fails with "not found" writes nothing. context --write-agent refuses -g.
  • The Claude CwdChanged hook 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-run dotagents init.
  • env --format powershell doubles 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).
  • env without --diff no longer prints inherited DOTAGENTS_* 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).
  • init writes the store's AGENTS.md itself, whatever agents are named; with --agents gemini,cursor,copilot it wrote none, and put GEMINI.md, .cursorrules and .github/copilot-instructions.md inside the store, where no harness looks. Gemini CLI now gets an @ import of the store's AGENTS.md in ~/.gemini/GEMINI.md (or the project's GEMINI.md); Cursor and Copilot get the context through context --write-agent or launch. An unknown --agents name is an error instead of a warning.
  • Adapters are told the scope init resolved 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_DIR when that is set.
  • A project-scope init no longer edits Codex's or Antigravity's global hook config (it turned dotagents on in every workspace); run init -g for them. Antigravity's hook runs under the interpreter init ran with.
  • context --write-agent / launch write Cursor's context to .cursor/rules/dotagents.mdc with alwaysApply: true (.cursorrules is legacy), and Cursor's and Copilot's own reading of the project's AGENTS.md is 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.
  • env stamps no agent identity in a plain shell: a repository with an AGENTS.md made it export AGENT=codex to 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 init warns when it writes the Claude include (or Gemini's) into a file git does not ignore.
  • tools/cloud-setup.sh installs dotagents-cli by default; the dotagents name it used is not this project on PyPI.
  • overlays add / sync never delete a skill you placed or edited in the shared skills/ dir. A same-named skill already there is kept, with a warning; a published copy you edited is kept on sync unless you pass --overwrite, while an untouched copy still follows its overlay (the digest of each copy is recorded in skills/.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 remove deletes read-only files (git objects on Windows) and unlinks a symlinked overlay instead of failing half-way; an overlay that already carries a .git copy is removed cleanly.
  • dotagents with no subcommand logs its hint instead of raising when no parser has been built.
  • dotagents env puts the dotagents bin directories at the front of PATH in their precedence order even when the calling shell already had some of them, and drops empty and relative PATH entries, which resolved against whatever directory a later command ran in.
  • init --from accepts the forms the docs list: a dotagents checkout, a file:, http(s):, sftp:, s3: or zip: 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.json or hooks.json keeps a symlinked file a symlink and updates its target, and keeps the file's permissions.
  • init accepts a settings.json saved with a UTF-8 byte-order mark, and a corrupt settings file stops init before it writes anything instead of leaving a half-initialised store.
  • On Windows, dotagents launch never runs a harness from the current directory (a claude.cmd at a repository's root, say).
  • dotagents launch no longer passes the harness a variable an env layer unset; --dry-run lists them on an unset: 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 init warns when skills it links into .claude/skills/ are not gitignored.
  • On Python 3.9 and 3.10, tomli is 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 files build-pyz generates 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, TEMP and similar variables is no longer reported as an env file's changes, and a PATH an 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-dir emits the store it actually walked as AGENTS_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 its PATH.
  • 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": sync no longer skips with success, add no longer installs without a dependency, and list warns 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; requires names and rules paths can no longer point outside the overlay; an unreadable rules file is a warning instead of a crash that blocked every later add and sync.
  • overlays add rolls back a new install whose setup fails and updates AGENTS.md for what did install; overlays remove unpublishes skills only after the overlay is deleted and always updates AGENTS.md.
  • overlays add and overlays sync refuse a source inside the store's own overlays/ directory, where a later remove would delete the only copy.
  • An overlays dry run says it would recompose or create AGENTS.md instead of reporting it as done.
  • On Windows, init names the PowerShell PreToolUse hook only when --powershell-env-hook wired 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 .pyz exactly what build-pyz vendored (recorded in dotagents/_bundle.json at build time), from a plain install the dependencies present. --json adds where it runs from and the Python.
  • Overlay sources and repos can be any path pathlib_next speaks. With the uri extra (dotagents-cli[uri]; [http], [sftp], [s3] add the schemes' clients) an http(s):// directory listing, an sftp, s3, dav or github tree 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 an http(s):// registry file still works through the standard library, and anything else names the extra it needs. build-pyz --extras uri,http vendors the extra into the zipapp. GitCache is now SourceCache.

Changed

  • The base config's first line, "Startup: annotate that you read …", names the actual path of the AGENTS.md it is in (init renders it per store, and every overlays add / remove / sync recompose keeps it), not a literal ~/.agents/AGENTS.md that a relocated store or a project's own file never was. The base overlay is dotagents/ only: the block template is dotagents/templates/AGENTS.md, <store>/dotagents/AGENTS.md is the working notes for that directory (read when working in it, not by default), <store>/dotagents/README.md the user-facing one, and init no longer writes a <store>/CLAUDE.md or a store-root README (no harness read the former; the include init writes into ~/.claude/CLAUDE.md is the real one). Existing stores keep the files they have.
  • The example overlays' branch is repo (was overlays); 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's DOTAGENTS_OVERLAYS_REF default follows.

0.5.0 - 2026-09-10

Added

  • Overlays can come from git repositories and registries. overlays add / sync / list / show resolve 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's dotagents.{json,toml,yaml,yml}, the user store's, then the bundled overlays/. 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, an http(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 like findings: starts an agent's CLI (claude, codex, gemini, cursor-agent, copilot, pi, or --command <program>) with the full env assembly exported to the process and the child, and the assembled context written to a file (exported as AGENTS_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 managed dotagents:context block otherwise. The program is resolved on the exported PATH, so a harness an overlay's bin/ provides is found; --dry-run prints the command line and the exported names; --no-context and --inline shape the context step; the exit code is the harness's. Adapters gained launch_command and launch_context_args().
  • The pi agent (pi.dev, @earendil-works/pi-coding-agent): detected by its documented PI_CODING_AGENT marker, model from PI_MODEL, no vendor (it is multi-provider). Its config dir ($PI_CODING_AGENT_DIR, else ~/.pi/agent) and the project's AGENTS.md / CLAUDE.md count as loaded by the harness; context --write-agent and launch hand the context over through .pi/APPEND_SYSTEM.md, the file pi appends to its system prompt, and launch uses --append-system-prompt directly on POSIX. init -g puts a managed pointer to the store's AGENTS.md into the config dir's AGENTS.md (pi has no include syntax), never a copy of the block.
  • env exports AGENTS_PYTHON: the interpreter dotagents itself 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 bare python/python3 on PATH may be a Store alias stub, an emulated build, a venv's, or missing.

Changed

  • overlays add no 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 --source and $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 duplicate DOTAGENTS_AGENTS_DIR / DOTAGENTS_OVERLAY_DIR an overlay's setup script received. Only the AGENTS_* names are read or set.
  • The extensionless POSIX setup script an overlay could ship: setup.py is the one form, run under the interpreter running dotagents on every platform.
  • The precursor's lowercase session_start hook key is no longer migrated.

Fixed

  • init no longer registers the PowerShell hook variants on POSIX hosts, and removes the ones an earlier init wrote there. A shell: powershell entry 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 when bash is absent. Re-running init -g on 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 a sys.exit() — which is not an Exception and used to escape the guard and kill every invocation, init -g for 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 (frontmatter name/description/status/created for 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. add records one (--body/--body-file, - = stdin), list prints the active ones (--all, --processed, --json), show prints one (--json), done appends a required ## Resolution and MOVES the file to processed/ (never deletes), reopen moves it back, remove deletes a mistaken one, index regenerates INDEX.md (active first, then processed — every mutating command rewrites it), path prints 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.
  • env prepends every level's existing lib/ dir to PYTHONPATH, the way it already prepends every level's bin/ to PATH — overlays first, then the store, then the project's .agents/, never the project root — and does so before the env-file chain, so an env.py (and every subprocess that inherits the env) can import an overlay's lib/ module. Only dirs that exist are added, and PYTHONPATH is untouched when there are none. The formatter already converts any *PATH variable between Windows and POSIX forms, so PYTHONPATH gets the same treatment as PATH.
  • env emits one <NAME>_OVERLAY_ROOT per installed overlay (the overlay's install dir), seeded before the env-file chain like the two scope roots and, like them, only if unset. NAME is 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 name context already 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 name overlays add installs under (Overlay.normalize_name), so the var for overlays/<n>/ is always Overlay.root_var_for(n).

Changed

  • The overlay-root note overlays add writes above routing lines cites $ENGINEERING_OVERLAY_ROOT as its example; the docs and CI install the engineering overlay, 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 _resolve module (and its API docs page) is gone, its LEVEL_NAMES live in _scope.
  • The system store is a store like the others. Scope.system_root (/etc/agents, or $AGENTS_SYSTEM_ROOT) heads Scope.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/agents tier 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 list prints one installed (<level>) block per store that has anything, most specific first.
  • The library walk takes one object. Scope now carries the whole picture (user_root, project_root, stores, overlays, files(*names)), and get_file_paths, get_environment, get_diff, resolve_env_files, get_bin_paths, get_lib_paths, get_overlay_roots, assemble_context and assemble_context_data take a Scope instead 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.md and the <NAME>_OVERLAY_ROOT var (a store root the session had already pinned is re-pointed). -g sees the user store alone. Overlay.installed(*stores) is the one discovery behind the contract-A walk, env, context and overlays list / show; the _scope discover_overlays pass-through is gone (Overlay.discover(root) is the single-root form). overlays list in a project scope prints both installed (project) and installed (user), marking shadowed store copies; overlays show falls back to the user store's copy before the source.
  • Overlay routing lines refer to overlay files through $<NAME>_OVERLAY_ROOT (the variable env exports per installed overlay), not a hard ~/.agents/ path; when a merged block carries such lines, AGENTS.md gets one line above them saying what the token is and how to resolve it. The example overlays on the overlays branch follow this convention and ship no setup scripts any more (PATH, PYTHONPATH and the root var are env's job; an overlay's own env belongs in its env.py). tools/cloud-setup.sh finds the private-sync settings snippet inside the installed overlay.
  • The base overlay's findings workflow now goes through the command. The managed AGENTS.md rule ("Global-config misses"), dotagents/DECISIONS.md ("How findings become config") and the skeleton README say dotagents findings add -g ... to record a miss, list -g / show -g to triage and done -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 -g default), 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-run dotagents init to refresh the managed block.
  • dotagents._overlays is now built around an Overlay class — 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 and env all share, and Overlay.sort_by_priority the 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_name and _scope.normalize_overlay_name. recompose_overlay_block stays a module function (it works over a set of overlays) and now accepts Overlay instances or directories. _overlays is a private module, so no public version signal.
  • init no longer copies bundled command modules (*.py) from the package's dotagents/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 of findings and shadowed every later one. A same-named module dropped into a scope's dotagents/cmds/ still overrides the bundled one.

Fixed

  • One broken command module no longer breaks every dotagents call. Discovery skips a source that fails to import for any reason (a SyntaxError, an exception at import time) with a warning naming it. It caught ImportError only, and duho propagates the rest on purpose, so a typo in ~/.agents/dotagents/cmds/foo.py made env, context, init and even --version traceback, 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; a mkdtemp per item with no cleanup had left 632 dotagents-* directories in one machine's %TEMP%.
  • overlays add installs a source dir spelled my_overlay (it normalized to my-overlay and looked only that up), validates and resolves EVERY name against the source before touching anything (add good bad used to install good, run its setup, then fail), installs each manifest's requires first (transitively; --no-requires to 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 by remove), and reports the setup script on a dry run.
  • overlays remove normalizes names (add My_Ov + remove My_Ov said "not installed") and recomposes the managed block over the overlays that remain, so an overlay's rules and routing leave AGENTS.md with it; the warning that pointed at the removed dotagents install is gone.
  • overlays sync honours --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, so routing = ["a"] # note no longer swallows the NEXT array (a rules path became a routing line), an indented ] no longer yields [], 'single-quoted' strings parse, and priority = 5 # low is 5, not 500. description and requires are read.
  • _compose_block with a base that has no ## Load on demand heading appends the overlay rules at the end of the block, as its warning always claimed; they were dropped.
  • build-pyz outside a source checkout is a clear error instead of a FileNotFoundError.
  • dotagents, overlays and findings invoked with no subcommand print their help and exit 2 instead of logging a hint and exiting 0.
  • --agents-dir X on the command line is honoured by command discovery, so the store the command is about to use is the one whose cmds/ are found.
  • findings add rejects a name already carried by another note's frontmatter (get matches frontmatter names first, so done would have processed the wrong file).
  • Two overlay dirs with the same manifest name sort deterministically (dir name is the final tiebreaker).
  • SessionStart context is no longer injected twice. The PowerShell variants of Claude's SessionStart / CwdChanged handlers run only when bash is 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 PreToolUse env-loader prefix is quoted correctly: inside "$(...)" its \" were literal quote characters, so PATH became ".agents/bin:...:<last>" with the quotes, the project .agents/bin was 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_HOME when set (bash: ${AGENTS_HOME:-$HOME/.agents}), so a custom store gets hooks that can find dotagents; the PowerShell SessionStart variant prefers the project's own .agents\bin like the bash one. The PowerShell tool env-loader runs env --diff instead of re-assigning the whole environment on every call.
  • The bash CwdChanged handler re-pins AGENTS_PROJECT_ROOT into $CLAUDE_ENV_FILE when the new directory carries a .agents/; the SessionStart pin is only-if-unset, so a cd into 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 and AGENTS_PROJECT_ROOT of the dotagents context spawn), as its docstring already claimed, honours $AGENTS_HOME when locating dotagents, and injects nothing when the assembly exits non-zero. Its constant is PREINVOCATION_HOOK_SCRIPT (it wires a PreInvocation hook).
  • 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. init writes the @ include where Claude Code reads it -- ~/.claude/CLAUDE.md for the user store, <project>/.claude/CLAUDE.md for 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) while context subtracted ~/.agents/AGENTS.md as "already loaded" on a static assumption, so nothing delivered the base rules. context now subtracts exactly what the harness's entry files actually @-include (Agent.loaded_paths), recursively, on this machine.
  • context no longer inlines every .md file 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 in context never 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 (matching env), not only one that ships a CONTEXT.md.
  • context --write-agent merges the context into the harness's own instruction file under the PROJECT root -- Claude .claude/CLAUDE.md, Codex AGENTS.md, Gemini GEMINI.md, Cursor .cursorrules, Copilot .github/copilot-instructions.md, Antigravity .agents/rules/dotagents.md -- as a managed dotagents:context block 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. --format is validated, and --write-agent refuses --format json or an output path instead of silently ignoring one.
  • Project-scope overlays (<project>/.agents/overlays/, where overlays add installs by default) are now part of the contract-A walk: their bin, lib, env files, cmds and CONTEXT.md resolve, after the user store's, and each gets a <NAME>_OVERLAY_ROOT (a project overlay wins a name clash). Nothing consumed them before except the AGENTS.md recompose.
  • Managed-block markers must be a line of their own: a prose MENTION of the markers (the base AGENTS.md carries 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 --from base 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.json are 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 -g with a custom store used to wire Claude's hooks into <store-parent>/.claude/settings.local.json, which nothing reads.
  • CODEX_HOME no longer marks a running Codex session (it is the user's persistent state-dir override, exported from a shell profile); the CODEX_SANDBOX* vars still do, and CODEX_HOME still locates the config.
  • An unknown --agents name no longer overrides a pinned identity in env.
  • 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, so env -0 ran regardless and reported rc 0 with whatever had been assigned before the error.
  • Bash's own PWD (in /c/... form), OLDPWD, SHLVL and MSYSTEM* 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 auto returned cmd when invoked through the dotagents.cmd wrapper from PowerShell (the wrapper cannot exec, so cmd.exe sits between Python and the shell); a cmd whose own parent is a shell is now skipped.
  • env formats: fish escapes backslashes; cmd doubles % and flattens newlines; yaml quotes values a reader would type (true, 123, null, 1e3, leading indicators); powershell/cmd no longer mangle a forward-slash native path (C:/a/tools became C;A:\tools); output is written as UTF-8 bytes so a non-Latin-1 value cannot crash on a cp1252 console.
  • init -g, overlays ... -g and every other resolve_scope caller honour $AGENTS_HOME (the var env itself emits) instead of the literal ~/.agents, and --agents-dir overrides the store in the project scope too (it was silently ignored without -g). resolve_user_store moved to _scope (still re-exported from dotagents.cli); the findings command's local workaround is gone.
  • overlays list / overlays add apply 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 --source or the env var.
  • findings list / show write UTF-8 bytes, so a description with a non-Latin-1 character no longer raises on a cp1252 console.
  • An env.py may 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's setup.py appends its own managed block to the store's env.py and 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 codex run from a session whose environment already carried dotagents' identity vars (which its own env-loader hook exports — AGENT=claude-code and 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 .pyz lost the help text of the umbrella's own --cmdspath flag (every subcommand's help survived). The zipapp source-repoint shim covered each dotagents.cli.<x> command module but not the dotagents.cli package itself, and it resolved a package's source as cli.py instead of cli/__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 .pyz now 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 by env, 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 $VAR in any env value were executed or expanded when the file was sourced, while \n and 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-local local.env / pre.local.env run at the project levels. Every session start ran the chain, so opening a session in a cloned repository with a top-level env.py was 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) and pathlib_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 .0 patch: 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 of nargs="*" — --agents and --cmdspath are 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 to pathlib_next's own extras.
  • build-pyz's vendored pins moved with those floors — the .pyz now 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 versions pip install dotagents-cli would have refused. The rebuilt .pyz keeps full flag/help/positional fidelity through the zipapp shim.

0.3.3 - 2026-08-16

Added

  • env and context gained --agents-dir (from the shared DotAgentsArgs base) to override the store for one run. Their -g/--global keeps its existing, narrower meaning here — skip the project-level files — and now says so in --help.

Removed

  • build-pyz no longer bundles the repo's tools/ as dotagents/_tools inside the built .pyz, and the --tools-dir flag is gone with it. Nothing read _tools: the compiled audit wrapper and a personal scanner's wrapper that shelled out to it no longer exist. Every shipped artifact carried the dead weight while tools/audit.py claimed it was "not shipped in the .pyz" — now true.
  • dotagents._sync — a pathlib_next.PathSyncer wrapper that existed only to back the install subcommand's backup/copy report. install was 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_next remains a dependency (duho, and --from URI support). Its API-reference page went with it.

Fixed

  • A cluster of "docs say X, tree does Y" corrections — tools/audit.py no longer claims to ship as a bundled audit command module (it is repo CI tooling and there is no dotagents audit) and its --root help names the real default; install.py's usage line drops the removed install and the never-existing audit; the README's tools/ row says where a personal command module actually lives; the API header names the real _overlays exports (install_overlay_dir / apply_overlay / run_overlay_setup) and the real package-data dirs (_overlay / _overlays_src, never a skeleton/); 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_identity no longer pretends to emit AGENTS_AGENT. The line sourced the value from $AGENTS_AGENT and 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 both AGENTS_AGENT and AGENTS_CODE_SESSION_ID (the latter was deliberately never emitted) and says so explicitly, so nothing branches on a var that never arrives.
  • dotagents env and dotagents context now resolve their roots instead of hardcoding them. Both took Path.cwd() as the project root and ~/.agents as the user store, so $AGENTS_PROJECT_ROOT (or the agent-native $CLAUDE_PROJECT_DIR) and $AGENTS_HOME were ignored by the two commands that most needed them — a SessionStart hook runs dotagents context from 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 new dotagents.cli.resolve_user_store() (--agents-dir → $AGENTS_HOME → legacy $DOTAGENTS_AGENTS_DIR → ~/.agents), matching what the package's own resolve_scope docstring 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 no SessionStart equivalent — only PreToolUse/PostToolUse/PreInvocation/PostInvocation/ Stop — so a PreInvocation hook gated on invocationNum == 0 behaves 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 PreToolUse env 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 every Bash tool call via updatedInput.command — the same rewrite mechanism Claude's own PreToolUse hook uses, confirmed directly against Codex's docs.
  • init wires a PowerShell PreToolUse env hook on Windows, closing a real gap: $CLAUDE_ENV_FILE only reaches Claude's Bash tool ($env:CLAUDE_ENV_FILE is 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 .ps1 file — a script file is subject to PowerShell's execution policy, and dotagents has no code-signing certificate; the inline form runs even under Restricted).
  • SessionStart/CwdChanged now register two handlers each — a bash-syntax one and an explicit shell: "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 to powershell -Command on 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-dir base class. init and all four overlays subcommands now inherit it instead of independently redeclaring the same fields, so scope resolution can't silently drift between commands (one previously defaulted agents_dir eagerly to Path.home() / ".agents"; harmless in practice, but needless).

Fixed

  • dotagents env --format powershell/cmd left an already-POSIX PATH unconverted, the mirror of the export/dotenv/fish fix below going the other direction. Found live: run from a genuine Windows PowerShell terminal whose own inherited PATH already 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 watching git.exe fail 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 .pyz degraded -g on any discovered command inheriting a dotagents-defined base class (first hit by DotAgentsArgs above) — 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). --global degraded to the name-derived --global-scope and -g vanished silently. Fixed, with new CI coverage asserting the exact short flag survives a real built pyz.
  • harness_loads relative 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.md user-store file Codex's harness never reads. dotagents context --agents codex emitted an empty sources: [] even with real content present. Now resolved against project_root and compared by full path, like the absolute (~/, /) forms already were.
  • dotagents context --format json crashed with UnicodeEncodeError on any character outside Latin-1 (a bare print() 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:NAME sigil — 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/fish now emit a POSIX PATH on Windows, instead of the OS-native C:\...;C:\... form. This is the exact command the Claude SessionStart hook 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). PATHEXT is 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/yaml are 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

  • init wires agent hooks and links the shared skills dir, for each active agent with a published hook schema (today: Claude and Codex). --no-hooks skips 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 sync are 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-in private-sync overlay, 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/sync never adopt or copy back a <project>/.agents that is itself a git checkout (.git present — dir, or file for worktrees). A hosted-runner session that lists the agents repo as a source gets it cloned to <project>/.agents by 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's git add -A would push as a bare gitlink (and sync's copy-back had the same swallow, with overwrite=True). Both paths now log a skip and leave the checkout in place; link --force keeps an escape hatch that backs the checkout up to .agents.bak* (git state intact) and links the store.
  • dotagents sync now authenticates the private repo directly against github.com when DOTAGENTS_AGENTS_TOKEN is set — and on a hosted runner that rewrites github traffic to a scoped in-session proxy, bypasses the rewrite — so a standalone dotagents sync no 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 -c credential helper in a normal environment, or an isolated GIT_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.sh no longer lets a single container-start clone failure permanently disable the environment. The clone often loses a race with egress/proxy readiness; previously it exit 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.sh also wires that recovery hook when DOTAGENTS_AGENTS_REMOTE is 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 just exit 0'd, leaving nothing to retry — the environment stayed dead every session (observed: setup ran the correct one-liner and emitted only the banner + skipping line, ~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.)
  • .gitignore templates and dotagents link now use a slashless .agents instead of .agents/. link creates .agents as 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_agents is 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.md Leakage rule all recommend .agents.
  • Recommend curl … -o file && sh file over curl … | sh for the setup-script field (README, kb/PRIVATE_SYNC.md, tools/cloud-setup.sh header). With a pipe the field's exit code is sh'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.sh prints starting/done banners (so a setup-script log proves whether it executed — a blank log means the field never invoked it, a config issue) and mkdir -p "$HOME" before git 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 link symlinks a project's .agents to 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; --copy mirrors it as a real dir for no-symlink environments (with automatic fallback), --force handles conflicts. dotagents sync runs git pull --rebase/commit/push on the private repo, copies a copy-mode project's .agents back into its store first (--project), and bootstraps a fresh repo in one command (--remote). Logic in src/dotagents/_link.py; the model keeps per-user config and every project's private .agents in 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) plus hooks/private-sync-{start,stop}.sh (SessionStart clone/pull + link, Stop sync-back) and a settings.snippet.json for ~/.claude/settings.json, so cloud sessions link and sync automatically. Cloud auth is a fine-grained PAT via DOTAGENTS_AGENTS_TOKEN, wired through a git credential helper that reads it from the environment (never persisted to .git/config); hooks/_agents-git-auth.sh auto-detects a hosted-runner github.com→in-session-proxy insteadOf rewrite 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.sh is a self-contained container-start bootstrap (inlines auth + bypass, so it runs before ~/.agents exists) 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 dotagents CLI package (src/dotagents/, built on duho for the argument surface and pathlib_next for copy/URI handling) exposing init (lay down the neutral base overlay), install (base plus opt-in overlays via repeatable --overlays <path>, copied additively), audit (wraps tools/audit_config.py), and build-pyz (vendors pinned duho/pathlib_next via pip install --target + zipapp into a self-contained, downloadable dotagents.pyz). init's AGENTS.md/CLAUDE.md are merged as a marker-delimited managed block so re-running never clobbers customizations outside the block. install --bin-dir writes dotagents/dotagents.cmd wrappers. install.py is a thin shim over dotagents.cli.main().
  • Config content is a neutral base overlay (src/dotagents/_overlay/ — the AGENTS.md scaffolding + design-log convention init writes) plus opt-in overlays (overlays/<name>/): flows (PLAN/EXEC/REVIEW/REPO + MODELS), recovery, references, python/node/rust, agents, tools. Each carries an overlay.toml manifest for a future dotagents overlays subcommand.
  • Repo layout: the CLI in src/dotagents/, config overlays in overlays/, required tooling in top-level tools/ (audit_config.py and 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.py has --repo-hygiene (scans tracked files for personal/machine-specific leftovers).

Changed

  • Migrate the CLI to duho>=0.3.3 (was >=0.1.1). duho's Args/Cmd split means commands are now class X(LoggingArgs, Cmd) with a __call__ entrypoint (was a bare LoggingArgs with __run__) and the umbrella root is class Dotagents(LoggingArgs, Cli). Field declarations (annotation + help string + flags tuple) are unchanged. Bumped the build-pyz vendored duho default to 0.3.3.

Fixed

  • Restore full flag/help fidelity in the built dotagents.pyz under 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 --from to --from-, the link positional to --path, and dropping help text. cli.main now repoints the affected module sources (dotagents.cli, duho.presets) to extracted temp files before dispatch; a no-op for a plain install.