agentic-dev-loop 0.3.0__tar.gz → 0.9.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 (123) hide show
  1. agentic_dev_loop-0.9.0/PKG-INFO +731 -0
  2. agentic_dev_loop-0.9.0/README.md +687 -0
  3. agentic_dev_loop-0.9.0/pyproject.toml +85 -0
  4. agentic_dev_loop-0.9.0/src/agentic_dev_loop/__init__.py +1 -0
  5. agentic_dev_loop-0.9.0/src/agentic_dev_loop/core/__init__.py +18 -0
  6. agentic_dev_loop-0.9.0/src/agentic_dev_loop/core/config.py +908 -0
  7. agentic_dev_loop-0.9.0/src/agentic_dev_loop/core/paths.py +91 -0
  8. {agentic_dev_loop-0.3.0/src/agentic_dev_loop → agentic_dev_loop-0.9.0/src/agentic_dev_loop/core}/results.py +1 -1
  9. agentic_dev_loop-0.3.0/src/agentic_dev_loop/outputs.py → agentic_dev_loop-0.9.0/src/agentic_dev_loop/core/verdict.py +20 -53
  10. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/__init__.py +2 -0
  11. {agentic_dev_loop-0.3.0/src/agentic_dev_loop → agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine}/actions.py +77 -13
  12. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/agent_error_guide.md +7 -0
  13. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/agent_error_guide.py +26 -0
  14. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/agents.py +208 -0
  15. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/allowlist.py +66 -0
  16. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/brief_coverage.py +85 -0
  17. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/brief_prompt.md +46 -0
  18. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/brief_prompt.py +47 -0
  19. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/cli.py +215 -0
  20. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/containers.py +81 -0
  21. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/deps.py +77 -0
  22. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/dryrun.py +161 -0
  23. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/egress_policy.py +130 -0
  24. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/egress_proxy.py +359 -0
  25. {agentic_dev_loop-0.3.0/src/agentic_dev_loop → agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine}/extensions/aidlc.py +41 -13
  26. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/forbidden.py +91 -0
  27. {agentic_dev_loop-0.3.0/src/agentic_dev_loop → agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine}/graph.py +135 -57
  28. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/hostboundary.py +137 -0
  29. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/isolation.py +2117 -0
  30. {agentic_dev_loop-0.3.0/src/agentic_dev_loop → agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine}/llm.py +16 -9
  31. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/loaders.py +141 -0
  32. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/mounts.py +63 -0
  33. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/outputs.py +63 -0
  34. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/presets.py +50 -0
  35. {agentic_dev_loop-0.3.0/src/agentic_dev_loop → agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine}/proc.py +20 -2
  36. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/registry.py +306 -0
  37. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/seccomp/__init__.py +72 -0
  38. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +190 -0
  39. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/secrets.py +118 -0
  40. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/stages.py +1148 -0
  41. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/step_workspace.py +176 -0
  42. agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine/sync_back.py +306 -0
  43. agentic_dev_loop-0.9.0/src/agentic_dev_loop/viz/__init__.py +13 -0
  44. agentic_dev_loop-0.9.0/src/agentic_dev_loop/viz/__main__.py +42 -0
  45. agentic_dev_loop-0.3.0/src/agentic_dev_loop/viz.py → agentic_dev_loop-0.9.0/src/agentic_dev_loop/viz/server.py +133 -45
  46. agentic_dev_loop-0.9.0/src/agentic_dev_loop.egg-info/PKG-INFO +731 -0
  47. agentic_dev_loop-0.9.0/src/agentic_dev_loop.egg-info/SOURCES.txt +102 -0
  48. agentic_dev_loop-0.9.0/src/agentic_dev_loop.egg-info/entry_points.txt +2 -0
  49. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/src/agentic_dev_loop.egg-info/requires.txt +8 -2
  50. agentic_dev_loop-0.9.0/tests/test_028_declared_caches.py +259 -0
  51. agentic_dev_loop-0.9.0/tests/test_029_merge_on_conflict.py +97 -0
  52. agentic_dev_loop-0.9.0/tests/test_033_brief_step.py +606 -0
  53. agentic_dev_loop-0.9.0/tests/test_035_finalize_nudge.py +218 -0
  54. agentic_dev_loop-0.9.0/tests/test_036_banned_commands_and_lane_boundary.py +273 -0
  55. agentic_dev_loop-0.9.0/tests/test_041_brief_coverage.py +293 -0
  56. agentic_dev_loop-0.9.0/tests/test_046_brief_prompt.py +45 -0
  57. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_actions.py +167 -19
  58. agentic_dev_loop-0.9.0/tests/test_agent_error_guide.py +187 -0
  59. agentic_dev_loop-0.9.0/tests/test_agents.py +260 -0
  60. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_aidlc.py +29 -20
  61. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_cli.py +216 -11
  62. agentic_dev_loop-0.9.0/tests/test_config.py +841 -0
  63. agentic_dev_loop-0.9.0/tests/test_containers.py +138 -0
  64. agentic_dev_loop-0.9.0/tests/test_deps.py +76 -0
  65. agentic_dev_loop-0.9.0/tests/test_dry_run.py +199 -0
  66. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_e2e_fixtures.py +145 -29
  67. agentic_dev_loop-0.9.0/tests/test_egress_proxy.py +428 -0
  68. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_graph.py +169 -39
  69. agentic_dev_loop-0.9.0/tests/test_hostboundary.py +137 -0
  70. agentic_dev_loop-0.9.0/tests/test_hostboundary_integration.py +113 -0
  71. agentic_dev_loop-0.9.0/tests/test_isolation.py +2700 -0
  72. agentic_dev_loop-0.9.0/tests/test_isolation_integration.py +489 -0
  73. agentic_dev_loop-0.9.0/tests/test_isolation_pbt.py +163 -0
  74. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_llm.py +49 -9
  75. agentic_dev_loop-0.9.0/tests/test_loaders.py +227 -0
  76. agentic_dev_loop-0.9.0/tests/test_module_contract.py +94 -0
  77. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_outputs.py +7 -7
  78. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_outputs_pbt.py +6 -3
  79. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_proc.py +39 -2
  80. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_publish.py +75 -1
  81. agentic_dev_loop-0.9.0/tests/test_qa_prompt.py +53 -0
  82. agentic_dev_loop-0.9.0/tests/test_registry.py +264 -0
  83. agentic_dev_loop-0.9.0/tests/test_repo_ascii.py +60 -0
  84. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_results.py +1 -1
  85. agentic_dev_loop-0.9.0/tests/test_sdk_compat.py +135 -0
  86. agentic_dev_loop-0.9.0/tests/test_secrets.py +222 -0
  87. agentic_dev_loop-0.9.0/tests/test_secrets_integration.py +67 -0
  88. agentic_dev_loop-0.9.0/tests/test_secrets_pipeline.py +108 -0
  89. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_source_resolution.py +3 -5
  90. agentic_dev_loop-0.9.0/tests/test_stages.py +930 -0
  91. agentic_dev_loop-0.9.0/tests/test_step_workspace.py +159 -0
  92. agentic_dev_loop-0.9.0/tests/test_sync_back.py +396 -0
  93. agentic_dev_loop-0.9.0/tests/test_trigger_script.py +219 -0
  94. agentic_dev_loop-0.9.0/tests/test_unit5_config.py +173 -0
  95. agentic_dev_loop-0.9.0/tests/test_unit5_pbt.py +93 -0
  96. agentic_dev_loop-0.9.0/tests/test_unit5_pure.py +196 -0
  97. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_verdict.py +56 -13
  98. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/tests/test_viz.py +195 -13
  99. agentic_dev_loop-0.3.0/PKG-INFO +0 -318
  100. agentic_dev_loop-0.3.0/README.md +0 -280
  101. agentic_dev_loop-0.3.0/pyproject.toml +0 -59
  102. agentic_dev_loop-0.3.0/src/agentic_dev_loop/__init__.py +0 -1
  103. agentic_dev_loop-0.3.0/src/agentic_dev_loop/agents.py +0 -49
  104. agentic_dev_loop-0.3.0/src/agentic_dev_loop/cli.py +0 -82
  105. agentic_dev_loop-0.3.0/src/agentic_dev_loop/config.py +0 -156
  106. agentic_dev_loop-0.3.0/src/agentic_dev_loop/loaders.py +0 -60
  107. agentic_dev_loop-0.3.0/src/agentic_dev_loop/registry.py +0 -133
  108. agentic_dev_loop-0.3.0/src/agentic_dev_loop/stages.py +0 -423
  109. agentic_dev_loop-0.3.0/src/agentic_dev_loop.egg-info/PKG-INFO +0 -318
  110. agentic_dev_loop-0.3.0/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -46
  111. agentic_dev_loop-0.3.0/src/agentic_dev_loop.egg-info/entry_points.txt +0 -2
  112. agentic_dev_loop-0.3.0/tests/test_agents.py +0 -88
  113. agentic_dev_loop-0.3.0/tests/test_config.py +0 -374
  114. agentic_dev_loop-0.3.0/tests/test_loaders.py +0 -118
  115. agentic_dev_loop-0.3.0/tests/test_registry.py +0 -114
  116. agentic_dev_loop-0.3.0/tests/test_sdk_compat.py +0 -68
  117. agentic_dev_loop-0.3.0/tests/test_stages.py +0 -466
  118. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/LICENSE +0 -0
  119. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/setup.cfg +0 -0
  120. {agentic_dev_loop-0.3.0/src/agentic_dev_loop → agentic_dev_loop-0.9.0/src/agentic_dev_loop/engine}/extensions/__init__.py +0 -0
  121. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/src/agentic_dev_loop/py.typed +0 -0
  122. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
  123. {agentic_dev_loop-0.3.0 → agentic_dev_loop-0.9.0}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
@@ -0,0 +1,731 @@
1
+ Metadata-Version: 2.4
2
+ Name: agentic-dev-loop
3
+ Version: 0.9.0
4
+ Summary: Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase.
5
+ Author: Luke Wilson
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/lukedawilson/agentic-dev-loop
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: MacOS
12
+ Classifier: Operating System :: Microsoft :: Windows
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Software Development
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Topic :: Security
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.12
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: pydantic>=2.0
29
+ Requires-Dist: PyYAML>=6.0
30
+ Requires-Dist: openhands-sdk~=1.17.0
31
+ Requires-Dist: openhands-tools~=1.17.0
32
+ Requires-Dist: agent-client-protocol<1.0.0
33
+ Requires-Dist: langgraph>=1.2
34
+ Requires-Dist: langgraph-checkpoint-sqlite>=3.0
35
+ Requires-Dist: filelock>=4.2.1
36
+ Requires-Dist: pyasn1>=0.6.5
37
+ Requires-Dist: pypdf>=6.21.4
38
+ Provides-Extra: dev
39
+ Requires-Dist: hypothesis>=6.0; extra == "dev"
40
+ Requires-Dist: pytest>=8.0; extra == "dev"
41
+ Requires-Dist: pip-audit>=2.0; extra == "dev"
42
+ Requires-Dist: pre-commit>=3.0; extra == "dev"
43
+ Dynamic: license-file
44
+
45
+ # agentic-dev-loop
46
+
47
+ > **Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase.**
48
+
49
+ Describe your pipeline in one YAML file - agent steps, mechanical test gates, and
50
+ trusted actions - and `agentic-dev-loop` runs it end to end: agent and command steps get
51
+ kernel-level sandboxing via Docker, verdicts route the graph, every boundary is
52
+ checkpointed, and nothing gets committed until every gate passes.
53
+
54
+ ```mermaid
55
+ flowchart LR
56
+ dev["agent: dev"] --> test{{"test gate"}} --> review["agent: review"] --> qa["agent: qa"] --> commit["action: commit"]
57
+ test -- fail --> dev
58
+ review -- fail --> dev
59
+ qa -- fail --> dev
60
+ ```
61
+
62
+ - **A tool, not a wiring project.** One CLI entry point, opinionated defaults,
63
+ fixed safety policies. You write the pipeline and the agent definitions -
64
+ nothing else.
65
+ - **Deterministic.** Same pipeline + same state → same routing. Verdicts are
66
+ parsed from files, retries are bounded, nothing is best-effort.
67
+ - **Sandboxed by default.** The agent's shell and every command step run inside
68
+ an always-on Docker sandbox. There is no opt-out.
69
+
70
+ ---
71
+
72
+ ## Core features
73
+
74
+ | Feature | What it gives you |
75
+ | --- | --- |
76
+ | **Stage-gated agent loop** | Agents do the work, test stages verify it, and the pipeline only advances when each stage passes its gates - looping back with full context when one fails. |
77
+ | **Kernel-level sandboxing** | Agent work and command stages run isolated from your machine: [no network access **by default** - authors opt specific stages in](#per-stage-networking), writes only to declared `mounts:`, and no access to `.git` (steps run against a fresh export). No sandbox available → the run stops instead of degrading. |
78
+ | **Crash-safe resume** | Interrupt a run at any time and pick it up later - finished stages stay finished, and you resume from exactly where it stopped. |
79
+ | **Live graph visualisation** | Follow every run in the browser: stage status, pass/fail verdicts, retry cycles, and live console output as it happens. |
80
+ | **Extensible** | Plug in your own actions, tools, and document loaders to adapt the loop to your projects. |
81
+
82
+ The fixed policies *are* the product: fail fast on missing config/secrets, no
83
+ silent fallbacks, verdicts only from artifacts, bounded retries, per-attempt
84
+ artifact hygiene, a single clean-worktree-gated commit point, no default LLM
85
+ timeout (agent runs are long), and fail fast on a broken sandbox - daemon-side
86
+ exec failures are classified and stop the run without an agent retry loop,
87
+ while a startup reaper reclaims containers leaked by a crashed run.
88
+
89
+ ## Supported platforms
90
+
91
+ The engine is plain Linux under the hood and runs wherever a compliant
92
+ Docker engine does:
93
+
94
+ | Platform | Support |
95
+ | --- | --- |
96
+ | **Linux** | Native. Docker Engine, OrbStack, Colima, or podman with docker compat. |
97
+ | **macOS** | Native. Docker Desktop, OrbStack, or Colima. |
98
+ | **Windows** | Via **WSL2** (Ubuntu distro). Enable Docker Desktop's WSL integration (or run Docker Engine inside the distro) and keep the repo on the WSL filesystem (`~/dev/...`), not `/mnt/c` - bind-mounting a 9p path into containers breaks permissions and is slow. |
99
+
100
+ Native Windows (outside WSL2) is not supported: every Windows-specific code
101
+ path and test was removed, and CI runs on Ubuntu only.
102
+
103
+ ## Installation
104
+
105
+ **Prerequisites**
106
+
107
+ | Requirement | Detail |
108
+ | --- | --- |
109
+ | Python | ≥ 3.12 |
110
+ | Docker engine | Engine API ≥ 1.43 (Engine 24). Any compliant engine works - Docker Desktop, OrbStack, Colima, Rancher Desktop, podman docker-compat. On Windows, run under WSL2: the repo must live on the WSL filesystem, not /mnt/c. |
111
+ | LLM API key | The env var named by your pipeline's `api_key_env` (e.g. `OPENAI_API_KEY`) must be set - a missing key fails fast, naming it. |
112
+
113
+ ```bash
114
+ pip install "agentic-dev-loop==1.0.0a17"
115
+ agentic-dev-loop --version
116
+ ```
117
+
118
+ The canonical consumer install is **pipx**, which puts the engine's venv
119
+ *outside* the workspace - the sandbox mounts only the step's declared `mounts:`
120
+ from a fresh export, so an out-of-workspace install is unmounted and never
121
+ agent-writable:
122
+
123
+ ```bash
124
+ pipx install "agentic-dev-loop==1.0.0a17"
125
+ ```
126
+
127
+ If you install with `uv` shortly after a release, pass `--refresh` - PyPI
128
+ serves the package index with `Cache-Control: max-age=600`, and uv otherwise
129
+ reuses a pre-upload snapshot of the index for up to 10 minutes, reporting
130
+ "no version of agentic-dev-loop==X.Y.Z" for a version that is already live:
131
+
132
+ ```bash
133
+ uv pip install --refresh "agentic-dev-loop==1.0.0a17"
134
+ ```
135
+
136
+ On the first run the engine pulls the **digest-pinned** dev image
137
+ (`ghcr.io/lukedawilson/agentic-dev-loop/dev`) and verifies its digest - the
138
+ supply-chain check refuses to continue on a mismatch. If you're running
139
+ pre-publish tooling, point `AGENTIC_DEV_LOOP_DEV_IMAGE` at your locally built
140
+ image. Engine setup, first-run details, the full `pipeline.yaml` contract, and
141
+ troubleshooting for every fail-fast code live in
142
+ [docs/setup.md](docs/setup.md).
143
+
144
+ ## Usage
145
+
146
+ ```bash
147
+ export OPENAI_API_KEY=... # whatever api_key_env names
148
+ agentic-dev-loop pipeline.yaml # run it
149
+ agentic-dev-loop pipeline.yaml --dry-run # validate first - no LLM, no Docker
150
+ agentic-dev-loop pipeline.yaml --resume <thread-id> --max-attempts 5
151
+ ```
152
+
153
+ | Flag | Effect |
154
+ | --- | --- |
155
+ | `--dry-run` | Full load-and-validate pass, then print the resolved plan. No LLM, no Docker, no state writes, no API key. Exit `0` on a clean load. |
156
+ | `--resume <thread-id>` | Continue the latest checkpoint of a previous run (crash, Ctrl-C, exhaustion) with all accumulated notes intact. |
157
+ | `--max-attempts <n>` | Override the pipeline's `max_attempts`. |
158
+ | `--refresh` | Force re-provision of every `setup:` hook (bypasses the stamp cache). |
159
+ | `--clean-stamps` | Delete cached provision stamps and exit. |
160
+ | `--no-viz` / `--viz-port <port>` | Suppress the live graph view / set a preferred port (default 8321; falls back to a free port when busy). |
161
+ | extension flags | Document-loader flags (e.g. `--plan`, `--aidlc-unit`) appear once the pipeline's extensions are loaded - `agentic-dev-loop PIPELINE --help` shows them. |
162
+
163
+ Exit code is `0` on pipeline success, `1` otherwise.
164
+
165
+ `agentic-dev-loop containers` inspects the engine's sandbox containers and
166
+ networks from the host: the default list is read-only and resolves each
167
+ engine-owned object to its owning run and a `live`, `orphaned`,
168
+ `stale-entry`, or `unregistered` verdict; `containers --reap` removes only
169
+ registry-owned orphans whose owner pid is dead (never unregistered names).
170
+ A SIGTERM to a running pipeline still closes the sandbox (the graph's
171
+ `finally` runs); SIGKILL is unhandleable, so its containers are reclaimed by
172
+ the startup reaper on the next run or by `containers --reap`. The reaper
173
+ and `--reap` prune a registry entry only after the daemon confirms the
174
+ object is gone; a surviving object (a failed removal) keeps its entry so
175
+ the next startup retries it. Pass
176
+ `--state-dir` to point at a custom `isolation.state_dir`.
177
+
178
+ **What a run looks like** (the shipped self-loop example):
179
+
180
+ ![The live graph view - pipeline stages with verdict badges, retry cycles, and per-stage consoles](docs/viz-screenshot.png)
181
+
182
+ 1. **dev** - the dev agent works with full context (loaded docs + shared files).
183
+ Its edits are untracked; only declared `produces:` files are hygiene-tracked.
184
+ 2. **test** - a mechanical command step runs the suite inside the sandbox.
185
+ Exit 0 moves on; anything else rewinds to `dev` with the failing output.
186
+ 3. **review / qa** - verdict agents write artifacts whose `VERDICT:` lines route
187
+ the graph. `fail` rewinds to `dev` and consumes an attempt.
188
+ 4. **commit** - the single commit point fires only when all gates passed and
189
+ the worktree was clean at start.
190
+ 5. **success** - committed; or **failure** when attempts are exhausted.
191
+
192
+ ### pipeline.yaml reference
193
+
194
+ ```yaml
195
+ name: my-construction-loop
196
+ extensions:
197
+ - agentic_dev_loop.engine.extensions.aidlc:AidlcExtension # bundled AI-DLC doc loader
198
+ additional_files:
199
+ - ../README.md # shared context for every agent step
200
+
201
+ llm:
202
+ model: openai/deepseek-v4-pro
203
+ base_url: https://api.deepseek.com
204
+ api_key_env: OPENAI_API_KEY
205
+ # reasoning_effort: medium # optional: low | medium | high | xhigh | none
206
+
207
+ max_attempts: 10 # CLI --max-attempts overrides
208
+ agents_dir: sdk_agents # your <name>.agent.md files
209
+
210
+ steps:
211
+ - name: brief
212
+ reasoning_effort: medium
213
+ brief_inputs:
214
+ include: [AGENTS.md, coding-standards-ddd.yaml]
215
+ sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
216
+ env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
217
+ on_pass: dev
218
+
219
+ - name: dev
220
+ agent: dev
221
+ produces: implementation-summary.md
222
+ terminal_timeout: 900
223
+ on_pass: test
224
+
225
+ - name: test
226
+ command: .venv-sandbox/bin/python -m pytest tests/ -q -m "not docker"
227
+ setup: python -m venv .venv-sandbox && .venv-sandbox/bin/pip install -e ".[dev]"
228
+ timeout: 600
229
+ on_pass: review
230
+ on_fail: { goto: dev, retry: true }
231
+
232
+ - name: review
233
+ agent: review
234
+ artifact: review-findings.md
235
+ verdicts: { pass: APPROVED, fail: NEEDS CHANGES }
236
+ # reasoning_effort: xhigh # per-step override; falls back to the llm block, then SDK default
237
+ on_pass: qa
238
+ on_fail: { goto: dev, retry: true }
239
+
240
+ - name: qa
241
+ agent: qa
242
+ artifact: qa-report.md
243
+ verdicts: { pass: PASS, fail: FAIL }
244
+ max_iterations: 100
245
+ finalize_nudge_turns: 10
246
+ on_pass: commit
247
+ on_fail: { goto: dev, retry: true }
248
+
249
+ - name: commit
250
+ action: commit
251
+ params: { message: "feat: construct {subject} (agent dev loop)" }
252
+ ```
253
+
254
+ | Key | Notes |
255
+ | --- | --- |
256
+ | `agent:` / `command:` / `action:` / `brief_inputs:` | 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)). `brief_inputs:` is a native brief step - see its row below. |
257
+ | `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
258
+ | `brief_inputs:` | The discriminator for a native brief step: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. There is no `agent:`, `produces:`, or `max_iterations:` - the prompt is engine-embedded, the capture file is `attempt-brief.md`, and the iteration cap is 12, all engine-owned. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as the step's task message, and regenerates the step only when those inputs change. `sections` is the ordered list of digest headings the brief must emit (default `[Repo rules digest, Unit scope digest, Environment facts, Open risks]`; non-empty and unique). `name` stays free-form, so multiple brief steps are allowed. |
259
+ | `terminal_timeout:` | Optional agent-step per-action timeout in seconds (gt 0). `SandboxTerminalExecutor` uses it when the agent leaves the terminal action timeout unset, overriding the SDK tool's default. `None` = current behavior. |
260
+ | `finalize_nudge_turns:` | Optional agent-step final-response nudge (gt 0). Requires `max_iterations:` and must be less than it. At `max_iterations - finalize_nudge_turns` completed turns the engine injects one advisory turn ("`N turns remain: produce your final response now`"), exactly once; the agent may still exhaust its turns and existing stuck/unclear handling applies unchanged. Absent = current behavior. Provisional pending one consumer run (revert if noisy or the raised cap inflates cost). |
261
+ | `custom_prompt:` | Planned (planning-docs/040). Optional agent-step value: literal text, or a path whose file contents are read at load time, appended verbatim to the agent prompt at context assembly (after the prompt, before the task/artifacts). A value that looks like a path but names no file is a validation error. Agent steps only. |
262
+ | `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block (pipeline-global) or per-step (overrides the block). Unset anywhere = the field is not passed and the SDK default (`high`) applies, so today's behavior is unchanged. Forwarded only for models the SDK/LiteLLM mark as supporting it; an unsupported value fails at the first LLM call. See [Reasoning effort](#reasoning-effort-opt-in). |
263
+ | `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
264
+ | `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). |
265
+ | `setup_probe:` | Optional consumer-authored in-container probe run on a warm `setup:` stamp instead of the venv probe. Exit 0 trusts the stamp (skips); any nonzero re-provisions with network on (fail toward freshness). The probe text is NOT part of the stamp key, so a probe-only change needs `--refresh` or `--clean-stamps` to take effect. Example (asserting a warmed esbuild binary is Linux-shaped): `file frontend-editor/node_modules/esbuild/bin/esbuild | grep -q ELF`. |
266
+ | `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). |
267
+ | `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). |
268
+ | `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). |
269
+ | `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` - artifacts and checkpoints live outside the sandbox mount. |
270
+ | `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. |
271
+ | `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). |
272
+ | `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. |
273
+ | `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). |
274
+ | `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). |
275
+
276
+ ### Reasoning effort (opt-in)
277
+
278
+ `reasoning_effort` is an opt-in latency dial on the pipeline `llm` block and
279
+ on individual agent steps. It takes one of `low`, `medium`, `high`, `xhigh`,
280
+ or `none`; unset means the field is never passed and the SDK default
281
+ (`high`) applies, so a pipeline that omits it behaves exactly as before. A
282
+ step's value overrides the `llm` block's value, so the review lane can hold
283
+ a higher effort than the rest.
284
+
285
+ The value is forwarded only for models the SDK/LiteLLM mark as supporting
286
+ it; an unsupported value fails at the first LLM call (the schema rejects
287
+ unknown names at load time, but cannot know provider capability). No
288
+ pipeline ships a value: adopt one only after an observed run proves lower
289
+ per-turn latency with a green suite and no provider 400s.
290
+
291
+ Recommended lane defaults (guidance only, not enforced):
292
+
293
+ | Lane | reasoning_effort | Why |
294
+ | --- | --- | --- |
295
+ | brief | medium | digest and classify, no design work |
296
+ | dev | high | hold the line: dev caught a real plan bug and a real sandbox triage this unit |
297
+ | qa | medium | classification discipline must stay reliable |
298
+ | review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
299
+
300
+ A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
301
+ run start (or when its inputs change), its engine-owned `attempt-brief.md`
302
+ digest survives attempt starts, and the dev/review agents read that digest
303
+ instead of re-deriving repo rules and environment facts every attempt.
304
+
305
+ The brief digest is mechanically coverage-checked: the configured
306
+ `brief_inputs.sections` headings must each appear on their own line, every
307
+ anchor extracted from the `include` files (markdown `##` headings, and YAML
308
+ category names plus `enforcement_levels`) must appear on its own line
309
+ (case-insensitive, whitespace-collapsed, `**` decoration allowed), and every
310
+ `env_facts` entry must appear verbatim. The section list feeds both the
311
+ engine-embedded brief prompt and the coverage gate, so the two can never
312
+ disagree; a miss
313
+ re-runs the brief agent once per missing set with a `## Coverage retry`
314
+ correction note, bounded to three retries per input set; if the same items are
315
+ still dropped after the cap, the run fails and `last-error.md` names the
316
+ persistent drops. Include files with no extractable anchors pass vacuously, and
317
+ unusual YAML rule shapes are under-checked, never over-checked.
318
+
319
+ ### Secrets (`secrets:`) - refs only, never values
320
+
321
+ Secret values never belong in the pipeline yaml, the workspace, or `state_dir`.
322
+ Declare the refs at the top level and opt each step into the refs it needs:
323
+
324
+ ```yaml
325
+ secrets: # refs only - never values
326
+ kinde-client-secret: # ref name (arbitrary)
327
+ from: env # provider name
328
+ key: KINDIE_CLIENT_SECRET # provider-specific locator
329
+ r2-keys:
330
+ from: env
331
+ key: R2_KEYS
332
+
333
+ steps:
334
+ - name: qa
335
+ agent: qa
336
+ secrets: [kinde-client-secret, r2-keys]
337
+ ```
338
+
339
+ - The engine resolves every declared ref **host-side at run start** (the `env`
340
+ provider reads the named host environment variable). A missing variable, an
341
+ unknown provider, or a ref a step lists without declaring it fails fast -
342
+ naming the ref, the step, and the provider error. No silent fallback.
343
+ - A resolved secret is injected into a step's exec env **only when that step
344
+ lists the ref** - the same path step `env:` uses, no new channel. The secret
345
+ never reaches the provision container, `state_dir`, or any other step.
346
+ - Prefer a mechanical `command` step for secret-consuming work (a script runs
347
+ the app with creds) so the LLM and the secret never meet; an `agent` step
348
+ holds a secret only when its job genuinely requires it.
349
+ - **`.env*` ban.** At run start the workspace root is scanned for `.env*`
350
+ files. Any hit fails fast, naming the files, with the remedy text. The
351
+ convention fixtures `.env.example`, `.env.template`, and `.env.sample` are
352
+ whitelisted. This applies regardless of `network:` flags.
353
+ - **Output scrubbing.** Resolved secret values are scrubbed from every
354
+ engine-written output - captured step output, retry notes, artifacts, and
355
+ `state_dir` files. (Hygiene: the declaring step's agent has already seen
356
+ the value in its own env.)
357
+
358
+ ### Per-stage networking
359
+
360
+ The sandbox is network-off by default. A step that legitimately needs egress -
361
+ a QA step that curls a service it booted, an agent that must hit an API or
362
+ fetch package metadata mid-conversation - declares it explicitly, and names
363
+ exactly what it may reach:
364
+
365
+ ```yaml
366
+ steps:
367
+ - name: fetch-docs
368
+ command: curl -fsSL https://example.com/spec.json -o spec.json
369
+ mounts: [{ path: ".", mode: rw }] # the cwd + a place to write spec.json
370
+ network: true # egress for this step only
371
+ egress: { targets: [example.com] } # ...and only these hosts
372
+
373
+ - name: restore
374
+ command: .venv-sandbox/bin/pip install -e ".[dev]"
375
+ mounts: [{ path: ".", mode: rw }]
376
+ network: true
377
+ egress: { presets: [package-registries] } # pypi/npm/NuGet/cargo + VCS
378
+ ```
379
+
380
+ - **Default off.** Every step runs with `--network none` unless it declares
381
+ `network: true`.
382
+ - **A networked step that writes output must also declare a writable mount.**
383
+ `mounts:` is strict-by-default: with no `mounts:` a step mounts nothing, so
384
+ a command like `curl ... -o spec.json` has no writable cwd to write into.
385
+ Declare `mounts: [{ path: ".", mode: rw }]` (or a narrower rw path) for any
386
+ networked step that needs to produce files.
387
+ - **`network: true` is scoped by the `egress:` allowlist, and egress is
388
+ structurally enforced.** A networked step runs on a per-run internal
389
+ docker network with no route to the host. Its traffic is forced
390
+ through a containerized egress proxy (injected via
391
+ `HTTP_PROXY`/`HTTPS_PROXY`, pointed at the proxy's internal IP
392
+ on the internal network; each distinct egress policy gets its own
393
+ proxy with a distinct internal IP); a request to an undeclared host
394
+ is denied and logged as an enforcement event. The engine never adds
395
+ an undeclared target. `egress: { class: browser }` is the explicit
396
+ broad-but-logged class (every connection logged, nothing filtered); command
397
+ and agent steps default to a strict allowlist.
398
+ - **Command, agent, and sandbox-action steps** may declare it. The host-side
399
+ built-in `commit` action never takes it (refused once the registry is known).
400
+ - **Author opt-in is the control.** A networked step grants its agent egress
401
+ to exactly the declared hosts for that stage; the default stays network-off
402
+ everywhere. Reserve it for stages that need it (QA, integration). The
403
+ residual risks are documented in [docs/setup.md](docs/setup.md).
404
+ - **Host-service reachability (planning-docs/037, 038).** The egress
405
+ proxy is a container on the run's internal network with a second
406
+ host-access attachment carrying the `host.docker.internal`
407
+ host-gateway alias, so the name resolves natively inside the proxy
408
+ container and host services (an S3 SDK against a local MinIO, say)
409
+ are reached through the enforced, logged path. Sandbox containers
410
+ have no host route: the internal network carries no default route to
411
+ the host, and sandbox containers get no host-gateway alias.
412
+ `dial_rewrites` on the proxy remains the platform override for hosts
413
+ where the alias does not resolve.
414
+
415
+ ### Sync-back conflicts (host edits during a run)
416
+
417
+ Only declared-`rw` paths sync back to the real repo after a step. If the host
418
+ also edits a declared-`rw` path while the step is running, sync-back prints
419
+ `[isolation] sync-back conflict: ...` and raises `AE-SBX-016`; the host's
420
+ version wins, the baseline is re-snapshotted, and the next step (even
421
+ `echo hello`) runs clean instead of re-raising forever. For append-style logs
422
+ edited by both sides (an audit log, say), declare them in the top-level
423
+ `merge_on_conflict:` list: those paths are union-merged - the host keeps its
424
+ lines, the sandbox's new lines are appended in order, both sides converge, and
425
+ the run continues with `[isolation] merged concurrent edits: ...` on the
426
+ console.
427
+
428
+
429
+ ## Authoring a loop for your own repo
430
+
431
+ A consumer repo needs two things written by hand - a pipeline file and the
432
+ agent files it names - both committed before the first run (the host-boundary
433
+ check refuses dirty tooling, and the `commit` step never commits it). Two
434
+ reference layouts ship today: this repo dogfoods the engine on itself
435
+ (python + pytest, no frontend, CLI-only qa), and the consumer repo carries
436
+ the wider shape (.NET + npm frontend + a networked browser-probing qa step).
437
+
438
+ ```
439
+ <repo>/agentic-dev-loop/pipeline.yaml
440
+ <repo>/agentic-dev-loop/sdk_agents/dev.agent.md
441
+ <repo>/agentic-dev-loop/sdk_agents/review.agent.md
442
+ <repo>/agentic-dev-loop/sdk_agents/qa.agent.md
443
+ ```
444
+
445
+ ### The agent files
446
+
447
+ Each `sdk_agents/<name>.agent.md` is a markdown prompt plus YAML frontmatter:
448
+ `name` matches a step's `agent:` value; `tools` lists the tool names the step
449
+ may use (`terminal` always; a repo can add more via extensions, e.g.
450
+ `figma`).
451
+
452
+ ```markdown
453
+ ---
454
+ name: review # matches a step's `agent:` value
455
+ tools: [terminal]
456
+ ---
457
+ # Review Agent
458
+ ## WORKFLOW # numbered steps
459
+ ## RULES # absolute never/always rules
460
+ ## SANDBOX NOTES # the sandbox truth for THIS step
461
+ ## OUTPUT # the verdict contract
462
+ ```
463
+
464
+ Three roles, three contracts:
465
+
466
+ | Role | Verdict contract | Typical sandbox |
467
+ | --- | --- | --- |
468
+ | dev | none - the final response is captured as the `produces:` summary | rw mount, offline |
469
+ | review | `VERDICT: APPROVED` / `VERDICT: NEEDS CHANGES` on the first content line | ro mount, offline |
470
+ | qa | `VERDICT: PASS` / `VERDICT: FAIL`; map `unverifiable: UNVERIFIABLE` if you want a third way | shaped by the repo: rw + network + `class: browser` when it boots servers and drives a browser (the consumer repo); ro + offline when it probes the CLI only (this repo) |
471
+
472
+ The conventions that make a good agent file - each one below exists because
473
+ an agent burned budget learning it mid-run:
474
+
475
+ - **State the sandbox truth in the prompt.** Every agent file carries a
476
+ `SANDBOX NOTES` section naming exactly what its step allows: mounts ro or
477
+ rw, network on or off, and which gates have already run. Never make the
478
+ agent discover it.
479
+ - **Gates are the source of truth - cite the record, never re-run.** A
480
+ command the test gate, the editor-tests gate, or the dev summary reports
481
+ as run is coverage evidence, cited as `covered by <test>` or the gate
482
+ record. Review and qa never re-run builds or suites; qa maps every
483
+ acceptance criterion to existing coverage and probes only the remainder.
484
+ - **dev enumerates its impossible set and stops at it.** The dev prompt
485
+ says: stop the moment your next step needs a command this sandbox cannot
486
+ run, and lists the set (package-manager fetches after a new pin, npm or
487
+ vitest offline, playwright downloads, database-category tests), one
488
+ attempt per command. That keeps the productive edit/build/test loop and
489
+ kills doomed retries. The set is toolchain-specific - write yours; this
490
+ repo's is a single bullet (no package managers), the consumer repo's is
491
+ four.
492
+ - **Failure output returns as *Previous attempt feedback*.** Steps route
493
+ `on_fail: { goto: dev, retry: true }`, so gate failure output lands in
494
+ the dev agent's next context; the dev prompt makes every listed issue a
495
+ required fix.
496
+ - **No git mutations, ever.** The `commit` step is the single commit point.
497
+ - **`max_iterations` per role.** qa is bounded probing (100 in this
498
+ repo's dogfood loop, with `finalize_nudge_turns: 10` so it never ends
499
+ silently at the cap - both provisional); dev is open-ended construction
500
+ and stays uncapped (the engine default).
501
+
502
+ ### The pipeline file
503
+
504
+ The full field reference is above; this is the stage lineup and what to
505
+ adapt per repo.
506
+
507
+ | Stage | Kind | Typical shape | Why |
508
+ | --- | --- | --- | --- |
509
+ | dev | agent | rw, offline | does the work; edits are untracked |
510
+ | restore | command | network + `presets: [package-registries]` | fetches what dev pinned (`deps_policy: open`) or the locked install; also the audit escalation point |
511
+ | test | command | offline is fine (warm cache) | exit code is the verdict; the source of truth |
512
+ | editor-tests | command | network + `presets: [package-registries]` | frontend suites that cannot run offline (`npm ci && npm test`). A separate step, never merged into `test`: the step name is the failure diagnosis, its timeout does not contend the backend suite's, and a retry re-runs only its own chain |
513
+ | conformance | command | network + `presets: [package-registries]` | repo-specific fitness checks (the consumer repo: jscpd + DDD rules) |
514
+ | review | agent | ro, offline | static conformance verdict |
515
+ | qa | agent | per repo (see table above) | live-behaviour probes; `max_iterations: 100` + `finalize_nudge_turns: 10` (provisional); inject secrets only if the probes need them |
516
+ | commit | action | host-side | the single commit point |
517
+
518
+ Adaptation checklist for a new repo:
519
+
520
+ 1. `llm:` - model, `base_url`, `api_key_env`.
521
+ 2. `isolation:` - toolchain image override (the consumer repo pins a
522
+ toolchain image by digest) and `seccomp_profile: bundled:chromium-userns`
523
+ only if a stage runs Chromium.
524
+ 3. `caches:` - toolchain cache dirs as rw bind-mounts shared across runs
525
+ (`.nuget-packages`, `qa/node_modules`, `qa/.browsers`,
526
+ `frontend-editor/node_modules`); caches are never synced back, and a
527
+ step's `setup:` hook runs with them mounted so it can warm them.
528
+ 4. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
529
+ 5. `secrets:` - refs only, resolved from host env; each step opts in to the
530
+ refs it needs.
531
+ 6. `egress:` - `presets: [package-registries]` for fetch steps;
532
+ `class: browser` (broad-but-logged) for browser-tier qa only.
533
+ 7. `deps_policy:` and `lockfiles:` - see
534
+ [Dependency policy](#dependency-policy-locked-by-default).
535
+ 8. The dev prompt's impossible set - write it for your toolchain.
536
+ 9. `additional_files:` - the repo's `AGENTS.md`, coding standards, design
537
+ docs: shared context for every agent step.
538
+
539
+ Validate before the first run - dry-run loads and prints the resolved plan
540
+ with no LLM, no Docker, no state writes:
541
+
542
+ ```bash
543
+ agentic-dev-loop agentic-dev-loop/pipeline.yaml --dry-run
544
+ ```
545
+
546
+ Then commit the tooling and run for real.
547
+
548
+ ## Dependency policy (locked by default)
549
+
550
+ The supply-chain dial is a pipeline-level `deps_policy:` field, **locked by
551
+ default**. The engine injects the policy into every agent step's context -
552
+ the rule lives in the engine, not in hand-authored `dev.agent.md` prose (the
553
+ class of rule that deadlocked the NuGet cache).
554
+
555
+ ```yaml
556
+ deps_policy: locked # or: open (default: locked)
557
+ lockfiles: [uv.lock] # or: [packages.lock.json, package-lock.json]
558
+ ```
559
+
560
+ - **`locked`** - the human-owned lockfile is the source of truth; the agent
561
+ may not add dependencies. The injected rule says *"You may only use
562
+ dependencies already present in `<lockfile>`. If the design requires a
563
+ dependency that is not present, declare it in your final response under the
564
+ fixed header `## Missing dependencies` (one `name==version` per line) and
565
+ finish what you can. Do not add it to the manifest."* A non-empty
566
+ `## Missing dependencies` declaration **fails the run fast** with
567
+ `Extend <lockfile> with these pins and re-run: …` - one human lockfile edit,
568
+ not a retry loop. The declaration is advisory to the human; the engine never
569
+ auto-fetches the declared packages.
570
+ - **`open`** - the bootstrap protocol (018): the agent may add exact-pinned
571
+ manifest entries directly (never a package-manager `add` command); the
572
+ network-on restore step fetches them. The engine injects the same rule
573
+ canonically. Nothing about `open` is default.
574
+
575
+ The **restore step** runs the toolchain's locked mode and fails when the
576
+ manifest and lockfile disagree:
577
+
578
+ | Toolchain | Locked restore | Lockfile |
579
+ | --- | --- | --- |
580
+ | .NET | `dotnet restore --locked-mode` | `packages.lock.json` (set `RestorePackagesWithLockFile=true`) |
581
+ | node | `npm ci` | `package-lock.json` |
582
+ | python | `uv sync --locked` (or pip-tools `pip-sync requirements.lock`) | `uv.lock` - pip has no native lockfile; `uv` is the canonical locked-install tool |
583
+
584
+ ### Audit escalation in the restore step
585
+
586
+ The restore step also fails on known-CVE findings, at a per-toolchain
587
+ threshold:
588
+
589
+ | Toolchain | Audit (fails the step) |
590
+ | --- | --- |
591
+ | .NET | `NuGetAudit` at `error` - any audit finding fails the restore |
592
+ | node | `npm audit --audit-level=high` |
593
+ | python | `pip-audit` - any finding fails |
594
+
595
+ Under `locked`, a failing audit means the lockfile pins a vulnerable version:
596
+ the run fails and the human regenerates the lockfile at a patched version.
597
+ Under `open`, the failure routes to `dev` like any restore failure - the dev
598
+ agent can bump the pin, but that pin is audited again on the next pass, so
599
+ the loop self-corrects.
600
+
601
+ ### Generating a lockfile
602
+
603
+ A human generates the lockfile once, on a network machine, and commits it -
604
+ the engine never fetches on the agent's behalf:
605
+
606
+ ```bash
607
+ uv lock # python - writes uv.lock from pyproject.toml
608
+ uv sync --locked # installs exactly uv.lock; fails on any drift
609
+ npm install --package-lock-only # node - writes package-lock.json
610
+ npm ci # installs exactly package-lock.json
611
+ dotnet restore --use-lock-file # .NET - writes packages.lock.json
612
+ dotnet restore --locked-mode # installs exactly packages.lock.json
613
+ ```
614
+
615
+ Keep the lockfile tracked (do not gitignore it) and re-run the locked restore
616
+ to prove it holds from a clean cache before setting `deps_policy: locked`.
617
+
618
+ ## Agent-facing error guide
619
+
620
+ The engine bundles an always-on guide for the engine error codes an agent
621
+ can actually observe while running terminal commands. The guide lives at
622
+ `src/agentic_dev_loop/engine/agent_error_guide.md`, versioned with the engine
623
+ code that emits the codes, and is injected into every agent step's task
624
+ context at assembly time. There is no opt-in and no opt-out in v1; command
625
+ and action steps never receive it because no agent reads them.
626
+
627
+ Each agent-visible code has exactly one table row, shaped like this:
628
+
629
+ | Code | Meaning | Correct response |
630
+ | --- | --- | --- |
631
+ | AE-SBX-016 | The loop refused to sync an edit because the real repo changed underneath on a path this workspace also touches; the file in this workspace is stale and its edits will not be applied. | The command is not the problem: retry once, do not edit or work around the named path, and mention the code in your final response if it persists. |
632
+
633
+ The guide is consumer-agnostic: it explains the general mechanism and never
634
+ names a consumer or a consumer domain rule ID. If an ID must appear inside a
635
+ quoted engine message, the placeholder `<domain-rule-id>` is used. Any engine
636
+ change that adds or changes an agent-visible error code must update the guide
637
+ in the same commit; the test suite enforces the pairing by parsing the
638
+ agent-visible `SandboxError` raise sites against the bundled table.
639
+
640
+ ## Host boundary (security model)
641
+
642
+ The orchestrator, tool layer, and the `commit` action run in the **host
643
+ process** - they hold your credentials and your full user account, so the
644
+ engine keeps anything agent-writable out of that process:
645
+
646
+ - **Consumer actions are sandbox-executed.** An action you register via an
647
+ extension is a command-shaped script (`script + params`), run through the
648
+ same sandbox path as command steps - the exit code is the verdict, and the
649
+ code never runs in the host. Only the engine's trusted built-ins (`commit`,
650
+ document loaders) run host-side.
651
+ - **Extensions load from outside the workspace.** `extensions_dir` defaults to
652
+ `~/.agentic-dev-loop/extensions`; at load time any extension or tool module
653
+ whose file resolves under the workspace is refused *before import*, naming
654
+ the file.
655
+ - **The engine installs outside the workspace.** Install with pipx so the venv
656
+ is unmounted from the sandbox; a startup self-check refuses to run if
657
+ `agentic_dev_loop` resolves under the workspace (an editable install bypasses
658
+ everything).
659
+ - **Tooling is read-only by the loop.** At run start the pipeline file,
660
+ `agents_dir`, `additional_files`, `aidlc-docs/`, and `tools/` must be
661
+ git-clean relative to HEAD - otherwise the run refuses, naming the dirty
662
+ files. The `commit` action excludes those paths from its sweep, so the loop
663
+ never commits its own tooling.
664
+ - **`commit` stays host-side, deliberately.** It holds push credentials; moving
665
+ it into the sandbox would trade host-exec-of-trusted-code for
666
+ credential-in-sandbox. It runs argv-style with a scrubbed child env and
667
+ `-c core.hooksPath=~/.agentic-dev-loop/state/hooks` so repo hooks never
668
+ execute.
669
+
670
+ Net: the host executes nothing from the workspace, reads only git-clean
671
+ control files, and the only agent-writable host-consumed thing left is the code
672
+ the loop is building.
673
+
674
+ ### Consumer migration notes
675
+
676
+ - **Actions** - replace a host-side `fn` registration with
677
+ `registry.add_sandbox_action(name, script)`. `script` is a command string
678
+ rendered with the step's `params:` plus `{subject}`; the exit code is the
679
+ verdict. `add_action` (host-side) is now engine-built-in-only and is refused
680
+ from extensions.
681
+ - **Extensions** - move workspace-resident extension modules (e.g.
682
+ `extensions/*.py`) into `~/.agentic-dev-loop/extensions/` (or whatever
683
+ `extensions_dir:` names), and reinstall the engine via pipx so it is not
684
+ editable/in-workspace.
685
+ - **Tooling** - commit the pipeline, agent defs, shared `additional_files`,
686
+ `aidlc-docs/`, and `tools/` before running: the run refuses dirty tooling,
687
+ and `commit` never commits it.
688
+
689
+ ## Development environment
690
+
691
+ ```bash
692
+ git clone git@github.com:lukedawilson/agentic-dev-loop.git
693
+ cd agentic-dev-loop
694
+ pipx install "agentic-dev-loop==1.0.0a17" # engine venv OUTSIDE the workspace (self-check)
695
+ export OPENAI_API_KEY=... # set your LLM key in the host env (no .env in the repo)
696
+ python -m pytest tests/ -q # fast suite (in-repo .venv is for tests only)
697
+ python -m pytest tests/ -q -m "not docker" # skip engine-dependent tests
698
+ agentic-dev-loop agentic-dev-loop/pipeline.yaml --dry-run # run the shipped pipeline
699
+ ./run-demo.sh # viz demo - scripted run replay, no pipeline needed
700
+ ```
701
+
702
+ - `setup-venv.sh` builds the throwaway `.venv` for local `pytest` runs (the
703
+ pipeline's `test` step provisions its own `.venv-sandbox` inside the
704
+ sandbox). The loop itself must run from the pipx install - never an
705
+ in-workspace `.venv`, which the startup self-check refuses.
706
+ - Docker-marked integration tests need a local daemon; CI runs them on Linux.
707
+ - The shipped pipeline (`agentic-dev-loop/pipeline.yaml`) **dogfoods the tool on
708
+ its own repo** - dev → test → review → qa → commit, with the engine's own
709
+ three agent definitions in `sdk_agents/`.
710
+ - CI: `ci.yml` (unit + integration suite, ubuntu),
711
+ `publish-image.yml` (multi-arch dev image, amd64 + arm64), `release.yml`
712
+ (PyPI publishing).
713
+
714
+ ## Contributing
715
+
716
+ - **Read [AGENTS.md](AGENTS.md) first** - it is the instruction set for both
717
+ human and agent contributors (default mode: the user drives, ask before
718
+ coding, TDD, no silent fallbacks).
719
+ - Development is structured by **AI-DLC** - the lifecycle workflow in
720
+ `.aidlc/aidlc-rules/aws-aidlc-rules/` with working docs (plans, units, state)
721
+ in `aidlc-docs/`. The bundled document loader autodetects the current unit
722
+ from `aidlc-state.md` via `--aidlc-unit`, or load any plan with `--plan`.
723
+ - OpenCode commands over the workflow: `/unit-status`, `/epic-status`,
724
+ `/ticket-details`, `/update-subtask`, `/apply-aidlc-patches`, and more.
725
+ - Working extension samples (actions, tools, document loaders) ship as
726
+ `tests/sample_ext.py`.
727
+
728
+ ---
729
+
730
+ Built on [OpenHands SDK](https://github.com/OpenHands/software-agent-sdk) and
731
+ [LangGraph](https://github.com/langchain-ai/langgraph).