dotagents-cli 0.7.6__tar.gz → 0.8.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/CHANGELOG.md +59 -1
  2. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/PKG-INFO +6 -1
  3. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/README.md +5 -0
  4. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/guide/commands.md +23 -3
  5. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/guide/overlays.md +43 -5
  6. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/guide/private-sync.md +12 -1
  7. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/AGENTS.md +96 -20
  8. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/__init__.py +1 -1
  9. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_context.py +16 -0
  10. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_env.py +40 -3
  11. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_overlay/dotagents/templates/AGENTS.md +7 -0
  12. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_overlays.py +10 -4
  13. dotagents_cli-0.8.0/src/dotagents/_pipwheel.py +319 -0
  14. dotagents_cli-0.8.0/src/dotagents/_pyvenv.py +329 -0
  15. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_resources.py +16 -3
  16. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/cli/build_pyz.py +16 -0
  17. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/cli/init.py +16 -0
  18. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/cli/overlays.py +135 -24
  19. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/conftest.py +21 -1
  20. dotagents_cli-0.8.0/tests/test_cloud_setup.py +97 -0
  21. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_fix_skills_adapters.py +16 -0
  22. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_init_base.py +15 -0
  23. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_overlays_cli.py +61 -0
  24. dotagents_cli-0.8.0/tests/test_pipwheel.py +102 -0
  25. dotagents_cli-0.8.0/tests/test_pyvenv_core.py +216 -0
  26. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tools/cloud-setup.sh +18 -4
  27. dotagents_cli-0.7.6/tests/test_cloud_setup.py +0 -55
  28. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/.gitignore +0 -0
  29. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/LICENSE +0 -0
  30. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/agents.md +0 -0
  31. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/cli.md +0 -0
  32. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/commands.md +0 -0
  33. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/context.md +0 -0
  34. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/env.md +0 -0
  35. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/fs.md +0 -0
  36. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/hooks.md +0 -0
  37. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/merge.md +0 -0
  38. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/overlays.md +0 -0
  39. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/reference.md +0 -0
  40. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/resources.md +0 -0
  41. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/scope.md +0 -0
  42. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/skills.md +0 -0
  43. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/sources.md +0 -0
  44. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/api/wrappers.md +0 -0
  45. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/changelog.md +0 -0
  46. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/guide/authoring.md +0 -0
  47. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/guide/install.md +0 -0
  48. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/docs/index.md +0 -0
  49. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/install.py +0 -0
  50. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/mkdocs.yml +0 -0
  51. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/pyproject.toml +0 -0
  52. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/__main__.py +0 -0
  53. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_agents.py +0 -0
  54. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_fs.py +0 -0
  55. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_hooks.py +0 -0
  56. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_ignore.py +0 -0
  57. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_merge.py +0 -0
  58. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_overlay/dotagents/cmds/findings.py +0 -0
  59. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_overlay/dotagents/cmds/launch.py +0 -0
  60. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_overlay/dotagents/hooks/preinvocation_antigravity_context.py +0 -0
  61. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_overlay/dotagents/hooks/pretooluse_codex_env.py +0 -0
  62. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_overlay/dotagents/hooks/sessionstart_codex_context.py +0 -0
  63. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_overlay/dotagents/templates/PROJECT.md +0 -0
  64. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_scope.py +0 -0
  65. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_skills.py +0 -0
  66. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_sources.py +0 -0
  67. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/_wrappers.py +0 -0
  68. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/cli/__init__.py +0 -0
  69. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/cli/_common.py +0 -0
  70. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/cli/about.py +0 -0
  71. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/cli/context.py +0 -0
  72. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/cli/env.py +0 -0
  73. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/cli/path.py +0 -0
  74. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/src/dotagents/py.typed +0 -0
  75. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/_helpers.py +0 -0
  76. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/_shell.py +0 -0
  77. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_about.py +0 -0
  78. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_agents.py +0 -0
  79. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_api_header.py +0 -0
  80. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_audit_leak.py +0 -0
  81. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_build_pyz.py +0 -0
  82. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_cli_main.py +0 -0
  83. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_cmds_discovery.py +0 -0
  84. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_context.py +0 -0
  85. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_env.py +0 -0
  86. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_env_cli_scope.py +0 -0
  87. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_env_format.py +0 -0
  88. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_env_into.py +0 -0
  89. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_env_regressions.py +0 -0
  90. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_env_safety.py +0 -0
  91. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_findings_cmd.py +0 -0
  92. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_fix_adapters.py +0 -0
  93. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_fix_cli.py +0 -0
  94. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_fix_env.py +0 -0
  95. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_fix_hooks.py +0 -0
  96. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_fix_init.py +0 -0
  97. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_fix_overlays.py +0 -0
  98. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_fix_packaging.py +0 -0
  99. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_fix_scope_context.py +0 -0
  100. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_git_ssl_revoke.py +0 -0
  101. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_hooks.py +0 -0
  102. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_ignore.py +0 -0
  103. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_last_mile.py +0 -0
  104. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_launch_cmd.py +0 -0
  105. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_local_additions.py +0 -0
  106. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_mcp.py +0 -0
  107. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_no_internal_labels.py +0 -0
  108. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_overlay_libs.py +0 -0
  109. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_overlays_command.py +0 -0
  110. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_path_timeout_frontmatter.py +0 -0
  111. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_powershell_env_hook.py +0 -0
  112. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_quoting_injection_safety.py +0 -0
  113. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_scope.py +0 -0
  114. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_sources.py +0 -0
  115. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_sources_uri.py +0 -0
  116. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_wire_hooks.py +0 -0
  117. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tests/test_wrappers.py +0 -0
  118. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tools/audit.py +0 -0
  119. {dotagents_cli-0.7.6 → dotagents_cli-0.8.0}/tools/pyz_smoke.sh +0 -0
@@ -7,6 +7,63 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-10-16
11
+
12
+ ### Added
13
+
14
+ - `overlays add` takes globs: `overlays add '*' -g` installs every overlay the repos
15
+ offer, which for a store with a registry (`<store>/dotagents.toml`) is everything
16
+ it names. A glob matching nothing is an error.
17
+ - `tools/cloud-setup.sh` installs every overlay the store's registry names
18
+ (`overlays add '*'`) before falling back to installing `private-sync` alone.
19
+
20
+ - Each store has a Python venv, `<store>/.pyvenv/<X.Y.Z>-<os>-<arch>/`, made from
21
+ the Python that runs dotagents, where skills run. `init` and `overlays add` /
22
+ `sync` / `remove` keep it current: the user venv always has duho, and every
23
+ overlay's `requirements.txt` is installed (in one pip run, only when a file
24
+ changed, before the overlay's `setup.py`). A project gets its own venv when one of
25
+ its overlays ships requirements or a `lib/`. `--no-venv` skips it for a run;
26
+ `AGENTS_NO_PYVENV=1` turns it off. `.pyvenv/` holds the agents' venvs only: a
27
+ venv there that no sync completed is never used, and a project's own code keeps
28
+ its venvs in the project tree.
29
+ - The venvs layer project > user > system through a `.pth` file in each: the store's
30
+ own overlay libs, then each lower store's venv packages and libs, then dotagents
31
+ itself. A project venv imports the project's packages first, then the user's.
32
+ - `env` points `AGENTS_PYTHON` at the active venv and names it in `AGENTS_PYVENV`;
33
+ overlay `setup.py` scripts run under it. `context` prints it ("Python for
34
+ skills"), and its JSON form carries it as `python`. The managed block tells
35
+ agents to run skill scripts with `$AGENTS_PYTHON`.
36
+ - pip without ensurepip: a venv where the interpreter has no ensurepip is created
37
+ without pip, and pip runs from its wheel (25.3, the last for Python 3.9, checked
38
+ against a pinned sha256). The wheel is found in the store's cache, in a `.pyz`
39
+ built with the new `build-pyz --with-pip`, in `AGENTS_PIP_SOURCE` (a directory of
40
+ wheels, or any URI pathapi-uri reads) or pip's `find-links`, or on your index
41
+ (`PIP_INDEX_URL`, or the mirror in `pip.ini` / `pip.conf`). `AGENTS_PIP_SOURCE`
42
+ is also passed to pip as `--find-links` for the requirements.
43
+
44
+ ### Changed
45
+
46
+ - `overlays add` no longer stops at the first overlay that fails. Each failure is
47
+ logged, the overlays that require the failed one are skipped, every other overlay
48
+ is installed, and the command exits 1 naming each failure. A failed overlay this
49
+ run was installing for the first time is removed again, as before; the ones that
50
+ installed stay.
51
+ - With a scope venv, `env` no longer puts the overlay libs it layers on
52
+ `PYTHONPATH`: the venv's interpreter imports them, and no other Python in the
53
+ session should. Without a venv, `PYTHONPATH` carries them as before.
54
+ `AGENTS_PYTHONPATH` still lists every lib.
55
+ - The base block's "Startup: annotate that you read `…`" line names a store under
56
+ the home directory from there (`~/.agents/AGENTS.md`), and any other store by its
57
+ absolute path as before. A store kept in git and used on machines whose home
58
+ directories differ no longer shows `AGENTS.md` modified on all but one of them.
59
+
60
+ ### Fixed
61
+
62
+ - `env --cache` on Windows with Python before 3.13 could miss a cache it had just
63
+ written: `time.time()` there lags the file's own timestamp by up to one clock
64
+ tick, and a cache file dated after "now" was treated as stale. A cache file
65
+ dated up to 2 s in the future now counts as fresh.
66
+
10
67
  ## [0.7.6] - 2026-10-15
11
68
 
12
69
  ### Added
@@ -1470,7 +1527,8 @@ Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0)
1470
1527
  affected module sources (`dotagents.cli`, `duho.presets`) to extracted temp files
1471
1528
  before dispatch; a no-op for a plain install.
1472
1529
 
1473
- [Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.7.6...HEAD
1530
+ [Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.8.0...HEAD
1531
+ [0.8.0]: https://github.com/jose-pr/dotagents/compare/v0.7.6...v0.8.0
1474
1532
  [0.7.6]: https://github.com/jose-pr/dotagents/compare/v0.7.5...v0.7.6
1475
1533
  [0.7.5]: https://github.com/jose-pr/dotagents/compare/v0.7.4...v0.7.5
1476
1534
  [0.7.4]: https://github.com/jose-pr/dotagents/compare/v0.7.3...v0.7.4
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: dotagents-cli
3
- Version: 0.7.6
3
+ Version: 0.8.0
4
4
  Summary: The dotagents CLI: install and manage a portable ~/.agents config for AI coding agents
5
5
  Project-URL: Homepage, https://github.com/jose-pr/dotagents/
6
6
  Project-URL: Repository, https://github.com/jose-pr/dotagents
@@ -215,6 +215,11 @@ store's shared `skills/` so every agent sees the same skills.
215
215
  `.ignore` at the overlay's root match (setup output, local state) and tool caches are left
216
216
  alone; `--prune` clears them too. A fresh `add` whose setup script fails is rolled
217
217
  back.
218
+ - Each store has a Python venv, `<store>/.pyvenv/<version>-<os>-<arch>/`, where skills
219
+ run (`$AGENTS_PYTHON`): duho, every overlay's `requirements.txt` and every overlay
220
+ `lib/` import there, layered project > user > system. pip comes from the
221
+ interpreter, else its pinned wheel (cache, `AGENTS_PIP_SOURCE`, your index mirror,
222
+ or a `.pyz` built `--with-pip`).
218
223
  - `sync` refreshes each overlay from the repo it was installed from (`--repo` replaces
219
224
  that source), installs a `requires` added upstream, and re-merges the managed block.
220
225
  - `remove` deletes the overlay's directory, unpublishes only the skills **it**
@@ -160,6 +160,11 @@ store's shared `skills/` so every agent sees the same skills.
160
160
  `.ignore` at the overlay's root match (setup output, local state) and tool caches are left
161
161
  alone; `--prune` clears them too. A fresh `add` whose setup script fails is rolled
162
162
  back.
163
+ - Each store has a Python venv, `<store>/.pyvenv/<version>-<os>-<arch>/`, where skills
164
+ run (`$AGENTS_PYTHON`): duho, every overlay's `requirements.txt` and every overlay
165
+ `lib/` import there, layered project > user > system. pip comes from the
166
+ interpreter, else its pinned wheel (cache, `AGENTS_PIP_SOURCE`, your index mirror,
167
+ or a `.pyz` built `--with-pip`).
163
168
  - `sync` refreshes each overlay from the repo it was installed from (`--repo` replaces
164
169
  that source), installs a `requires` added upstream, and re-merges the managed block.
165
170
  - `remove` deletes the overlay's directory, unpublishes only the skills **it**
@@ -100,10 +100,15 @@ dotagents init # base config into ./.agents (project sc
100
100
  dotagents init -g # ...into ~/.agents (user scope)
101
101
  dotagents init --bin-dir ~/.local/bin # also write a `dotagents` command on PATH
102
102
  dotagents init --no-hooks # skip agent hook wiring + the skills links
103
+ dotagents init --no-venv # skip the store's Python venv (duho for skills)
103
104
  dotagents init --agents claude,codex # exactly these agents (default: detected + claude)
104
105
  dotagents init --dry-run
105
106
  ```
106
107
 
108
+ `init` also creates or updates the store's Python venv, where skills run (see
109
+ [the scope venv](overlays.md#python-dependencies-and-the-scope-venv)). Offline, or
110
+ with no pip to be had, that is a warning; `overlays sync` retries.
111
+
107
112
  ### Hook wiring
108
113
 
109
114
  For each active agent, `init` adds what that harness needs to load the config, and
@@ -198,6 +203,7 @@ Manages opt-in overlays **by name**. See [Overlays](overlays.md) for the full mo
198
203
 
199
204
  ```bash
200
205
  dotagents overlays add python engineering # install into the scope, publish skills, merge rules/routing
206
+ dotagents overlays add '*' -g # everything the repos offer (a store registry: all it names)
201
207
  dotagents overlays list # installed (discovered) + available from the repos
202
208
  dotagents overlays sync 'py*' # refresh installed overlays matching a glob
203
209
  dotagents overlays sync --prune # also clear what .gitignore/.ignore protect: exactly upstream
@@ -205,7 +211,14 @@ dotagents overlays remove python # delete the overlay dir, unpublish i
205
211
  dotagents overlays show python # describe one: manifest, requires, setup, skills, source (--json)
206
212
  ```
207
213
 
208
- - **`add`** installs what each manifest's `requires` names first, each requirement
214
+ - **`add`** takes names or globs: `'*'` is every overlay the repos offer, so with
215
+ a store registry it installs everything the registry names, and `'py*'` the ones
216
+ starting with `py`; a glob that matches nothing is an error. One overlay failing
217
+ (its copy, requirements or setup) does not stop the rest: it is logged, what
218
+ requires it is skipped, everything else installs, and the command exits 1 naming
219
+ each failure. A failed overlay this run was installing for the first time is
220
+ removed again; one that was already installed is left in place, not set up.
221
+ `add` installs what each manifest's `requires` names first, each requirement
209
222
  installed and set up before the overlay that needs it (`--no-requires` to skip); a
210
223
  requirement the scope already has, in its own store or the user store for a project,
211
224
  is satisfied and left alone, while a name you ask for explicitly is always
@@ -213,8 +226,11 @@ dotagents overlays show python # describe one: manifest, requires, s
213
226
  before touching anything — the first repo that offers a name wins — records where
214
227
  each overlay came from, and publishes skills from the installed copy, never
215
228
  replacing a skill already in the shared `skills/` dir (and links them into
216
- Claude's skills dir when `init` wired Claude for the store). A fresh install whose setup
217
- script fails is rolled back. A source inside the store's own `overlays/` directory
229
+ Claude's skills dir when `init` wired Claude for the store). Each overlay's
230
+ `requirements.txt` is installed into the scope venv before its setup script runs
231
+ (`--no-venv` skips it; see
232
+ [the scope venv](overlays.md#python-dependencies-and-the-scope-venv)). A fresh install
233
+ whose requirements or setup script fail is rolled back. A source inside the store's own `overlays/` directory
218
234
  is refused (by `sync` too): `remove` would delete the only copy.
219
235
  - `add`, `remove` and `show` without an overlay name are usage errors (exit 2), and
220
236
  a `--dry-run` says what it would write (`would recompose ...`) instead of reporting
@@ -590,6 +606,7 @@ is not listed.
590
606
  ```bash
591
607
  python -m dotagents build-pyz --out dist/dotagents.pyz
592
608
  python -m dotagents build-pyz --extras uri,repo # also vendor what those extras install
609
+ python -m dotagents build-pyz --with-pip # also carry pip, for machines without it
593
610
  ```
594
611
 
595
612
  Builds a self-contained zipapp from a source checkout, with `duho` and `pathapi`
@@ -608,6 +625,9 @@ vendored (plus what the dotagents-cli extras named by `--extras` install), so it
608
625
  vendored as pure-Python wheels resolved for Python 3.9, so the file does not
609
626
  depend on the machine that built it; `sftp` needs native modules and fails the
610
627
  build with a message saying so. The archive is compressed.
628
+ - `--with-pip` — also vendor the pinned pip wheel (about 1.8 MB), so the `.pyz` can
629
+ set up the scope venv where Python has no ensurepip and no index is reachable
630
+ (see [the scope venv](overlays.md#python-dependencies-and-the-scope-venv)).
611
631
 
612
632
  Each release attaches a built one:
613
633
  <https://github.com/jose-pr/dotagents/releases/latest/download/dotagents.pyz>.
@@ -226,12 +226,49 @@ copies, where symlinks are unavailable) into the scope's shared skills dir, so e
226
226
  agent that reads that dir picks up the same skills. Removing the overlay unpublishes
227
227
  only the skills it published, then sweeps any now-broken symlinks.
228
228
 
229
+ ## Python dependencies and the scope venv
230
+
231
+ Every store has a Python venv, `<store>/.pyvenv/<X.Y.Z>-<os>-<arch>/` (for example
232
+ `~/.agents/.pyvenv/3.14.7-nt-arm64`), made from the Python that runs dotagents.
233
+ A store's `.pyvenv/` holds these **agents' venvs only**: skills, overlay helpers and
234
+ setup scripts run there. A project's own code gets its venvs beside the store, in
235
+ the project root's `.pyvenv/` (`<project>/.pyvenv/<version>-<os>-<arch>/`, which the
236
+ python overlay's `dotagents pyvenv` creates); only a venv dotagents itself synced is
237
+ ever used as an agents' venv. `init` and every
238
+ `overlays add` / `sync` / `remove` keep it current:
239
+
240
+ - The user store's venv always has **duho**, so any skill can build a command line
241
+ on it, plus the packages each overlay installed there lists in a
242
+ **`requirements.txt`** at its root (pip's format). They are installed in one pip
243
+ run, so pip sees every constraint at once, and only when a file changed.
244
+ - A project gets its own venv only when one of its overlays ships a
245
+ `requirements.txt` or a `lib/`; otherwise a project session uses the user venv.
246
+ - The venvs **layer like everything else**. A `.pth` file in each venv lists, highest
247
+ precedence first: the store's own overlay libs, then the user venv's packages and
248
+ libs, then the system store's. dotagents itself comes last. So in a project venv,
249
+ the project's packages and libs win, then the user's, then the system's.
250
+ - `dotagents env` points **`AGENTS_PYTHON`** at the active venv (and names it in
251
+ `AGENTS_PYVENV`). Overlay `setup.py` and `env.py` run under it, and the managed
252
+ block tells agents to run skill scripts with it. Libs the venv layers stay off
253
+ `PYTHONPATH`, so no other Python in the session, such as a project's own venv,
254
+ imports them by accident.
255
+
256
+ **No ensurepip, no network?** pip runs from its wheel, pinned by version (25.3, the
257
+ last for Python 3.9) and hash, found in this order: the store's cache, a copy
258
+ vendored into the `.pyz` (`build-pyz --with-pip`), `AGENTS_PIP_SOURCE` (a directory
259
+ of wheels, or any URI pathapi-uri reads: `file:`, `s3:`, `sftp:`, ...), pip's own
260
+ `find-links`, then your index (`PIP_INDEX_URL` or the mirror in `pip.ini` /
261
+ `pip.conf`). `AGENTS_PIP_SOURCE` is also handed to pip as `--find-links` for the
262
+ requirements themselves.
263
+
264
+ Opt out per run with `--no-venv`, or for good with `AGENTS_NO_PYVENV=1`.
265
+
229
266
  ## Setup scripts
230
267
 
231
268
  An overlay may ship an **idempotent** `setup.py` at its root — the recommended,
232
- OS-agnostic form: it runs under the same Python that runs dotagents, so it works on
233
- every platform. After `add` / `sync` copies the overlay
234
- in, dotagents runs the script automatically. Reserve it for real install-time work:
269
+ OS-agnostic form: it runs under `AGENTS_PYTHON` (the scope venv above, with the
270
+ overlay's requirements installed first), so it works on every platform. After
271
+ `add` / `sync` copies the overlay in, dotagents runs the script automatically. Reserve it for real install-time work:
235
272
  `PATH`, `PYTHONPATH` and `$<NAME>_OVERLAY_ROOT` are `dotagents env`'s job
236
273
  (above), and an overlay's own env vars belong in its `env.py`, not in a script that
237
274
  writes one into the store — none of the example overlays ship a setup script any more.
@@ -242,8 +279,9 @@ Presence of a script is the opt-in; skip it with `--no-setup`. The author contra
242
279
  - **Environment** carries `AGENTS_HOME` (the user store), `AGENTS_SCOPE_ROOT` (the
243
280
  store the overlay is installed into), `AGENTS_SCOPE` (`user` or `project`) and
244
281
  `AGENTS_OVERLAY_DIR` (the overlay's own installed dir) — never hardcode a home path.
245
- - **Environment** is also the scope's assembled `dotagents env`: every overlay's `lib/`
246
- on `PYTHONPATH`, its `bin/` on `PATH`.
282
+ - **Environment** is also the scope's assembled `dotagents env`: every overlay's `bin/`
283
+ on `PATH`, and every overlay's `lib/` importable (through the venv's layering, or
284
+ `PYTHONPATH` without a venv).
247
285
  - **Time limit**: 300 seconds, or the manifest's `setup_timeout`, or the command's
248
286
  `--setup-timeout` (which wins); `0` means no limit. A script still running then is
249
287
  stopped, and that counts as a failure (exit 124).
@@ -19,6 +19,16 @@ never lands in the public repo). `<name>` defaults to the project's basename, so
19
19
  local checkout and a cloud checkout of the same project resolve to the same store
20
20
  entry.
21
21
 
22
+ The store's installed overlays can travel two ways:
23
+
24
+ - **Tracked copies**: `overlays/` is committed. Every machine and cloud session has
25
+ them with the clone, offline; every `overlays sync` is a diff in the private repo.
26
+ - **A registry**: `<store>/dotagents.toml` names one source per overlay and
27
+ `overlays/` is left out of the repo. `dotagents overlays add '*' -g` installs
28
+ everything it names, keeping going past one that fails; the bootstrap below runs
29
+ it in a cloud session. The repo stays small; each new machine fetches the
30
+ overlays once.
31
+
22
32
  ## Commands
23
33
 
24
34
  The overlay goes into the user store, since the store is what it syncs:
@@ -82,7 +92,8 @@ curl -fsSL https://raw.githubusercontent.com/<you>/dotagents/main/tools/cloud-se
82
92
  success. `&&` propagates the curl failure instead.
83
93
 
84
94
  The bootstrap authenticates (bypassing a hosted-runner `github.com` → proxy git
85
- rewrite), clones/pulls the store, installs the CLI, links the project, and wires the
95
+ rewrite), clones/pulls the store, installs the CLI, installs every overlay the store's
96
+ registry names (`overlays add '*'`, when it has one), links the project, and wires the
86
97
  hooks into `~/.claude/settings.json` — driven by `AGENTS_REMOTE` /
87
98
  `DOTAGENTS_AGENTS_TOKEN` / `DOTAGENTS_CLI_INSTALL` environment variables (the token is
88
99
  never committed). `AGENTS_OVERLAYS_REMOTE` / `AGENTS_OVERLAYS_REF` choose where it
@@ -30,10 +30,10 @@ and an error saying a command is required, and exits 2.
30
30
 
31
31
  | Command | Contract |
32
32
  | --- | --- |
33
- | `init [-g] [--dest D] [--from SRC] [--bin-dir D] [--agents a,b] [--no-hooks] [--powershell-env-hook] [--force] [--dry-run]` | Merge the base block into `<store>/AGENTS.md`, write `<store>/bin/dotagents[.cmd]`, and per active agent (`--agents`, else detected + `claude`) write its include and hooks. `--force` replaces `AGENTS.md`'s content, backed up under `<store>/install_backup/<timestamp>/`. `--from` records its base in `<store>/dotagents/config.toml`. |
34
- | `overlays add NAME... [-g] [--repo SPEC]... [--copy] [--no-setup] [--setup-timeout S] [--no-requires] [--prune] [--dry-run]` | Install each overlay and its `requires` into `<store>/overlays/<name>/` (an installed one is made to match its source again, as `sync` does; `--prune` clears its ignored files too), publish its skills, run its `setup.py`, recompose the managed block. Every name is resolved before anything is written; a source inside `<store>/overlays/` is refused; a fresh install whose setup fails is rolled back. No name: exit 2. |
35
- | `overlays remove NAME... [-g] [--force] [--dry-run]` | Delete the overlay dirs, unpublish the skills they published, recompose the block. Refuses an overlay another installed overlay requires unless `--force`. No name: exit 2. |
36
- | `overlays sync [GLOB] [-g] [--repo SPEC]... [--copy] [--prune] [--no-setup] [--setup-timeout S] [--dry-run]` | Refresh installed overlays from the repo recorded at install (`--repo` replaces it): the install is made to match the source exactly: new and changed files land, `overlay.toml` is refreshed, files the source does not ship are removed; a file edited here is backed up first under `<store>/install_backup/<timestamp>/overlays/<name>/`. What the `.gitignore` / `.ignore` at the overlay's root match (setup output, local state) and tool caches are left alone; `--prune` clears them too. Installs new `requires`. Exits 1 when an overlay's repo cannot be loaded, its source lies inside `<store>/overlays/`, or its setup fails. |
33
+ | `init [-g] [--dest D] [--from SRC] [--bin-dir D] [--agents a,b] [--no-hooks] [--powershell-env-hook] [--no-venv] [--force] [--dry-run]` | Merge the base block into `<store>/AGENTS.md`, write `<store>/bin/dotagents[.cmd]`, and per active agent (`--agents`, else detected + `claude`) write its include and hooks. Then bring the scope's Python venvs up to date (`_pyvenv.sync_venvs`; a failure is a warning; not with `--dest` or `--no-venv`). `--force` replaces `AGENTS.md`'s content, backed up under `<store>/install_backup/<timestamp>/`. `--from` records its base in `<store>/dotagents/config.toml`. |
34
+ | `overlays add NAME\|GLOB... [-g] [--repo SPEC]... [--copy] [--no-setup] [--setup-timeout S] [--no-requires] [--no-venv] [--prune] [--dry-run]` | Install each overlay and its `requires` into `<store>/overlays/<name>/` (an installed one is made to match its source again, as `sync` does; `--prune` clears its ignored files too), publish its skills, install every overlay's `requirements.txt` into the scope venv (`_pyvenv`; not with `--no-venv`), run its `setup.py`, recompose the managed block. A glob (`'*'`, `'py*'`) stands for every name the repos offer that matches it; one matching nothing is an error. Every name is resolved before anything is written; a source inside `<store>/overlays/` is refused. An overlay whose copy, requirements or setup fail is logged and skipped, along with what requires it; the others still install, and the command exits 1 naming each failure. One this run was installing for the first time is removed again; one already installed stays. No name: exit 2. |
35
+ | `overlays remove NAME... [-g] [--force] [--no-venv] [--dry-run]` | Delete the overlay dirs, unpublish the skills they published, recompose the block, rewrite the venv layering (installed requirements stay). Refuses an overlay another installed overlay requires unless `--force`. No name: exit 2. |
36
+ | `overlays sync [GLOB] [-g] [--repo SPEC]... [--copy] [--prune] [--no-setup] [--setup-timeout S] [--no-venv] [--dry-run]` | Refresh installed overlays from the repo recorded at install (`--repo` replaces it): the install is made to match the source exactly: new and changed files land, `overlay.toml` is refreshed, files the source does not ship are removed; a file edited here is backed up first under `<store>/install_backup/<timestamp>/overlays/<name>/`. What the `.gitignore` / `.ignore` at the overlay's root match (setup output, local state) and tool caches are left alone; `--prune` clears them too. Installs new `requires`, then the venv requirements (when they changed) before each setup. Exits 1 when an overlay's repo cannot be loaded, its source lies inside `<store>/overlays/`, or its requirements or setup fail. |
37
37
  | `overlays list [-g] [--repo SPEC]... [--json]` | Installed overlays per store (shadowed copies marked, unmet `requires` flagged) and what the repos offer. |
38
38
  | `overlays show NAME [-g] [--repo SPEC]... [--json]` | One overlay: the copy a session would use, else the source's; manifest, setup, skills, file count, recorded source. No name: exit 2. |
39
39
  | `context [OUT] [-g] [--agents a,b] [--format markdown\|system-reminder\|json] [--write-agent] [--inline]` | Print (or write to `OUT`) the assembled context. `--write-agent` merges it into each agent's `context_target` under the project root (not with `-g`). Exits 2 when `--agents` names no known agent. |
@@ -42,7 +42,7 @@ and an error saying a command is required, and exits 2.
42
42
  | `launch [AGENT] [-g] [--command PROG] [--no-context] [--inline] [--write-agent] [--dry-run] [-- ARGS...]` | Run the agent's CLI with the env applied and the context handed over; the exit code is the harness's (`128+N` on signal N). |
43
43
  | `findings add\|list\|show\|done\|reopen\|remove\|index\|path [-g] [--dir D]` | The findings queue at `<store>/findings/` (`--dir` overrides): one markdown file per finding, `done` moves it to `processed/` with a required resolution, `INDEX.md` regenerated on every change. The frontmatter is valid YAML (a value is double-quoted when it must be); `index --requote` fixes values written bare in existing files, touching nothing else. |
44
44
  | `about [--json]` | `dotagents-cli <version>`, then `<distribution> <version>` per bundled (`.pyz`) or installed package. |
45
- | `build-pyz [--out P] [--python SHEBANG] [--duho-version V] [--pathapi-version V] [--extras a,b]` | Build a zipapp from a source checkout; errors elsewhere. |
45
+ | `build-pyz [--out P] [--python SHEBANG] [--duho-version V] [--pathapi-version V] [--extras a,b] [--with-pip]` | Build a zipapp from a source checkout; errors elsewhere. `--with-pip` vendors the pinned pip wheel (`_pipwheel`) as `dotagents/_vendor/`. |
46
46
 
47
47
  **Every command but `env` is also an MCP tool.** With `DOTAGENTS_MCP=stdio` in its
48
48
  environment, `dotagents` serves the command set — built-in, bundled and discovered —
@@ -142,8 +142,11 @@ The package's own data; library code, importing nothing from `dotagents.cli`.
142
142
  `AGENTS_MD_PLACEHOLDER = "{{AGENTS_MD}}"`.
143
143
  - `base_agents_text(src, dest, *, project=False) -> str` — the base block for the
144
144
  store at `dest`: the project template for a project store when the base has one,
145
- else the user template, else `<src>/AGENTS.md`; `{{AGENTS_MD}}` rendered as the
146
- absolute POSIX path of `<dest>/AGENTS.md`.
145
+ else the user template, else `<src>/AGENTS.md`; `{{AGENTS_MD}}` rendered as
146
+ `home_relative(<dest>/AGENTS.md)`, so a store synced between machines whose home
147
+ directories differ renders the same block.
148
+ - `home_relative(path) -> str` — `path` as POSIX text, `~/<rest>` when it is under
149
+ `Path.home()`, absolute otherwise.
147
150
  - `pyz_archive() -> str | None` — the `.pyz` this dotagents is imported from (the
148
151
  ancestor of the module's `__file__` that is a file), or `None` for a plain
149
152
  install. The archive also holds the vendored duho and pathapi; `env`
@@ -384,8 +387,12 @@ proxy model.
384
387
 
385
388
  - Roots: `AGENTS_HOME` and `AGENTS_PROJECT_ROOT` name the stores walked, absolute —
386
389
  an inherited value naming the same directory holds, another is replaced; in a
387
- user-scope walk `AGENTS_PROJECT_ROOT` is set only when unset. Only when unset:
388
- `AGENTS_PYTHON` (`sys.executable`) and one `<NAME>_OVERLAY_ROOT` per
390
+ user-scope walk `AGENTS_PROJECT_ROOT` is set only when unset. `AGENTS_PYTHON`:
391
+ the active scope venv's interpreter (`_pyvenv.active_venv`), else
392
+ `sys.executable`; it replaces an inherited value equal to `sys.executable` or
393
+ naming another `.pyvenv` interpreter, and any other pin holds. `AGENTS_PYVENV`:
394
+ that venv's dir, removed when there is none. Only when unset: one
395
+ `<NAME>_OVERLAY_ROOT` per
389
396
  `scope.overlays` (a value pinned to the user store's copy is re-pointed when a
390
397
  project copy shadows it). `AGENTS_PYLIB` (`_env.PYLIB_VAR`): the `.pyz` when
391
398
  dotagents runs from one (`_resources.pyz_archive()`), else not set — where its
@@ -395,10 +402,11 @@ proxy model.
395
402
  - `PATH`: every level's `bin` (contract A, not project-root, missing dirs included)
396
403
  at the front in that order, whatever the caller's `PATH` held; empty and
397
404
  cwd-relative inherited entries are dropped.
398
- - `PYTHONPATH`: every existing level's `lib` at the front, highest precedence
399
- first; the entries an inherited `AGENTS_PYTHONPATH` named (another scope's libs)
400
- are removed first. `AGENTS_PYTHONPATH`: the same list, `os.pathsep`-joined;
401
- removed when there are none.
405
+ - `PYTHONPATH`: every existing level's `lib` the active venv's `.pth` does not
406
+ already layer, at the front, highest precedence first (with a venv synced for the
407
+ scope, none); the entries an inherited `AGENTS_PYTHONPATH` named (another scope's
408
+ libs) are removed first. `AGENTS_PYTHONPATH`: every existing `lib`,
409
+ `os.pathsep`-joined, layered or not; removed when there are none.
402
410
  - Env files: all `pre.env.py` / `pre.env` (+ the project store's `pre.local.env`),
403
411
  then all `env.py` / `env` (+ `local.env`), contract-A order, regular files only,
404
412
  each evaluated against everything before it. The project root contributes none.
@@ -444,7 +452,8 @@ API:
444
452
  - `ENV_CACHE_TTL = 300.0` (seconds). `env_cache_key(scope, base_env, *parts) -> str`
445
453
  — a digest of what an assembly reads without running it: roots, cwd, interpreter,
446
454
  package version, installed overlays, each env-file candidate's
447
- existence/mtime/size, every file under an existing `lib`, the whole `base_env`,
455
+ existence/mtime/size, every file under an existing `lib`, each store's venv state
456
+ file, the whole `base_env`,
448
457
  and `parts` (format, mode). `read_env_cache(scope, key, *, now=None) ->
449
458
  Optional[str]` (None when absent or older than the TTL); `write_env_cache(scope,
450
459
  key, text) -> None` (owner-only, under `<user store>/.cache/env/`; never raises).
@@ -462,7 +471,9 @@ API:
462
471
  and `AGENTS.local.md` (project store and project root), each prefixed
463
472
  `<!-- Source: <path> -->`. Then a skills listing (`- **name** (`<SKILL.md>`):
464
473
  description`, from each skill's leading frontmatter; a project skill wins), then
465
- a "Local additions to overlay files" section when `local_additions` finds any.
474
+ a "Local additions to overlay files" section when `local_additions` finds any,
475
+ then, with `expand_vars`, the line ``Python for skills (`$AGENTS_PYTHON`): `<path>` ``
476
+ (`skills_python`).
466
477
  `<PROJECT_ROOT>` and `<NAME_OVERLAY_ROOT>` placeholders expand; with
467
478
  `expand_vars` so do `$NAME_OVERLAY_ROOT` / `${NAME_OVERLAY_ROOT}`. `inline`
468
479
  appends the on-demand `.md` files the sources reference, each resolved against
@@ -470,8 +481,10 @@ API:
470
481
  system source never into the project).
471
482
  - `assemble_context_data(agent, scope, *, inline=False, expand_vars=True) -> dict`
472
483
  — `{"agent", "harness", "sources", "context", "skills": [{"name",
473
- "description", "path"}], "local_additions": [{"overlay_file", "local": [...]}]}`;
474
- `context` without the skills and local-additions listings.
484
+ "description", "path"}], "local_additions": [{"overlay_file", "local": [...]}],
485
+ "python"}`; `context` without the skills and local-additions listings.
486
+ - `skills_python(scope) -> str` — the active scope venv's interpreter, else
487
+ `sys.executable`.
475
488
  - `local_additions(scope) -> list[tuple[Path, list[Path]]]` — the user's own
476
489
  additions to installed overlay files: for each `.md` an overlay in `scope.overlays`
477
490
  ships in a subdirectory other than `skills/`, every store's file at the same
@@ -675,6 +688,62 @@ objects `{"matcher"?: str, "hooks": [{"type": "command", "command": str, ...}]}`
675
688
  - `check_path_warning(bin_dir) -> Optional[str]` — a warning with the PATH line to
676
689
  add, or `None` when `bin_dir` is on `PATH`.
677
690
 
691
+ ## `dotagents._pyvenv`
692
+
693
+ One venv per store, `<store>/.pyvenv/<X.Y.Z>-<os>-<arch>/`, made from the running
694
+ interpreter; layered project > user > system through a `.pth`. `.pyvenv/` holds the
695
+ agents' venvs only (skills, overlay helpers, setup scripts), never a project's own
696
+ code. `AGENTS_NO_PYVENV=1` (or `true`/`yes`/`on`) turns it all off.
697
+
698
+ - `BASE_REQUIREMENTS = ("duho>=0.7.5,<0.8",)` — always in the user venv; dotagents'
699
+ own duho range. `REQUIREMENTS_FILE = "requirements.txt"` (at an overlay's root),
700
+ `PYVENV_DIR = ".pyvenv"`, `STATE_FILE = ".dotagents-venv.json"` (in the venv:
701
+ `{"digest", "requirements", "pth"}`), `PTH_FILE = "dotagents-layers.pth"` (in its
702
+ site-packages), `DISABLE_ENV = "AGENTS_NO_PYVENV"`.
703
+ - `sync_venvs(scope, *, dry_run=False, logger=None) -> list[Path]` — the user venv
704
+ (base requirements + the user store's overlays' `requirements.txt`), then the
705
+ project's when `wants_venv` (its own overlays' requirements only). pip runs only
706
+ when the requirements' digest changed, in one `pip install -r ... -r ...` per
707
+ venv, with `--find-links` from `AGENTS_PIP_SOURCE`. The `.pth` is rewritten when
708
+ it differs: the store's own libs, then each lower store's venv site-packages and
709
+ libs, then `runtime_entry()`. Raises `VenvError` (a `SystemExit`) on failure.
710
+ - `wants_venv(scope, level, store, libs) -> bool` — always for `user`; for `project`
711
+ when it has overlay requirements or libs; never for `system` (a venv an
712
+ administrator made there is still layered).
713
+ - `ensure_venv(target, python=None, *, with_pip=None, dry_run=False, logger=None)
714
+ -> Optional[Path]` — the venv's interpreter, created when missing; `with_pip=None`
715
+ tries ensurepip and falls back to `--without-pip`, `False` never uses it.
716
+ - `pip_command(vpy, *, cache_dir, logger=None) -> list[str]` — the venv's own
717
+ `-m pip`, else `[sys.executable, <pip wheel>/pip, "--python", <vpy>]`.
718
+ - `active_venv(scope, environ=None) -> Optional[Path]` — the highest store's venv
719
+ for the running interpreter that a sync completed (its `STATE_FILE` exists); a
720
+ venv made there any other way is never an agents venv.
721
+ - `layers(scope) -> list[tuple[str, Path, list[Path]]]` — `(level, store, libs)`,
722
+ highest first. `requirement_files(scope, store) -> list[Path]`.
723
+ `runtime_entry() -> str` (the `.pyz`, else the dir holding `dotagents`).
724
+ `venv_dir(store)`, `venv_python(venv)`, `site_packages(venv)`, `triplet()`,
725
+ `os_bucket()`, `read_state(venv)`, `disabled(environ=None)`,
726
+ `is_pyvenv_python(path)`.
727
+
728
+ ## `dotagents._pipwheel`
729
+
730
+ The pip wheel for a venv with no pip of its own.
731
+
732
+ - `PIP_VERSION = "25.3"` (the last pip for Python 3.9), `PIP_WHEEL`, `PIP_SHA256` —
733
+ every source must match the hash. `PIP_SOURCE_ENV = "AGENTS_PIP_SOURCE"`.
734
+ `VENDORED = ("_vendor", PIP_WHEEL)` (inside the package),
735
+ `DEFAULT_INDEX = "https://pypi.org/simple"`.
736
+ - `pip_wheel(cache_dir, *, logger=None, environ=None) -> Path` — from, in order: the
737
+ cache file, the vendored copy (`build-pyz --with-pip`), `AGENTS_PIP_SOURCE` and pip's
738
+ `find-links` (a dir, `file:`/`http(s):`, or a pathapi-uri URI), then the index
739
+ (`PIP_INDEX_URL`, else pip's config `index-url`, else PyPI; PEP 691 JSON, else PEP
740
+ 503 HTML), honouring pip's `cert`, `trusted-host`, `proxy` and credentials in the
741
+ URL. Raises `PipUnavailable` (a `SystemExit`) naming every source, redacted.
742
+ - `find_links(environ=None, *, cache_dir, logger=None) -> list[str]` —
743
+ `AGENTS_PIP_SOURCE` for pip's `--find-links`, a URI pip cannot read copied locally.
744
+ - `pip_config(environ=None) -> dict[str, str]` — `index-url`, `find-links`, `cert`,
745
+ `trusted-host`, `proxy` from `PIP_*` over pip's config files.
746
+
678
747
  ## `dotagents._fs`
679
748
 
680
749
  - `write_text_lf(path, text, *, atomic=False) -> Path` — UTF-8, LF line endings on
@@ -705,8 +774,12 @@ Read:
705
774
  - `AGENTS_OVERLAYS_REPO`, `AGENTS_OVERLAYS_REPO_<KEY>` — overlay repos (after
706
775
  `--repo`). `AGENTS_CMDS_PATH` — extra command dirs.
707
776
  - `AGENTS_HARNESS` — the running harness (registry name or harness id), for
708
- identity. `AGENTS_PYTHON` — the interpreter for `env.py`. `AGENTS_PYTHONPATH` —
709
- the overlay/store `lib` dirs `env` put on `PYTHONPATH`, highest precedence first.
777
+ identity. `AGENTS_PYTHON` — the interpreter for `env.py` and `setup.py`.
778
+ `AGENTS_PYTHONPATH` — the overlay/store `lib` dirs, highest precedence first.
779
+ `AGENTS_NO_PYVENV` — turns the scope venvs off. `AGENTS_PIP_SOURCE` — a directory
780
+ of wheels (or a URI to one) for pip, before the index. `PIP_INDEX_URL`,
781
+ `PIP_CONFIG_FILE`, `PIP_FIND_LINKS`, `PIP_CERT`, `PIP_TRUSTED_HOST`, `PIP_PROXY` and
782
+ pip's config files — read to fetch the pip wheel.
710
783
  `AGENTS_PYLIB` — the `.pyz` dotagents runs from (duho and pathapi import
711
784
  from it), unset for an installed dotagents.
712
785
  - Harness config dirs: `CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `PI_CODING_AGENT_DIR`.
@@ -722,7 +795,7 @@ Read:
722
795
 
723
796
  Emitted by `env` (and applied by `launch`): `AGENTS_HARNESS`, `AGENTS_VENDOR`,
724
797
  `AGENT`, `AGENTS_MODEL`, `AGENTS_HOME`, `AGENTS_PROJECT_ROOT`, `AGENTS_PYTHON`,
725
- `AGENTS_PYLIB` (from a `.pyz` only), `<NAME>_OVERLAY_ROOT`, `PATH`, `AGENTS_PYTHONPATH`, `AGENTS_PROXY` and the mirrored
798
+ `AGENTS_PYVENV` (with a scope venv), `AGENTS_PYLIB` (from a `.pyz` only), `<NAME>_OVERLAY_ROOT`, `PATH`, `AGENTS_PYTHONPATH`, `AGENTS_PROXY` and the mirrored
726
799
  proxy vars, plus whatever the env files set. `AGENTS_AGENT` is never emitted.
727
800
  `launch` also exports `AGENTS_CONTEXT_FILE`.
728
801
 
@@ -739,6 +812,9 @@ Set for an overlay's `setup.py`: `AGENTS_HOME` (user store), `AGENTS_SCOPE_ROOT`
739
812
  scratch dir removed at exit); `duho` reads the command modules' flags and help
740
813
  through the zip importer.
741
814
  - **Writes are LF-only.** Use `_fs.write_text_lf`, never `Path.write_text`.
815
+ - **Skills run under `AGENTS_PYTHON`**, the scope venv: duho, every overlay's
816
+ requirements and libs, and dotagents import there. Its site-packages is never put
817
+ on `PYTHONPATH`, which every other Python in the session would read first.
742
818
  - **`env` output is sensitive**: it prints resolved values. Nothing in the package
743
819
  logs a value, only names.
744
820
  - **Credentials in a repo URL** are removed from every log line, cache dir name and
@@ -1,3 +1,3 @@
1
1
  """dotagents: installable CLI for the dotagents agent-config payload."""
2
2
 
3
- __version__ = "0.7.6"
3
+ __version__ = "0.8.0"
@@ -463,9 +463,23 @@ def assemble_context(
463
463
  return ""
464
464
  text += _get_skills_listing(scope)
465
465
  text += _get_local_additions_listing(scope)
466
+ if expand_vars: # a machine path: never into a file that may be committed
467
+ text += "\n\nPython for skills (`$AGENTS_PYTHON`): `%s`\n" % skills_python(scope)
466
468
  return text
467
469
 
468
470
 
471
+ def skills_python(scope: _scope.Scope) -> str:
472
+ """The interpreter skills run with: the scope venv's (`dotagents._pyvenv`),
473
+ which imports duho, every overlay's requirements and every overlay lib;
474
+ else the one running dotagents."""
475
+ import sys
476
+
477
+ from dotagents import _pyvenv
478
+
479
+ venv = _pyvenv.active_venv(scope)
480
+ return str(_pyvenv.venv_python(venv)) if venv is not None else sys.executable
481
+
482
+
469
483
  def assemble_context_data(
470
484
  agent: _agents.Agent, scope: _scope.Scope, *, inline: bool = False,
471
485
  expand_vars: bool = True,
@@ -480,6 +494,7 @@ def assemble_context_data(
480
494
  "context": <assembled markdown text, minus the skills listing>,
481
495
  "skills": [{"name": ..., "description": ..., "path": <SKILL.md>}, ...],
482
496
  "local_additions": [{"overlay_file": ..., "local": [<path>, ...]}, ...],
497
+ "python": <the interpreter skills run with, see skills_python>,
483
498
  }
484
499
  ``context`` is the same assembled(+inlined, with ``inline=True``) text the
485
500
  markdown format emits, but WITHOUT the skills and local-additions listings
@@ -501,4 +516,5 @@ def assemble_context_data(
501
516
  {"overlay_file": str(path), "local": [str(p) for p in found]}
502
517
  for path, found in local_additions(scope)
503
518
  ],
519
+ "python": skills_python(scope),
504
520
  }
@@ -1054,7 +1054,24 @@ def get_environment(
1054
1054
  # a venv's, or absent, while this one demonstrably runs the CLI.
1055
1055
  # `sys.executable` is '' or None under an embedding host: then nothing is
1056
1056
  # exported rather than an empty command name.
1057
- _seed("AGENTS_PYTHON", sys.executable)
1057
+ # With a scope venv (`_pyvenv`) it is that venv's interpreter instead: it
1058
+ # sees every overlay lib and requirement through its .pth layering. It
1059
+ # replaces an inherited value this code would have set (the bare
1060
+ # interpreter, or another scope's venv); any other pin holds.
1061
+ from dotagents import _pyvenv
1062
+
1063
+ venv = _pyvenv.active_venv(scope, osenv)
1064
+ python = str(_pyvenv.venv_python(venv)) if venv is not None else sys.executable
1065
+ pinned = osenv.get("AGENTS_PYTHON")
1066
+ if python and pinned != python and (
1067
+ not pinned or pinned == sys.executable or _pyvenv.is_pyvenv_python(pinned)
1068
+ ):
1069
+ _apply({"AGENTS_PYTHON": python})
1070
+ if venv is not None:
1071
+ if osenv.get("AGENTS_PYVENV") != str(venv):
1072
+ _apply({"AGENTS_PYVENV": str(venv)})
1073
+ elif "AGENTS_PYVENV" in osenv:
1074
+ _apply(EnvChanges(removed=["AGENTS_PYVENV"]))
1058
1075
  # --- AGENTS_PYLIB --- where dotagents' own libraries (duho, pathapi)
1059
1076
  # import from for an overlay that builds on them. Set only when dotagents
1060
1077
  # runs from a .pyz: the archive, importable by zipimport. An installed
@@ -1117,8 +1134,15 @@ def get_environment(
1117
1134
  e for e in inherited.split(os.pathsep)
1118
1135
  if e and os.path.normcase(e) not in stale
1119
1136
  ]
1137
+ # A lib the scope venv's .pth already layers stays off PYTHONPATH: the
1138
+ # venv's interpreter (AGENTS_PYTHON) imports it anyway, and no other
1139
+ # Python in the session should. AGENTS_PYTHONPATH still names them all.
1140
+ layered = {
1141
+ os.path.normcase(e) for e in (_pyvenv.read_state(venv).get("pth") or [] if venv else [])
1142
+ }
1143
+ on_path = [p for p in lib_paths if os.path.normcase(p) not in layered]
1120
1144
  pythonpath = _prepended_path_var({"PYTHONPATH": os.pathsep.join(kept)}, "PYTHONPATH",
1121
- list(reversed(lib_paths)))
1145
+ list(reversed(on_path)))
1122
1146
  if pythonpath is None:
1123
1147
  pythonpath = os.pathsep.join(kept)
1124
1148
  if pythonpath != inherited:
@@ -1187,6 +1211,12 @@ def get_diff(
1187
1211
  #: store, the clock), so its output is re-derived at least this often.
1188
1212
  ENV_CACHE_TTL = 300.0
1189
1213
 
1214
+ #: How far in the future a cache file's mtime may be and still count as fresh.
1215
+ #: On Windows before Python 3.13 ``time.time()`` reads a clock that ticks every
1216
+ #: ~15.6 ms while NTFS stamps files from a precise one, so a file read back at
1217
+ #: once can be dated slightly after "now". Beyond this, the clock moved back.
1218
+ _ENV_CACHE_CLOCK_SLACK = 2.0
1219
+
1190
1220
  #: Every env-file name the two tiers resolve (see :func:`resolve_env_files`).
1191
1221
  _ENV_FILE_NAMES = (
1192
1222
  {"default": "pre.env.py", "project-root": ""},
@@ -1244,6 +1274,13 @@ def env_cache_key(scope: Scope, base_env: "dict[str, str]", *parts: str) -> str:
1244
1274
  items.append("lib %s %s" % (path, _stat_sig(path)))
1245
1275
  if path.is_dir():
1246
1276
  items += _tree_sig(path)
1277
+ # A venv sync (`_pyvenv`) changes AGENTS_PYTHON and what stays off PYTHONPATH.
1278
+ from dotagents import _pyvenv
1279
+
1280
+ items += [
1281
+ "venv %s %s" % (store, _stat_sig(_pyvenv.venv_dir(store) / _pyvenv.STATE_FILE))
1282
+ for store in scope.stores
1283
+ ]
1247
1284
  items += sorted("%s=%s" % kv for kv in base_env.items())
1248
1285
  h = hashlib.sha256()
1249
1286
  for item in items:
@@ -1263,7 +1300,7 @@ def read_env_cache(scope: Scope, key: str, *, now: "Optional[float]" = None) ->
1263
1300
  path = _env_cache_dir(scope) / key
1264
1301
  try:
1265
1302
  age = (time.time() if now is None else now) - os.stat(str(path)).st_mtime
1266
- if age < 0 or age >= ENV_CACHE_TTL:
1303
+ if age < -_ENV_CACHE_CLOCK_SLACK or age >= ENV_CACHE_TTL:
1267
1304
  return None
1268
1305
  with open(str(path), encoding="utf-8", newline="") as fh:
1269
1306
  return fh.read()
@@ -30,6 +30,13 @@ Startup: annotate that you read `{{AGENTS_MD}}`.
30
30
  `kb/` file routed from "Load on demand"), or decide no change is needed — and
31
31
  closes it with `dotagents findings done -g <name> -r "<resolution>"` (moved,
32
32
  never deleted; the resolution is the record).
33
+ - **Python for skills**: run every skill script, overlay `bin/` helper and anything
34
+ importing an overlay's `lib/` with `$AGENTS_PYTHON` (PowerShell
35
+ `& $env:AGENTS_PYTHON`), never a bare `python`, `python3` or `py`. It is the scope's
36
+ venv: duho, every overlay's requirements and every overlay `lib/` (project, then
37
+ user, then system) import there and nowhere else. When the variable is unset, use
38
+ the interpreter `dotagents context` prints. `.agents/.pyvenv/` holds only these
39
+ agents' venvs: never create, install into or test a project in one.
33
40
  - **This file**: everything between the `dotagents:begin`/`dotagents:end` markers is
34
41
  managed — `dotagents init` refreshes it and leaves anything outside untouched. Add
35
42
  your own rules and routing below the end marker, not inside.