agentic-dev-loop 1.0.0a1__tar.gz → 1.0.0a2__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.
- agentic_dev_loop-1.0.0a2/PKG-INFO +239 -0
- agentic_dev_loop-1.0.0a2/README.md +201 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/isolation.py +18 -15
- agentic_dev_loop-1.0.0a2/src/agentic_dev_loop.egg-info/PKG-INFO +239 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_isolation.py +27 -12
- agentic_dev_loop-1.0.0a1/PKG-INFO +0 -343
- agentic_dev_loop-1.0.0a1/README.md +0 -305
- agentic_dev_loop-1.0.0a1/src/agentic_dev_loop.egg-info/PKG-INFO +0 -343
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/actions.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/agents.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/cli.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/config.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/graph.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/llm.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/proc.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/registry.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/results.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/stages.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop/viz.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a1 → agentic_dev_loop-1.0.0a2}/tests/test_viz.py +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentic-dev-loop
|
|
3
|
+
Version: 1.0.0a2
|
|
4
|
+
Summary: Opinionated LangGraph engine for OpenHands agent pipelines
|
|
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: openhands-sdk~=1.17.0
|
|
29
|
+
Requires-Dist: openhands-tools~=1.17.0
|
|
30
|
+
Requires-Dist: langgraph>=1.2
|
|
31
|
+
Requires-Dist: langgraph-checkpoint-sqlite>=3.0
|
|
32
|
+
Requires-Dist: pydantic>=2.0
|
|
33
|
+
Requires-Dist: PyYAML>=6.0
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# agentic-dev-loop
|
|
40
|
+
|
|
41
|
+
> **Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase.**
|
|
42
|
+
|
|
43
|
+
Describe your pipeline in one YAML file — agent steps, mechanical test gates, and
|
|
44
|
+
trusted actions — and `agentic-dev-loop` runs it end to end: agent and command steps get
|
|
45
|
+
kernel-level sandboxing via Docker, verdicts route the graph, every boundary is
|
|
46
|
+
checkpointed, and nothing gets committed until every gate passes.
|
|
47
|
+
|
|
48
|
+
```mermaid
|
|
49
|
+
flowchart LR
|
|
50
|
+
dev["agent: dev"] --> test{{"test gate"}} --> review["agent: review"] --> qa["agent: qa"] --> commit["action: commit"]
|
|
51
|
+
test -- fail --> dev
|
|
52
|
+
review -- fail --> dev
|
|
53
|
+
qa -- fail --> dev
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- **A tool, not a wiring project.** One CLI entry point, opinionated defaults,
|
|
57
|
+
fixed safety policies. You write the pipeline and the agent definitions —
|
|
58
|
+
nothing else.
|
|
59
|
+
- **Deterministic.** Same pipeline + same state → same routing. Verdicts are
|
|
60
|
+
parsed from files, retries are bounded, nothing is best-effort.
|
|
61
|
+
- **Sandboxed by default.** The agent's shell and every command step run inside
|
|
62
|
+
an always-on Docker sandbox. There is no opt-out.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Core features
|
|
67
|
+
|
|
68
|
+
| Feature | What it gives you |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| **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. |
|
|
71
|
+
| **Kernel-level sandboxing** | Agent work and command stages run isolated from your machine: no network access, no writes outside your workspace. No sandbox available → the run stops instead of degrading. |
|
|
72
|
+
| **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. |
|
|
73
|
+
| **Live graph visualisation** | Follow every run in the browser: stage status, pass/fail verdicts, retry cycles, and live console output as it happens. |
|
|
74
|
+
| **Extensible** | Plug in your own actions, tools, and document loaders to adapt the loop to your projects. |
|
|
75
|
+
|
|
76
|
+
The fixed policies *are* the product: fail fast on missing config/secrets, no
|
|
77
|
+
silent fallbacks, verdicts only from artifacts, bounded retries, per-attempt
|
|
78
|
+
artifact hygiene, a single clean-worktree-gated commit point, and no default
|
|
79
|
+
LLM timeout (agent runs are long).
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
**Prerequisites**
|
|
84
|
+
|
|
85
|
+
| Requirement | Detail |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| Python | ≥ 3.12 |
|
|
88
|
+
| Docker engine | Engine API ≥ 1.43 (Engine 24). Any compliant engine works — Docker Desktop, OrbStack, Colima, Rancher Desktop, podman docker-compat. On Windows, Linux-containers mode is required. |
|
|
89
|
+
| 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. |
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pip install agentic-dev-loop==1.0.0a1 # current release (alpha — pin it explicitly)
|
|
93
|
+
# or: pip install --pre agentic-dev-loop
|
|
94
|
+
agentic-dev-loop --version
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
On the first run the engine pulls the **digest-pinned** dev image
|
|
98
|
+
(`ghcr.io/lukedawilson/agentic-dev-loop/dev`) and verifies its digest — the
|
|
99
|
+
supply-chain check refuses to continue on a mismatch. If you're running
|
|
100
|
+
pre-publish tooling, point `AGENTIC_DEV_LOOP_DEV_IMAGE` at your locally built
|
|
101
|
+
image. Engine setup, first-run details, the full `pipeline.yaml` contract, and
|
|
102
|
+
troubleshooting for every fail-fast code live in
|
|
103
|
+
[docs/setup.md](docs/setup.md).
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
export OPENAI_API_KEY=... # whatever api_key_env names
|
|
109
|
+
agentic-dev-loop pipeline.yaml # run it
|
|
110
|
+
agentic-dev-loop pipeline.yaml --dry-run # validate first — no LLM, no Docker
|
|
111
|
+
agentic-dev-loop pipeline.yaml --resume <thread-id> --max-attempts 5
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Flag | Effect |
|
|
115
|
+
| --- | --- |
|
|
116
|
+
| `--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. |
|
|
117
|
+
| `--resume <thread-id>` | Continue the latest checkpoint of a previous run (crash, Ctrl-C, exhaustion) with all accumulated notes intact. |
|
|
118
|
+
| `--max-attempts <n>` | Override the pipeline's `max_attempts`. |
|
|
119
|
+
| `--refresh` | Force re-provision of every `setup:` hook (bypasses the stamp cache). |
|
|
120
|
+
| `--clean-stamps` | Delete cached provision stamps and exit. |
|
|
121
|
+
| `--no-viz` / `--viz-port <port>` | Suppress the live graph view / set a preferred port (default 8321; falls back to a free port when busy). |
|
|
122
|
+
| 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. |
|
|
123
|
+
|
|
124
|
+
Exit code is `0` on pipeline success, `1` otherwise.
|
|
125
|
+
|
|
126
|
+
**What a run looks like** (the shipped self-loop example):
|
|
127
|
+
|
|
128
|
+

|
|
129
|
+
|
|
130
|
+
1. **dev** — the dev agent works with full context (loaded docs + shared files).
|
|
131
|
+
Its edits are untracked; only declared `produces:` files are hygiene-tracked.
|
|
132
|
+
2. **test** — a mechanical command step runs the suite inside the sandbox.
|
|
133
|
+
Exit 0 moves on; anything else rewinds to `dev` with the failing output.
|
|
134
|
+
3. **review / qa** — verdict agents write artifacts whose `VERDICT:` lines route
|
|
135
|
+
the graph. `fail` rewinds to `dev` and consumes an attempt.
|
|
136
|
+
4. **commit** — the single commit point fires only when all gates passed and
|
|
137
|
+
the worktree was clean at start.
|
|
138
|
+
5. **success** — committed; or **failure** when attempts are exhausted.
|
|
139
|
+
|
|
140
|
+
### pipeline.yaml reference
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
name: my-construction-loop
|
|
144
|
+
extensions:
|
|
145
|
+
- agentic_dev_loop.extensions.aidlc:AidlcExtension # bundled AI-DLC doc loader
|
|
146
|
+
additional_files:
|
|
147
|
+
- ../README.md # shared context for every agent step
|
|
148
|
+
|
|
149
|
+
llm:
|
|
150
|
+
model: openai/deepseek-v4-pro
|
|
151
|
+
base_url: https://api.deepseek.com
|
|
152
|
+
api_key_env: OPENAI_API_KEY
|
|
153
|
+
|
|
154
|
+
max_attempts: 10 # CLI --max-attempts overrides
|
|
155
|
+
agents_dir: sdk_agents # your <name>.agent.md files
|
|
156
|
+
|
|
157
|
+
steps:
|
|
158
|
+
- name: dev
|
|
159
|
+
agent: dev
|
|
160
|
+
produces: implementation-summary.md
|
|
161
|
+
on_pass: test
|
|
162
|
+
|
|
163
|
+
- name: test
|
|
164
|
+
command: .venv-sandbox/bin/python -m pytest tests/ -q -m "not docker"
|
|
165
|
+
setup: python -m venv .venv-sandbox && .venv-sandbox/bin/pip install -e ".[dev]"
|
|
166
|
+
timeout: 600
|
|
167
|
+
on_pass: review
|
|
168
|
+
on_fail: { goto: dev, retry: true }
|
|
169
|
+
|
|
170
|
+
- name: review
|
|
171
|
+
agent: review
|
|
172
|
+
artifact: review-findings.md
|
|
173
|
+
verdicts: { pass: APPROVED, fail: NEEDS CHANGES }
|
|
174
|
+
on_pass: qa
|
|
175
|
+
on_fail: { goto: dev, retry: true }
|
|
176
|
+
|
|
177
|
+
- name: qa
|
|
178
|
+
agent: qa
|
|
179
|
+
artifact: qa-report.md
|
|
180
|
+
verdicts: { pass: PASS, fail: FAIL }
|
|
181
|
+
on_pass: commit
|
|
182
|
+
on_fail: { goto: dev, retry: true }
|
|
183
|
+
|
|
184
|
+
- name: commit
|
|
185
|
+
action: commit
|
|
186
|
+
params: { message: "feat: construct {subject} (agent dev loop)" }
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
| Key | Notes |
|
|
190
|
+
| --- | --- |
|
|
191
|
+
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call); actions are registered host-side functions. |
|
|
192
|
+
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
193
|
+
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
194
|
+
| `env:` / `setup:` (command steps) | Declared env inside the sandbox (fixed safe base + declared vars only); `setup:` provisions the sandbox (network ON, stamp-cached, `--refresh` forces). |
|
|
195
|
+
| `isolation:` (optional) | Configures the sandbox — image override, resource caps, `allow_git_mutations`. It can never disable it. |
|
|
196
|
+
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` — artifacts and checkpoints live outside the sandbox mount. |
|
|
197
|
+
|
|
198
|
+
## Development environment
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
git clone git@github.com:lukedawilson/agentic-dev-loop.git
|
|
202
|
+
cd agentic-dev-loop
|
|
203
|
+
./setup-venv.sh # idempotent .venv setup (never touches bare pip)
|
|
204
|
+
# create .env with your LLM key (loaded by trigger-agentic-dev-loop.sh, never committed):
|
|
205
|
+
# echo 'OPENAI_API_KEY=...' > .env
|
|
206
|
+
python -m pytest tests/ -q # fast suite
|
|
207
|
+
python -m pytest tests/ -q -m "not docker" # skip engine-dependent tests
|
|
208
|
+
./trigger-agentic-dev-loop.sh --dry-run # run the shipped pipeline against this repo
|
|
209
|
+
./run-demo.sh # viz demo — scripted run replay, no pipeline needed
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
- `setup-venv.sh` is safe to re-run and picks the right interpreter even on
|
|
213
|
+
machines with exotic Python installations.
|
|
214
|
+
- Docker-marked integration tests need a local daemon; CI runs them on Linux.
|
|
215
|
+
- The shipped pipeline (`agentic-dev-loop/pipeline.yaml`) **dogfoods the tool on
|
|
216
|
+
its own repo** — dev → test → review → qa → commit, with the engine's own
|
|
217
|
+
three agent definitions in `sdk_agents/`.
|
|
218
|
+
- CI: `ci.yml` (unit + integration suite, ubuntu + windows),
|
|
219
|
+
`publish-image.yml` (multi-arch dev image, amd64 + arm64), `release.yml`
|
|
220
|
+
(PyPI publishing).
|
|
221
|
+
|
|
222
|
+
## Contributing
|
|
223
|
+
|
|
224
|
+
- **Read [AGENTS.md](AGENTS.md) first** — it is the instruction set for both
|
|
225
|
+
human and agent contributors (default mode: the user drives, ask before
|
|
226
|
+
coding, TDD, no silent fallbacks).
|
|
227
|
+
- Development is structured by **AI-DLC** — the lifecycle workflow in
|
|
228
|
+
`.aidlc/aidlc-rules/aws-aidlc-rules/` with working docs (plans, units, state)
|
|
229
|
+
in `aidlc-docs/`. The bundled document loader autodetects the current unit
|
|
230
|
+
from `aidlc-state.md` via `--aidlc-unit`, or load any plan with `--plan`.
|
|
231
|
+
- OpenCode commands over the workflow: `/unit-status`, `/epic-status`,
|
|
232
|
+
`/ticket-details`, `/update-subtask`, `/apply-aidlc-patches`, and more.
|
|
233
|
+
- Working extension samples (actions, tools, document loaders) ship as
|
|
234
|
+
`tests/sample_ext.py`.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
Built on [OpenHands SDK](https://github.com/OpenHands/software-agent-sdk) and
|
|
239
|
+
[LangGraph](https://github.com/langchain-ai/langgraph).
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# agentic-dev-loop
|
|
2
|
+
|
|
3
|
+
> **Turn an LLM into a gated, sandboxed, resumable construction loop over your codebase.**
|
|
4
|
+
|
|
5
|
+
Describe your pipeline in one YAML file — agent steps, mechanical test gates, and
|
|
6
|
+
trusted actions — and `agentic-dev-loop` runs it end to end: agent and command steps get
|
|
7
|
+
kernel-level sandboxing via Docker, verdicts route the graph, every boundary is
|
|
8
|
+
checkpointed, and nothing gets committed until every gate passes.
|
|
9
|
+
|
|
10
|
+
```mermaid
|
|
11
|
+
flowchart LR
|
|
12
|
+
dev["agent: dev"] --> test{{"test gate"}} --> review["agent: review"] --> qa["agent: qa"] --> commit["action: commit"]
|
|
13
|
+
test -- fail --> dev
|
|
14
|
+
review -- fail --> dev
|
|
15
|
+
qa -- fail --> dev
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- **A tool, not a wiring project.** One CLI entry point, opinionated defaults,
|
|
19
|
+
fixed safety policies. You write the pipeline and the agent definitions —
|
|
20
|
+
nothing else.
|
|
21
|
+
- **Deterministic.** Same pipeline + same state → same routing. Verdicts are
|
|
22
|
+
parsed from files, retries are bounded, nothing is best-effort.
|
|
23
|
+
- **Sandboxed by default.** The agent's shell and every command step run inside
|
|
24
|
+
an always-on Docker sandbox. There is no opt-out.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Core features
|
|
29
|
+
|
|
30
|
+
| Feature | What it gives you |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| **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. |
|
|
33
|
+
| **Kernel-level sandboxing** | Agent work and command stages run isolated from your machine: no network access, no writes outside your workspace. No sandbox available → the run stops instead of degrading. |
|
|
34
|
+
| **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. |
|
|
35
|
+
| **Live graph visualisation** | Follow every run in the browser: stage status, pass/fail verdicts, retry cycles, and live console output as it happens. |
|
|
36
|
+
| **Extensible** | Plug in your own actions, tools, and document loaders to adapt the loop to your projects. |
|
|
37
|
+
|
|
38
|
+
The fixed policies *are* the product: fail fast on missing config/secrets, no
|
|
39
|
+
silent fallbacks, verdicts only from artifacts, bounded retries, per-attempt
|
|
40
|
+
artifact hygiene, a single clean-worktree-gated commit point, and no default
|
|
41
|
+
LLM timeout (agent runs are long).
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
**Prerequisites**
|
|
46
|
+
|
|
47
|
+
| Requirement | Detail |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| Python | ≥ 3.12 |
|
|
50
|
+
| Docker engine | Engine API ≥ 1.43 (Engine 24). Any compliant engine works — Docker Desktop, OrbStack, Colima, Rancher Desktop, podman docker-compat. On Windows, Linux-containers mode is required. |
|
|
51
|
+
| 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. |
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install agentic-dev-loop==1.0.0a1 # current release (alpha — pin it explicitly)
|
|
55
|
+
# or: pip install --pre agentic-dev-loop
|
|
56
|
+
agentic-dev-loop --version
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
On the first run the engine pulls the **digest-pinned** dev image
|
|
60
|
+
(`ghcr.io/lukedawilson/agentic-dev-loop/dev`) and verifies its digest — the
|
|
61
|
+
supply-chain check refuses to continue on a mismatch. If you're running
|
|
62
|
+
pre-publish tooling, point `AGENTIC_DEV_LOOP_DEV_IMAGE` at your locally built
|
|
63
|
+
image. Engine setup, first-run details, the full `pipeline.yaml` contract, and
|
|
64
|
+
troubleshooting for every fail-fast code live in
|
|
65
|
+
[docs/setup.md](docs/setup.md).
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
export OPENAI_API_KEY=... # whatever api_key_env names
|
|
71
|
+
agentic-dev-loop pipeline.yaml # run it
|
|
72
|
+
agentic-dev-loop pipeline.yaml --dry-run # validate first — no LLM, no Docker
|
|
73
|
+
agentic-dev-loop pipeline.yaml --resume <thread-id> --max-attempts 5
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Flag | Effect |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `--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. |
|
|
79
|
+
| `--resume <thread-id>` | Continue the latest checkpoint of a previous run (crash, Ctrl-C, exhaustion) with all accumulated notes intact. |
|
|
80
|
+
| `--max-attempts <n>` | Override the pipeline's `max_attempts`. |
|
|
81
|
+
| `--refresh` | Force re-provision of every `setup:` hook (bypasses the stamp cache). |
|
|
82
|
+
| `--clean-stamps` | Delete cached provision stamps and exit. |
|
|
83
|
+
| `--no-viz` / `--viz-port <port>` | Suppress the live graph view / set a preferred port (default 8321; falls back to a free port when busy). |
|
|
84
|
+
| 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. |
|
|
85
|
+
|
|
86
|
+
Exit code is `0` on pipeline success, `1` otherwise.
|
|
87
|
+
|
|
88
|
+
**What a run looks like** (the shipped self-loop example):
|
|
89
|
+
|
|
90
|
+

|
|
91
|
+
|
|
92
|
+
1. **dev** — the dev agent works with full context (loaded docs + shared files).
|
|
93
|
+
Its edits are untracked; only declared `produces:` files are hygiene-tracked.
|
|
94
|
+
2. **test** — a mechanical command step runs the suite inside the sandbox.
|
|
95
|
+
Exit 0 moves on; anything else rewinds to `dev` with the failing output.
|
|
96
|
+
3. **review / qa** — verdict agents write artifacts whose `VERDICT:` lines route
|
|
97
|
+
the graph. `fail` rewinds to `dev` and consumes an attempt.
|
|
98
|
+
4. **commit** — the single commit point fires only when all gates passed and
|
|
99
|
+
the worktree was clean at start.
|
|
100
|
+
5. **success** — committed; or **failure** when attempts are exhausted.
|
|
101
|
+
|
|
102
|
+
### pipeline.yaml reference
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
name: my-construction-loop
|
|
106
|
+
extensions:
|
|
107
|
+
- agentic_dev_loop.extensions.aidlc:AidlcExtension # bundled AI-DLC doc loader
|
|
108
|
+
additional_files:
|
|
109
|
+
- ../README.md # shared context for every agent step
|
|
110
|
+
|
|
111
|
+
llm:
|
|
112
|
+
model: openai/deepseek-v4-pro
|
|
113
|
+
base_url: https://api.deepseek.com
|
|
114
|
+
api_key_env: OPENAI_API_KEY
|
|
115
|
+
|
|
116
|
+
max_attempts: 10 # CLI --max-attempts overrides
|
|
117
|
+
agents_dir: sdk_agents # your <name>.agent.md files
|
|
118
|
+
|
|
119
|
+
steps:
|
|
120
|
+
- name: dev
|
|
121
|
+
agent: dev
|
|
122
|
+
produces: implementation-summary.md
|
|
123
|
+
on_pass: test
|
|
124
|
+
|
|
125
|
+
- name: test
|
|
126
|
+
command: .venv-sandbox/bin/python -m pytest tests/ -q -m "not docker"
|
|
127
|
+
setup: python -m venv .venv-sandbox && .venv-sandbox/bin/pip install -e ".[dev]"
|
|
128
|
+
timeout: 600
|
|
129
|
+
on_pass: review
|
|
130
|
+
on_fail: { goto: dev, retry: true }
|
|
131
|
+
|
|
132
|
+
- name: review
|
|
133
|
+
agent: review
|
|
134
|
+
artifact: review-findings.md
|
|
135
|
+
verdicts: { pass: APPROVED, fail: NEEDS CHANGES }
|
|
136
|
+
on_pass: qa
|
|
137
|
+
on_fail: { goto: dev, retry: true }
|
|
138
|
+
|
|
139
|
+
- name: qa
|
|
140
|
+
agent: qa
|
|
141
|
+
artifact: qa-report.md
|
|
142
|
+
verdicts: { pass: PASS, fail: FAIL }
|
|
143
|
+
on_pass: commit
|
|
144
|
+
on_fail: { goto: dev, retry: true }
|
|
145
|
+
|
|
146
|
+
- name: commit
|
|
147
|
+
action: commit
|
|
148
|
+
params: { message: "feat: construct {subject} (agent dev loop)" }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
| Key | Notes |
|
|
152
|
+
| --- | --- |
|
|
153
|
+
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call); actions are registered host-side functions. |
|
|
154
|
+
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
155
|
+
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
156
|
+
| `env:` / `setup:` (command steps) | Declared env inside the sandbox (fixed safe base + declared vars only); `setup:` provisions the sandbox (network ON, stamp-cached, `--refresh` forces). |
|
|
157
|
+
| `isolation:` (optional) | Configures the sandbox — image override, resource caps, `allow_git_mutations`. It can never disable it. |
|
|
158
|
+
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` — artifacts and checkpoints live outside the sandbox mount. |
|
|
159
|
+
|
|
160
|
+
## Development environment
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
git clone git@github.com:lukedawilson/agentic-dev-loop.git
|
|
164
|
+
cd agentic-dev-loop
|
|
165
|
+
./setup-venv.sh # idempotent .venv setup (never touches bare pip)
|
|
166
|
+
# create .env with your LLM key (loaded by trigger-agentic-dev-loop.sh, never committed):
|
|
167
|
+
# echo 'OPENAI_API_KEY=...' > .env
|
|
168
|
+
python -m pytest tests/ -q # fast suite
|
|
169
|
+
python -m pytest tests/ -q -m "not docker" # skip engine-dependent tests
|
|
170
|
+
./trigger-agentic-dev-loop.sh --dry-run # run the shipped pipeline against this repo
|
|
171
|
+
./run-demo.sh # viz demo — scripted run replay, no pipeline needed
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- `setup-venv.sh` is safe to re-run and picks the right interpreter even on
|
|
175
|
+
machines with exotic Python installations.
|
|
176
|
+
- Docker-marked integration tests need a local daemon; CI runs them on Linux.
|
|
177
|
+
- The shipped pipeline (`agentic-dev-loop/pipeline.yaml`) **dogfoods the tool on
|
|
178
|
+
its own repo** — dev → test → review → qa → commit, with the engine's own
|
|
179
|
+
three agent definitions in `sdk_agents/`.
|
|
180
|
+
- CI: `ci.yml` (unit + integration suite, ubuntu + windows),
|
|
181
|
+
`publish-image.yml` (multi-arch dev image, amd64 + arm64), `release.yml`
|
|
182
|
+
(PyPI publishing).
|
|
183
|
+
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
- **Read [AGENTS.md](AGENTS.md) first** — it is the instruction set for both
|
|
187
|
+
human and agent contributors (default mode: the user drives, ask before
|
|
188
|
+
coding, TDD, no silent fallbacks).
|
|
189
|
+
- Development is structured by **AI-DLC** — the lifecycle workflow in
|
|
190
|
+
`.aidlc/aidlc-rules/aws-aidlc-rules/` with working docs (plans, units, state)
|
|
191
|
+
in `aidlc-docs/`. The bundled document loader autodetects the current unit
|
|
192
|
+
from `aidlc-state.md` via `--aidlc-unit`, or load any plan with `--plan`.
|
|
193
|
+
- OpenCode commands over the workflow: `/unit-status`, `/epic-status`,
|
|
194
|
+
`/ticket-details`, `/update-subtask`, `/apply-aidlc-patches`, and more.
|
|
195
|
+
- Working extension samples (actions, tools, document loaders) ship as
|
|
196
|
+
`tests/sample_ext.py`.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
Built on [OpenHands SDK](https://github.com/OpenHands/software-agent-sdk) and
|
|
201
|
+
[LangGraph](https://github.com/langchain-ai/langgraph).
|
|
@@ -282,12 +282,14 @@ def host_user_flag() -> str | None:
|
|
|
282
282
|
|
|
283
283
|
def docker_create_payload(*, run_id: str, image: str, profile: EffectiveProfile,
|
|
284
284
|
workspace: str) -> list[str]:
|
|
285
|
-
"""Exact ``docker
|
|
285
|
+
"""Exact ``docker run -d --rm`` argv for the execution container (§3.3, KD-8).
|
|
286
286
|
Single mount {workspace} (Q1); --network none, --read-only, --cap-drop ALL;
|
|
287
287
|
--user host uid:gid (host-owned bind mounts, AE-SBX-009 root cause) with
|
|
288
|
-
HOME=/tmp (the rootfs is read-only; /tmp is a 1777 tmpfs).
|
|
288
|
+
HOME=/tmp (the rootfs is read-only; /tmp is a 1777 tmpfs). ``--rm`` lets the
|
|
289
|
+
daemon auto-remove the container the instant it exits, however it exits
|
|
290
|
+
(D1) — a SIGKILLed CLI never reaches close()."""
|
|
289
291
|
payload = [
|
|
290
|
-
"docker", "
|
|
292
|
+
"docker", "run", "--detach", "--rm", "--name", f"{run_id}-exec",
|
|
291
293
|
"--network", "none", "--read-only", "--cap-drop", "ALL",
|
|
292
294
|
"--memory", profile.memory, "--cpus", str(profile.cpus),
|
|
293
295
|
"--tmpfs", "/tmp", "--tmpfs", "/root",
|
|
@@ -452,20 +454,17 @@ class DockerBackend(Sandbox):
|
|
|
452
454
|
self._verify_digest()
|
|
453
455
|
|
|
454
456
|
def create_execution_container(self) -> None:
|
|
455
|
-
"""Eager create + start (Q2) — never
|
|
457
|
+
"""Eager create + start via ``docker run -d --rm`` (Q2, D1) — never
|
|
458
|
+
lazy; ``run -d`` is create + start in one call, so there is no
|
|
459
|
+
separate ``docker start``."""
|
|
456
460
|
payload = docker_create_payload(
|
|
457
461
|
run_id=self._run_id, image=self._image, profile=self._profile,
|
|
458
462
|
workspace=self._workspace)
|
|
459
|
-
|
|
460
|
-
if
|
|
461
|
-
raise SandboxError(
|
|
462
|
-
f"AE-SBX-010: container create failed: "
|
|
463
|
-
f"{create.stderr.strip() or create.stdout.strip()}")
|
|
464
|
-
start = self._runner.run(["docker", "start", self._container], timeout=60)
|
|
465
|
-
if start.exit_code != 0:
|
|
463
|
+
res = self._runner.run(payload, timeout=60)
|
|
464
|
+
if res.exit_code != 0:
|
|
466
465
|
raise SandboxError(
|
|
467
|
-
f"AE-SBX-010: container
|
|
468
|
-
f"{
|
|
466
|
+
f"AE-SBX-010: container run failed: "
|
|
467
|
+
f"{res.stderr.strip() or res.stdout.strip()}")
|
|
469
468
|
self._started = True
|
|
470
469
|
|
|
471
470
|
def provision(self, setup_cmd: str, stamp_key: str, stamp_dir: str,
|
|
@@ -513,10 +512,14 @@ class DockerBackend(Sandbox):
|
|
|
513
512
|
return result
|
|
514
513
|
|
|
515
514
|
def close(self) -> None:
|
|
516
|
-
"""Best-effort stop + rm; idempotent (BR-8.3).
|
|
515
|
+
"""Best-effort stop + rm; idempotent (BR-8.3). With ``--rm`` the
|
|
516
|
+
daemon may already have auto-removed the container by the time close()
|
|
517
|
+
runs, so a nonzero stop/rm exit code ("no such container") is treated
|
|
518
|
+
as success, never raised (D2)."""
|
|
517
519
|
if not self._started:
|
|
518
520
|
return
|
|
519
521
|
self._runner.run(["docker", "stop", "-t", "5", self._container], timeout=60)
|
|
522
|
+
# rm -f returns rc != 0 when --rm already removed it — ignore, never raise.
|
|
520
523
|
self._runner.run(["docker", "rm", "-f", self._container], timeout=60)
|
|
521
524
|
self._started = False
|
|
522
525
|
|
|
@@ -639,7 +642,7 @@ def clean_stamps(state_dir: Path) -> int:
|
|
|
639
642
|
|
|
640
643
|
|
|
641
644
|
# ---------------------------------------------------------------------------
|
|
642
|
-
# Eager startup (Q2) — probe → image →
|
|
645
|
+
# Eager startup (Q2) — probe → image → run -d --rm → provision all setups
|
|
643
646
|
# ---------------------------------------------------------------------------
|
|
644
647
|
|
|
645
648
|
_LOCKFILE_NAMES = ("package-lock.json", "requirements.txt")
|