agentic-dev-loop 1.0.0a27__tar.gz → 1.0.0a28__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.

Potentially problematic release.


This version of agentic-dev-loop might be problematic. Click here for more details.

Files changed (89) hide show
  1. {agentic_dev_loop-1.0.0a27/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a28}/PKG-INFO +5 -4
  2. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/README.md +4 -3
  3. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/pyproject.toml +1 -1
  4. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/core/config.py +2 -2
  5. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/isolation.py +13 -5
  6. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/stages.py +1 -1
  7. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28/src/agentic_dev_loop.egg-info}/PKG-INFO +5 -4
  8. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_config.py +12 -8
  9. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_isolation.py +40 -0
  10. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/LICENSE +0 -0
  11. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/setup.cfg +0 -0
  12. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/__init__.py +0 -0
  13. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/core/__init__.py +0 -0
  14. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/core/paths.py +0 -0
  15. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/core/results.py +0 -0
  16. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/core/verdict.py +0 -0
  17. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/__init__.py +0 -0
  18. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/actions.py +0 -0
  19. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/agents.py +0 -0
  20. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/cli.py +0 -0
  21. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/deps.py +0 -0
  22. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/dryrun.py +0 -0
  23. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
  24. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
  25. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
  26. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
  27. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/graph.py +0 -0
  28. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
  29. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/llm.py +0 -0
  30. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/loaders.py +0 -0
  31. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/mounts.py +0 -0
  32. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/outputs.py +0 -0
  33. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/presets.py +0 -0
  34. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/proc.py +0 -0
  35. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/registry.py +0 -0
  36. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
  37. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
  38. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/secrets.py +0 -0
  39. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
  40. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/engine/sync_back.py +0 -0
  41. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/py.typed +0 -0
  42. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/viz/__init__.py +0 -0
  43. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/viz/__main__.py +0 -0
  44. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop/viz/server.py +0 -0
  45. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
  46. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
  47. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
  48. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
  49. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
  50. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_028_declared_caches.py +0 -0
  51. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_029_merge_on_conflict.py +0 -0
  52. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_actions.py +0 -0
  53. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_agents.py +0 -0
  54. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_aidlc.py +0 -0
  55. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_cli.py +0 -0
  56. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_deps.py +0 -0
  57. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_dry_run.py +0 -0
  58. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_e2e_fixtures.py +0 -0
  59. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_egress_proxy.py +0 -0
  60. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_graph.py +0 -0
  61. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_hostboundary.py +0 -0
  62. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_hostboundary_integration.py +0 -0
  63. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_isolation_integration.py +0 -0
  64. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_isolation_pbt.py +0 -0
  65. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_llm.py +0 -0
  66. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_loaders.py +0 -0
  67. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_module_contract.py +0 -0
  68. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_outputs.py +0 -0
  69. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_outputs_pbt.py +0 -0
  70. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_proc.py +0 -0
  71. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_publish.py +0 -0
  72. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_qa_prompt.py +0 -0
  73. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_registry.py +0 -0
  74. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_repo_ascii.py +0 -0
  75. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_results.py +0 -0
  76. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_sdk_compat.py +0 -0
  77. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_secrets.py +0 -0
  78. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_secrets_integration.py +0 -0
  79. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_secrets_pipeline.py +0 -0
  80. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_source_resolution.py +0 -0
  81. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_stages.py +0 -0
  82. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_step_workspace.py +0 -0
  83. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_sync_back.py +0 -0
  84. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_trigger_script.py +0 -0
  85. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_unit5_config.py +0 -0
  86. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_unit5_pbt.py +0 -0
  87. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_unit5_pure.py +0 -0
  88. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_verdict.py +0 -0
  89. {agentic_dev_loop-1.0.0a27 → agentic_dev_loop-1.0.0a28}/tests/test_viz.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-dev-loop
3
- Version: 1.0.0a27
3
+ Version: 1.0.0a28
4
4
  Summary: Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase.
5
5
  Author: Luke Wilson
6
6
  License-Expression: Apache-2.0
@@ -228,7 +228,7 @@ steps:
228
228
  | `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
229
229
  | `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
230
230
  | `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
231
- | `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` provisions the sandbox (command steps, network ON, stamp-cached); `network: true` lets a command/agent step reach the hosts its `egress:` allowlist names (default off) - see [Per-stage networking](#per-stage-networking). |
231
+ | `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` provisions the sandbox before the step runs (command or agent steps; network ON; stamp-cached, re-runs when the pipeline's lockfiles change); `network: true` lets a command/agent step reach the hosts its `egress:` allowlist names (default off) - see [Per-stage networking](#per-stage-networking). |
232
232
  | `mounts:` / `egress:` | Per-step workspace isolation. `mounts:` lists `{path, mode}` entries (`rw`/`ro`, default `ro`) and is **strict-by-default** - empty/missing mounts nothing; only declared-rw paths sync back to the repo. `egress:` is `{class, presets, targets}` (`class: browser` = broad-but-logged; otherwise a strict allowlist). See [Per-stage networking](#per-stage-networking) and [docs/setup.md](docs/setup.md). |
233
233
  | `secrets:` (top-level) / step `secrets:` | Declares secret **refs only - never values**; each step opts in to the refs it needs. Resolved host-side and injected into that step's exec env only. See [Secrets](#secrets--refs-only-never-values). |
234
234
  | `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`, `seccomp_profile`. It can never disable it. See [browser sandbox](docs/browser-sandbox.md). |
@@ -236,7 +236,7 @@ steps:
236
236
  | `extensions_dir` | Where consumer `extensions:` modules resolve - defaults to `~/.agentic-dev-loop/extensions`, **outside** the workspace. Workspace-resident extension/tool modules are refused at load. |
237
237
  | `deps_policy` | `locked` (default) or `open`. `locked` = the human-owned lockfile is the source of truth and the agent may not add dependencies; `open` = the bootstrap protocol (the agent may add exact-pinned manifest entries). See [Dependency policy](#dependency-policy-locked-by-default). |
238
238
  | `lockfiles` | The pipeline's declared lockfile names (workspace-relative), injected into agent context and named by the missing-dependency fail-fast. Required (non-empty) when `deps_policy: locked` - a `locked` pipeline with no lockfiles fails fast at load. |
239
- | `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from the real workspace (auto-created if missing); `sync_excludes:` names scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
239
+ | `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from the real workspace (auto-created if missing); a `setup:` hook can warm them (the provision container carries the same cache mounts); `sync_excludes:` names scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
240
240
  | `merge_on_conflict:` | Top-level run-wide list of append-style logs (same path vocabulary as `sync_excludes:`, default `[]`). A concurrent host edit to a listed path is union-merged instead of raising: the host file wins and the sandbox's export-only lines are appended in order; both sides converge and the run continues. See [docs/setup.md](docs/setup.md). |
241
241
 
242
242
  ### Secrets (`secrets:`) - refs only, never values
@@ -432,7 +432,8 @@ Adaptation checklist for a new repo:
432
432
  only if a stage runs Chromium.
433
433
  3. `caches:` - toolchain cache dirs as rw bind-mounts shared across runs
434
434
  (`.nuget-packages`, `qa/node_modules`, `qa/.browsers`,
435
- `frontend-editor/node_modules`); caches are never synced back.
435
+ `frontend-editor/node_modules`); caches are never synced back, and a
436
+ step's `setup:` hook runs with them mounted so it can warm them.
436
437
  4. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
437
438
  5. `secrets:` - refs only, resolved from host env; each step opts in to the
438
439
  refs it needs.
@@ -186,7 +186,7 @@ steps:
186
186
  | `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
187
187
  | `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
188
188
  | `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
189
- | `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` provisions the sandbox (command steps, network ON, stamp-cached); `network: true` lets a command/agent step reach the hosts its `egress:` allowlist names (default off) - see [Per-stage networking](#per-stage-networking). |
189
+ | `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` provisions the sandbox before the step runs (command or agent steps; network ON; stamp-cached, re-runs when the pipeline's lockfiles change); `network: true` lets a command/agent step reach the hosts its `egress:` allowlist names (default off) - see [Per-stage networking](#per-stage-networking). |
190
190
  | `mounts:` / `egress:` | Per-step workspace isolation. `mounts:` lists `{path, mode}` entries (`rw`/`ro`, default `ro`) and is **strict-by-default** - empty/missing mounts nothing; only declared-rw paths sync back to the repo. `egress:` is `{class, presets, targets}` (`class: browser` = broad-but-logged; otherwise a strict allowlist). See [Per-stage networking](#per-stage-networking) and [docs/setup.md](docs/setup.md). |
191
191
  | `secrets:` (top-level) / step `secrets:` | Declares secret **refs only - never values**; each step opts in to the refs it needs. Resolved host-side and injected into that step's exec env only. See [Secrets](#secrets--refs-only-never-values). |
192
192
  | `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`, `seccomp_profile`. It can never disable it. See [browser sandbox](docs/browser-sandbox.md). |
@@ -194,7 +194,7 @@ steps:
194
194
  | `extensions_dir` | Where consumer `extensions:` modules resolve - defaults to `~/.agentic-dev-loop/extensions`, **outside** the workspace. Workspace-resident extension/tool modules are refused at load. |
195
195
  | `deps_policy` | `locked` (default) or `open`. `locked` = the human-owned lockfile is the source of truth and the agent may not add dependencies; `open` = the bootstrap protocol (the agent may add exact-pinned manifest entries). See [Dependency policy](#dependency-policy-locked-by-default). |
196
196
  | `lockfiles` | The pipeline's declared lockfile names (workspace-relative), injected into agent context and named by the missing-dependency fail-fast. Required (non-empty) when `deps_policy: locked` - a `locked` pipeline with no lockfiles fails fast at load. |
197
- | `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from the real workspace (auto-created if missing); `sync_excludes:` names scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
197
+ | `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from the real workspace (auto-created if missing); a `setup:` hook can warm them (the provision container carries the same cache mounts); `sync_excludes:` names scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
198
198
  | `merge_on_conflict:` | Top-level run-wide list of append-style logs (same path vocabulary as `sync_excludes:`, default `[]`). A concurrent host edit to a listed path is union-merged instead of raising: the host file wins and the sandbox's export-only lines are appended in order; both sides converge and the run continues. See [docs/setup.md](docs/setup.md). |
199
199
 
200
200
  ### Secrets (`secrets:`) - refs only, never values
@@ -390,7 +390,8 @@ Adaptation checklist for a new repo:
390
390
  only if a stage runs Chromium.
391
391
  3. `caches:` - toolchain cache dirs as rw bind-mounts shared across runs
392
392
  (`.nuget-packages`, `qa/node_modules`, `qa/.browsers`,
393
- `frontend-editor/node_modules`); caches are never synced back.
393
+ `frontend-editor/node_modules`); caches are never synced back, and a
394
+ step's `setup:` hook runs with them mounted so it can warm them.
394
395
  4. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
395
396
  5. `secrets:` - refs only, resolved from host env; each step opts in to the
396
397
  refs it needs.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentic-dev-loop"
7
- version = "1.0.0a27"
7
+ version = "1.0.0a28"
8
8
  description = "Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -386,9 +386,9 @@ class StepConfig(BaseModel):
386
386
  raise ValueError(
387
387
  f"step {self.name!r}: 'timeout' requires 'command'"
388
388
  )
389
- if self.setup is not None and self.command is None:
389
+ if self.setup is not None and self.command is None and self.agent is None:
390
390
  raise ValueError(
391
- f"step {self.name!r}: 'setup' is only allowed on 'command' steps"
391
+ f"step {self.name!r}: 'setup' is only allowed on 'command' or 'agent' steps"
392
392
  )
393
393
  # `network` is NOT rejected here: the schema can't tell a host-side
394
394
  # built-in action (e.g. `commit`, which must never take egress) from
@@ -699,11 +699,14 @@ def docker_create_payload(*, run_id: str, image: str, profile: EffectiveProfile,
699
699
 
700
700
  def docker_provision_payload(*, run_id: str, image: str, workspace: str,
701
701
  setup_cmd: str,
702
- sandbox_venv_dir: str | None = None) -> list[str]:
702
+ sandbox_venv_dir: str | None = None,
703
+ cache_mounts: list[tuple[str, str, str]]
704
+ | None = None) -> list[str]:
703
705
  """Exact ``docker run --rm`` argv for the provision container (§3.4).
704
706
  Network ON (default bridge), rw rootfs, --cap-drop ALL, mount set
705
- {workspace} + host-only masks (022); --user host uid:gid (AE-SBX-009) with
706
- HOME=/tmp."""
707
+ {workspace} + host-only masks (022) + the declared cache paths (028, rw
708
+ bind mounts so a ``setup:`` hook can warm them); --user host uid:gid
709
+ (AE-SBX-009) with HOME=/tmp."""
707
710
  payload = [
708
711
  "docker", "run", "--rm", "--name", f"{run_id}-provision",
709
712
  "--network", "bridge", "--cap-drop", "ALL",
@@ -713,6 +716,8 @@ def docker_provision_payload(*, run_id: str, image: str, workspace: str,
713
716
  if user is not None:
714
717
  payload.extend(["--user", user, "--env", "HOME=/tmp"])
715
718
  payload.extend(_sandbox_mounts(workspace, sandbox_venv_dir))
719
+ for source, target, _mode in (cache_mounts or []):
720
+ payload += ["-v", f"{source}:{target}"]
716
721
  payload.extend([
717
722
  "--workdir", workspace,
718
723
  image, "sh", "-c", setup_cmd,
@@ -995,7 +1000,8 @@ class DockerBackend(Sandbox):
995
1000
  return
996
1001
  payload = docker_provision_payload(
997
1002
  run_id=self._run_id, image=self._image, workspace=self._workspace,
998
- setup_cmd=setup_cmd, sandbox_venv_dir=self._sandbox_venv_dir)
1003
+ setup_cmd=setup_cmd, sandbox_venv_dir=self._sandbox_venv_dir,
1004
+ cache_mounts=self._cache_mounts)
999
1005
  res = self._runner.run(payload, timeout=PROVISION_TIMEOUT_SECONDS)
1000
1006
  if res.exit_code != 0:
1001
1007
  tail = (res.stdout or res.stderr or "")[-4000:]
@@ -1541,7 +1547,9 @@ def _file_digest(p: Path) -> str:
1541
1547
  def _declared_setups(cfg: PipelineConfig, workspace: Path):
1542
1548
  hashes = _lockfile_hashes(workspace)
1543
1549
  for step in cfg.steps:
1544
- if step.command is not None and step.setup is not None:
1550
+ if step.setup is not None and (
1551
+ step.command is not None or step.agent is not None
1552
+ ):
1545
1553
  yield step.setup, setup_stamp_key(step.setup, hashes)
1546
1554
 
1547
1555
 
@@ -409,7 +409,7 @@ def make_agent_node(runtime: Runtime, step: StepConfig):
409
409
  update["artifacts"] = {step.name: result.response}
410
410
  return update
411
411
  if verdict == "unverifiable":
412
- print(f"[{subject}] {step.name} qa verdict: UNVERIFIABLE "
412
+ print(f"[{subject}] {step.name} verdict: UNVERIFIABLE "
413
413
  f"(moves on).", flush=True)
414
414
  update = {"step_verdicts": merged, "failed": False}
415
415
  if captures and result.response is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-dev-loop
3
- Version: 1.0.0a27
3
+ Version: 1.0.0a28
4
4
  Summary: Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase.
5
5
  Author: Luke Wilson
6
6
  License-Expression: Apache-2.0
@@ -228,7 +228,7 @@ steps:
228
228
  | `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
229
229
  | `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
230
230
  | `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
231
- | `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` provisions the sandbox (command steps, network ON, stamp-cached); `network: true` lets a command/agent step reach the hosts its `egress:` allowlist names (default off) - see [Per-stage networking](#per-stage-networking). |
231
+ | `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` provisions the sandbox before the step runs (command or agent steps; network ON; stamp-cached, re-runs when the pipeline's lockfiles change); `network: true` lets a command/agent step reach the hosts its `egress:` allowlist names (default off) - see [Per-stage networking](#per-stage-networking). |
232
232
  | `mounts:` / `egress:` | Per-step workspace isolation. `mounts:` lists `{path, mode}` entries (`rw`/`ro`, default `ro`) and is **strict-by-default** - empty/missing mounts nothing; only declared-rw paths sync back to the repo. `egress:` is `{class, presets, targets}` (`class: browser` = broad-but-logged; otherwise a strict allowlist). See [Per-stage networking](#per-stage-networking) and [docs/setup.md](docs/setup.md). |
233
233
  | `secrets:` (top-level) / step `secrets:` | Declares secret **refs only - never values**; each step opts in to the refs it needs. Resolved host-side and injected into that step's exec env only. See [Secrets](#secrets--refs-only-never-values). |
234
234
  | `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`, `seccomp_profile`. It can never disable it. See [browser sandbox](docs/browser-sandbox.md). |
@@ -236,7 +236,7 @@ steps:
236
236
  | `extensions_dir` | Where consumer `extensions:` modules resolve - defaults to `~/.agentic-dev-loop/extensions`, **outside** the workspace. Workspace-resident extension/tool modules are refused at load. |
237
237
  | `deps_policy` | `locked` (default) or `open`. `locked` = the human-owned lockfile is the source of truth and the agent may not add dependencies; `open` = the bootstrap protocol (the agent may add exact-pinned manifest entries). See [Dependency policy](#dependency-policy-locked-by-default). |
238
238
  | `lockfiles` | The pipeline's declared lockfile names (workspace-relative), injected into agent context and named by the missing-dependency fail-fast. Required (non-empty) when `deps_policy: locked` - a `locked` pipeline with no lockfiles fails fast at load. |
239
- | `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from the real workspace (auto-created if missing); `sync_excludes:` names scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
239
+ | `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from the real workspace (auto-created if missing); a `setup:` hook can warm them (the provision container carries the same cache mounts); `sync_excludes:` names scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
240
240
  | `merge_on_conflict:` | Top-level run-wide list of append-style logs (same path vocabulary as `sync_excludes:`, default `[]`). A concurrent host edit to a listed path is union-merged instead of raising: the host file wins and the sandbox's export-only lines are appended in order; both sides converge and the run continues. See [docs/setup.md](docs/setup.md). |
241
241
 
242
242
  ### Secrets (`secrets:`) - refs only, never values
@@ -432,7 +432,8 @@ Adaptation checklist for a new repo:
432
432
  only if a stage runs Chromium.
433
433
  3. `caches:` - toolchain cache dirs as rw bind-mounts shared across runs
434
434
  (`.nuget-packages`, `qa/node_modules`, `qa/.browsers`,
435
- `frontend-editor/node_modules`); caches are never synced back.
435
+ `frontend-editor/node_modules`); caches are never synced back, and a
436
+ step's `setup:` hook runs with them mounted so it can warm them.
436
437
  4. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
437
438
  5. `secrets:` - refs only, resolved from host env; each step opts in to the
438
439
  refs it needs.
@@ -489,15 +489,19 @@ class TestStepEnvAndSetup:
489
489
  "on_pass": "success"}]
490
490
  assert _load(tmp_path, data).steps[0].setup is not None
491
491
 
492
- @pytest.mark.parametrize("step", [
493
- {"name": "x", "agent": "dev", "setup": "npm i"},
494
- {"name": "x", "action": "commit", "setup": "npm i"},
495
- ])
496
- def test_setup_rejected_on_non_command_steps(self, tmp_path, step):
492
+ def test_setup_allowed_on_agent_step(self, tmp_path):
497
493
  data = _base()
498
- data["steps"] = [step | {"on_pass": "success"}]
499
- with pytest.raises(ValidationError,
500
- match="'setup' is only allowed on 'command' steps"):
494
+ data["steps"] = [{"name": "dev", "agent": "dev", "setup": "npm i",
495
+ "on_pass": "success"}]
496
+ assert _load(tmp_path, data).steps[0].setup == "npm i"
497
+
498
+ def test_setup_rejected_on_action_step(self, tmp_path):
499
+ data = _base()
500
+ data["steps"] = [{"name": "x", "action": "commit", "setup": "npm i",
501
+ "on_pass": "success"}]
502
+ with pytest.raises(
503
+ ValidationError,
504
+ match="'setup' is only allowed on 'command' or 'agent' steps"):
501
505
  _load(tmp_path, data)
502
506
 
503
507
  def test_env_round_trip(self, tmp_path):
@@ -2109,3 +2109,43 @@ class TestReaper:
2109
2109
  self._write(tmp_path / "state", [own, other])
2110
2110
  backend.close()
2111
2111
  assert self._read(tmp_path / "state") == [other]
2112
+
2113
+
2114
+ def test_declared_setups_includes_agent_steps(tmp_path):
2115
+ import yaml
2116
+
2117
+ from agentic_dev_loop.core.config import load_config
2118
+ from agentic_dev_loop.engine.isolation import _declared_setups
2119
+
2120
+ (tmp_path / "uv.lock").write_text("")
2121
+ data = {
2122
+ "name": "t",
2123
+ "llm": {"model": "m", "api_key_env": "K"},
2124
+ "agents_dir": "agents",
2125
+ "lockfiles": ["uv.lock"],
2126
+ "steps": [
2127
+ {"name": "dev", "agent": "dev",
2128
+ "setup": "cd frontend-editor && npm ci", "on_pass": "success"},
2129
+ {"name": "t", "command": "make", "setup": "echo warm",
2130
+ "on_pass": "success"},
2131
+ ],
2132
+ }
2133
+ p = tmp_path / "pipeline.yaml"
2134
+ p.write_text(yaml.safe_dump(data))
2135
+ cfg = load_config(p)
2136
+ assert set(dict(_declared_setups(cfg, tmp_path))) == {
2137
+ "cd frontend-editor && npm ci", "echo warm"}
2138
+
2139
+
2140
+ def test_provision_payload_mounts_declared_caches():
2141
+ payload = docker_provision_payload(
2142
+ run_id="run1",
2143
+ image="img",
2144
+ workspace="/ws",
2145
+ setup_cmd="cd frontend-editor && npm ci",
2146
+ cache_mounts=[
2147
+ ("/host/fe/node_modules", "/ws/frontend-editor/node_modules", "rw"),
2148
+ ],
2149
+ )
2150
+ assert "-v" in payload
2151
+ assert "/host/fe/node_modules:/ws/frontend-editor/node_modules" in payload