agentic-dev-loop 1.0.0a23__tar.gz → 1.0.0a25__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.
- {agentic_dev_loop-1.0.0a23/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a25}/PKG-INFO +137 -3
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/README.md +136 -2
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/core/config.py +36 -1
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/agents.py +17 -2
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/isolation.py +283 -12
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/stages.py +6 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/sync_back.py +72 -8
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25/src/agentic_dev_loop.egg-info}/PKG-INFO +137 -3
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop.egg-info/SOURCES.txt +1 -0
- agentic_dev_loop-1.0.0a25/tests/test_029_merge_on_conflict.py +97 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_agents.py +26 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_isolation.py +431 -10
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_isolation_integration.py +36 -3
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_sdk_compat.py +18 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_stages.py +28 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_sync_back.py +127 -1
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/graph.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/tests/test_viz.py +0 -0
{agentic_dev_loop-1.0.0a23/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a25}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0a25
|
|
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
|
|
@@ -79,8 +79,10 @@ flowchart LR
|
|
|
79
79
|
|
|
80
80
|
The fixed policies *are* the product: fail fast on missing config/secrets, no
|
|
81
81
|
silent fallbacks, verdicts only from artifacts, bounded retries, per-attempt
|
|
82
|
-
artifact hygiene, a single clean-worktree-gated commit point,
|
|
83
|
-
|
|
82
|
+
artifact hygiene, a single clean-worktree-gated commit point, no default LLM
|
|
83
|
+
timeout (agent runs are long), and fail fast on a broken sandbox - daemon-side
|
|
84
|
+
exec failures are classified and stop the run without an agent retry loop,
|
|
85
|
+
while a startup reaper reclaims containers leaked by a crashed run.
|
|
84
86
|
|
|
85
87
|
## Installation
|
|
86
88
|
|
|
@@ -221,6 +223,7 @@ steps:
|
|
|
221
223
|
| `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). |
|
|
222
224
|
| `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. |
|
|
223
225
|
| `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). |
|
|
226
|
+
| `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). |
|
|
224
227
|
|
|
225
228
|
### Secrets (`secrets:`) - refs only, never values
|
|
226
229
|
|
|
@@ -305,6 +308,137 @@ steps:
|
|
|
305
308
|
everywhere. Reserve it for stages that need it (QA, integration). The
|
|
306
309
|
residual risks are documented in [docs/setup.md](docs/setup.md).
|
|
307
310
|
|
|
311
|
+
### Sync-back conflicts (host edits during a run)
|
|
312
|
+
|
|
313
|
+
Only declared-`rw` paths sync back to the real repo after a step. If the host
|
|
314
|
+
also edits a declared-`rw` path while the step is running, sync-back prints
|
|
315
|
+
`[isolation] sync-back conflict: ...` and raises `AE-SBX-016`; the host's
|
|
316
|
+
version wins, the baseline is re-snapshotted, and the next step (even
|
|
317
|
+
`echo hello`) runs clean instead of re-raising forever. For append-style logs
|
|
318
|
+
edited by both sides (an audit log, say), declare them in the top-level
|
|
319
|
+
`merge_on_conflict:` list: those paths are union-merged - the host keeps its
|
|
320
|
+
lines, the sandbox's new lines are appended in order, both sides converge, and
|
|
321
|
+
the run continues with `[isolation] merged concurrent edits: ...` on the
|
|
322
|
+
console.
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
## Authoring a loop for your own repo
|
|
326
|
+
|
|
327
|
+
A consumer repo needs two things written by hand - a pipeline file and the
|
|
328
|
+
agent files it names - both committed before the first run (the host-boundary
|
|
329
|
+
check refuses dirty tooling, and the `commit` step never commits it). Two
|
|
330
|
+
reference layouts ship today: this repo dogfoods the engine on itself
|
|
331
|
+
(python + pytest, no frontend, CLI-only qa), and the consumer repo carries
|
|
332
|
+
the wider shape (.NET + npm frontend + a networked browser-probing qa step).
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
<repo>/agentic-dev-loop/pipeline.yaml
|
|
336
|
+
<repo>/agentic-dev-loop/sdk_agents/dev.agent.md
|
|
337
|
+
<repo>/agentic-dev-loop/sdk_agents/review.agent.md
|
|
338
|
+
<repo>/agentic-dev-loop/sdk_agents/qa.agent.md
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### The agent files
|
|
342
|
+
|
|
343
|
+
Each `sdk_agents/<name>.agent.md` is a markdown prompt plus YAML frontmatter:
|
|
344
|
+
`name` matches a step's `agent:` value; `tools` lists the tool names the step
|
|
345
|
+
may use (`terminal` always; a repo can add more via extensions, e.g.
|
|
346
|
+
`figma`).
|
|
347
|
+
|
|
348
|
+
```markdown
|
|
349
|
+
---
|
|
350
|
+
name: review # matches a step's `agent:` value
|
|
351
|
+
tools: [terminal]
|
|
352
|
+
---
|
|
353
|
+
# Review Agent
|
|
354
|
+
## WORKFLOW # numbered steps
|
|
355
|
+
## RULES # absolute never/always rules
|
|
356
|
+
## SANDBOX NOTES # the sandbox truth for THIS step
|
|
357
|
+
## OUTPUT # the verdict contract
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Three roles, three contracts:
|
|
361
|
+
|
|
362
|
+
| Role | Verdict contract | Typical sandbox |
|
|
363
|
+
| --- | --- | --- |
|
|
364
|
+
| dev | none - the final response is captured as the `produces:` summary | rw mount, offline |
|
|
365
|
+
| review | `VERDICT: APPROVED` / `VERDICT: NEEDS CHANGES` on the first content line | ro mount, offline |
|
|
366
|
+
| 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) |
|
|
367
|
+
|
|
368
|
+
The conventions that make a good agent file - each one below exists because
|
|
369
|
+
an agent burned budget learning it mid-run:
|
|
370
|
+
|
|
371
|
+
- **State the sandbox truth in the prompt.** Every agent file carries a
|
|
372
|
+
`SANDBOX NOTES` section naming exactly what its step allows: mounts ro or
|
|
373
|
+
rw, network on or off, and which gates have already run. Never make the
|
|
374
|
+
agent discover it.
|
|
375
|
+
- **Gates are the source of truth - cite the record, never re-run.** A
|
|
376
|
+
command the test gate, the editor-tests gate, or the dev summary reports
|
|
377
|
+
as run is coverage evidence, cited as `covered by <test>` or the gate
|
|
378
|
+
record. Review and qa never re-run builds or suites; qa maps every
|
|
379
|
+
acceptance criterion to existing coverage and probes only the remainder.
|
|
380
|
+
- **dev enumerates its impossible set and stops at it.** The dev prompt
|
|
381
|
+
says: stop the moment your next step needs a command this sandbox cannot
|
|
382
|
+
run, and lists the set (package-manager fetches after a new pin, npm or
|
|
383
|
+
vitest offline, playwright downloads, database-category tests), one
|
|
384
|
+
attempt per command. That keeps the productive edit/build/test loop and
|
|
385
|
+
kills doomed retries. The set is toolchain-specific - write yours; this
|
|
386
|
+
repo's is a single bullet (no package managers), the consumer repo's is
|
|
387
|
+
four.
|
|
388
|
+
- **Failure output returns as *Previous attempt feedback*.** Steps route
|
|
389
|
+
`on_fail: { goto: dev, retry: true }`, so gate failure output lands in
|
|
390
|
+
the dev agent's next context; the dev prompt makes every listed issue a
|
|
391
|
+
required fix.
|
|
392
|
+
- **No git mutations, ever.** The `commit` step is the single commit point.
|
|
393
|
+
- **`max_iterations` per role.** qa is bounded probing (60 in both
|
|
394
|
+
reference repos); dev is open-ended construction and stays uncapped
|
|
395
|
+
(the engine default).
|
|
396
|
+
|
|
397
|
+
### The pipeline file
|
|
398
|
+
|
|
399
|
+
The full field reference is above; this is the stage lineup and what to
|
|
400
|
+
adapt per repo.
|
|
401
|
+
|
|
402
|
+
| Stage | Kind | Typical shape | Why |
|
|
403
|
+
| --- | --- | --- | --- |
|
|
404
|
+
| dev | agent | rw, offline | does the work; edits are untracked |
|
|
405
|
+
| restore | command | network + `presets: [package-registries]` | fetches what dev pinned (`deps_policy: open`) or the locked install; also the audit escalation point |
|
|
406
|
+
| test | command | offline is fine (warm cache) | exit code is the verdict; the source of truth |
|
|
407
|
+
| 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 |
|
|
408
|
+
| conformance | command | network + `presets: [package-registries]` | repo-specific fitness checks (the consumer repo: jscpd + DDD rules) |
|
|
409
|
+
| review | agent | ro, offline | static conformance verdict |
|
|
410
|
+
| qa | agent | per repo (see table above) | live-behaviour probes; `max_iterations: 60`; inject secrets only if the probes need them |
|
|
411
|
+
| commit | action | host-side | the single commit point |
|
|
412
|
+
|
|
413
|
+
Adaptation checklist for a new repo:
|
|
414
|
+
|
|
415
|
+
1. `llm:` - model, `base_url`, `api_key_env`.
|
|
416
|
+
2. `isolation:` - toolchain image override (the consumer repo pins a
|
|
417
|
+
toolchain image by digest) and `seccomp_profile: bundled:chromium-userns`
|
|
418
|
+
only if a stage runs Chromium.
|
|
419
|
+
3. `caches:` - toolchain cache dirs as rw bind-mounts shared across runs
|
|
420
|
+
(`.nuget-packages`, `qa/node_modules`, `qa/.browsers`,
|
|
421
|
+
`frontend-editor/node_modules`); caches are never synced back.
|
|
422
|
+
4. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
|
|
423
|
+
5. `secrets:` - refs only, resolved from host env; each step opts in to the
|
|
424
|
+
refs it needs.
|
|
425
|
+
6. `egress:` - `presets: [package-registries]` for fetch steps;
|
|
426
|
+
`class: browser` (broad-but-logged) for browser-tier qa only.
|
|
427
|
+
7. `deps_policy:` and `lockfiles:` - see
|
|
428
|
+
[Dependency policy](#dependency-policy-locked-by-default).
|
|
429
|
+
8. The dev prompt's impossible set - write it for your toolchain.
|
|
430
|
+
9. `additional_files:` - the repo's `AGENTS.md`, coding standards, design
|
|
431
|
+
docs: shared context for every agent step.
|
|
432
|
+
|
|
433
|
+
Validate before the first run - dry-run loads and prints the resolved plan
|
|
434
|
+
with no LLM, no Docker, no state writes:
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
agentic-dev-loop agentic-dev-loop/pipeline.yaml --dry-run
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
Then commit the tooling and run for real.
|
|
441
|
+
|
|
308
442
|
## Dependency policy (locked by default)
|
|
309
443
|
|
|
310
444
|
The supply-chain dial is a pipeline-level `deps_policy:` field, **locked by
|
|
@@ -37,8 +37,10 @@ flowchart LR
|
|
|
37
37
|
|
|
38
38
|
The fixed policies *are* the product: fail fast on missing config/secrets, no
|
|
39
39
|
silent fallbacks, verdicts only from artifacts, bounded retries, per-attempt
|
|
40
|
-
artifact hygiene, a single clean-worktree-gated commit point,
|
|
41
|
-
|
|
40
|
+
artifact hygiene, a single clean-worktree-gated commit point, no default LLM
|
|
41
|
+
timeout (agent runs are long), and fail fast on a broken sandbox - daemon-side
|
|
42
|
+
exec failures are classified and stop the run without an agent retry loop,
|
|
43
|
+
while a startup reaper reclaims containers leaked by a crashed run.
|
|
42
44
|
|
|
43
45
|
## Installation
|
|
44
46
|
|
|
@@ -179,6 +181,7 @@ steps:
|
|
|
179
181
|
| `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). |
|
|
180
182
|
| `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. |
|
|
181
183
|
| `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). |
|
|
184
|
+
| `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). |
|
|
182
185
|
|
|
183
186
|
### Secrets (`secrets:`) - refs only, never values
|
|
184
187
|
|
|
@@ -263,6 +266,137 @@ steps:
|
|
|
263
266
|
everywhere. Reserve it for stages that need it (QA, integration). The
|
|
264
267
|
residual risks are documented in [docs/setup.md](docs/setup.md).
|
|
265
268
|
|
|
269
|
+
### Sync-back conflicts (host edits during a run)
|
|
270
|
+
|
|
271
|
+
Only declared-`rw` paths sync back to the real repo after a step. If the host
|
|
272
|
+
also edits a declared-`rw` path while the step is running, sync-back prints
|
|
273
|
+
`[isolation] sync-back conflict: ...` and raises `AE-SBX-016`; the host's
|
|
274
|
+
version wins, the baseline is re-snapshotted, and the next step (even
|
|
275
|
+
`echo hello`) runs clean instead of re-raising forever. For append-style logs
|
|
276
|
+
edited by both sides (an audit log, say), declare them in the top-level
|
|
277
|
+
`merge_on_conflict:` list: those paths are union-merged - the host keeps its
|
|
278
|
+
lines, the sandbox's new lines are appended in order, both sides converge, and
|
|
279
|
+
the run continues with `[isolation] merged concurrent edits: ...` on the
|
|
280
|
+
console.
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
## Authoring a loop for your own repo
|
|
284
|
+
|
|
285
|
+
A consumer repo needs two things written by hand - a pipeline file and the
|
|
286
|
+
agent files it names - both committed before the first run (the host-boundary
|
|
287
|
+
check refuses dirty tooling, and the `commit` step never commits it). Two
|
|
288
|
+
reference layouts ship today: this repo dogfoods the engine on itself
|
|
289
|
+
(python + pytest, no frontend, CLI-only qa), and the consumer repo carries
|
|
290
|
+
the wider shape (.NET + npm frontend + a networked browser-probing qa step).
|
|
291
|
+
|
|
292
|
+
```
|
|
293
|
+
<repo>/agentic-dev-loop/pipeline.yaml
|
|
294
|
+
<repo>/agentic-dev-loop/sdk_agents/dev.agent.md
|
|
295
|
+
<repo>/agentic-dev-loop/sdk_agents/review.agent.md
|
|
296
|
+
<repo>/agentic-dev-loop/sdk_agents/qa.agent.md
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### The agent files
|
|
300
|
+
|
|
301
|
+
Each `sdk_agents/<name>.agent.md` is a markdown prompt plus YAML frontmatter:
|
|
302
|
+
`name` matches a step's `agent:` value; `tools` lists the tool names the step
|
|
303
|
+
may use (`terminal` always; a repo can add more via extensions, e.g.
|
|
304
|
+
`figma`).
|
|
305
|
+
|
|
306
|
+
```markdown
|
|
307
|
+
---
|
|
308
|
+
name: review # matches a step's `agent:` value
|
|
309
|
+
tools: [terminal]
|
|
310
|
+
---
|
|
311
|
+
# Review Agent
|
|
312
|
+
## WORKFLOW # numbered steps
|
|
313
|
+
## RULES # absolute never/always rules
|
|
314
|
+
## SANDBOX NOTES # the sandbox truth for THIS step
|
|
315
|
+
## OUTPUT # the verdict contract
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Three roles, three contracts:
|
|
319
|
+
|
|
320
|
+
| Role | Verdict contract | Typical sandbox |
|
|
321
|
+
| --- | --- | --- |
|
|
322
|
+
| dev | none - the final response is captured as the `produces:` summary | rw mount, offline |
|
|
323
|
+
| review | `VERDICT: APPROVED` / `VERDICT: NEEDS CHANGES` on the first content line | ro mount, offline |
|
|
324
|
+
| 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) |
|
|
325
|
+
|
|
326
|
+
The conventions that make a good agent file - each one below exists because
|
|
327
|
+
an agent burned budget learning it mid-run:
|
|
328
|
+
|
|
329
|
+
- **State the sandbox truth in the prompt.** Every agent file carries a
|
|
330
|
+
`SANDBOX NOTES` section naming exactly what its step allows: mounts ro or
|
|
331
|
+
rw, network on or off, and which gates have already run. Never make the
|
|
332
|
+
agent discover it.
|
|
333
|
+
- **Gates are the source of truth - cite the record, never re-run.** A
|
|
334
|
+
command the test gate, the editor-tests gate, or the dev summary reports
|
|
335
|
+
as run is coverage evidence, cited as `covered by <test>` or the gate
|
|
336
|
+
record. Review and qa never re-run builds or suites; qa maps every
|
|
337
|
+
acceptance criterion to existing coverage and probes only the remainder.
|
|
338
|
+
- **dev enumerates its impossible set and stops at it.** The dev prompt
|
|
339
|
+
says: stop the moment your next step needs a command this sandbox cannot
|
|
340
|
+
run, and lists the set (package-manager fetches after a new pin, npm or
|
|
341
|
+
vitest offline, playwright downloads, database-category tests), one
|
|
342
|
+
attempt per command. That keeps the productive edit/build/test loop and
|
|
343
|
+
kills doomed retries. The set is toolchain-specific - write yours; this
|
|
344
|
+
repo's is a single bullet (no package managers), the consumer repo's is
|
|
345
|
+
four.
|
|
346
|
+
- **Failure output returns as *Previous attempt feedback*.** Steps route
|
|
347
|
+
`on_fail: { goto: dev, retry: true }`, so gate failure output lands in
|
|
348
|
+
the dev agent's next context; the dev prompt makes every listed issue a
|
|
349
|
+
required fix.
|
|
350
|
+
- **No git mutations, ever.** The `commit` step is the single commit point.
|
|
351
|
+
- **`max_iterations` per role.** qa is bounded probing (60 in both
|
|
352
|
+
reference repos); dev is open-ended construction and stays uncapped
|
|
353
|
+
(the engine default).
|
|
354
|
+
|
|
355
|
+
### The pipeline file
|
|
356
|
+
|
|
357
|
+
The full field reference is above; this is the stage lineup and what to
|
|
358
|
+
adapt per repo.
|
|
359
|
+
|
|
360
|
+
| Stage | Kind | Typical shape | Why |
|
|
361
|
+
| --- | --- | --- | --- |
|
|
362
|
+
| dev | agent | rw, offline | does the work; edits are untracked |
|
|
363
|
+
| restore | command | network + `presets: [package-registries]` | fetches what dev pinned (`deps_policy: open`) or the locked install; also the audit escalation point |
|
|
364
|
+
| test | command | offline is fine (warm cache) | exit code is the verdict; the source of truth |
|
|
365
|
+
| 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 |
|
|
366
|
+
| conformance | command | network + `presets: [package-registries]` | repo-specific fitness checks (the consumer repo: jscpd + DDD rules) |
|
|
367
|
+
| review | agent | ro, offline | static conformance verdict |
|
|
368
|
+
| qa | agent | per repo (see table above) | live-behaviour probes; `max_iterations: 60`; inject secrets only if the probes need them |
|
|
369
|
+
| commit | action | host-side | the single commit point |
|
|
370
|
+
|
|
371
|
+
Adaptation checklist for a new repo:
|
|
372
|
+
|
|
373
|
+
1. `llm:` - model, `base_url`, `api_key_env`.
|
|
374
|
+
2. `isolation:` - toolchain image override (the consumer repo pins a
|
|
375
|
+
toolchain image by digest) and `seccomp_profile: bundled:chromium-userns`
|
|
376
|
+
only if a stage runs Chromium.
|
|
377
|
+
3. `caches:` - toolchain cache dirs as rw bind-mounts shared across runs
|
|
378
|
+
(`.nuget-packages`, `qa/node_modules`, `qa/.browsers`,
|
|
379
|
+
`frontend-editor/node_modules`); caches are never synced back.
|
|
380
|
+
4. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
|
|
381
|
+
5. `secrets:` - refs only, resolved from host env; each step opts in to the
|
|
382
|
+
refs it needs.
|
|
383
|
+
6. `egress:` - `presets: [package-registries]` for fetch steps;
|
|
384
|
+
`class: browser` (broad-but-logged) for browser-tier qa only.
|
|
385
|
+
7. `deps_policy:` and `lockfiles:` - see
|
|
386
|
+
[Dependency policy](#dependency-policy-locked-by-default).
|
|
387
|
+
8. The dev prompt's impossible set - write it for your toolchain.
|
|
388
|
+
9. `additional_files:` - the repo's `AGENTS.md`, coding standards, design
|
|
389
|
+
docs: shared context for every agent step.
|
|
390
|
+
|
|
391
|
+
Validate before the first run - dry-run loads and prints the resolved plan
|
|
392
|
+
with no LLM, no Docker, no state writes:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
agentic-dev-loop agentic-dev-loop/pipeline.yaml --dry-run
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Then commit the tooling and run for real.
|
|
399
|
+
|
|
266
400
|
## Dependency policy (locked by default)
|
|
267
401
|
|
|
268
402
|
The supply-chain dial is a pipeline-level `deps_policy:` field, **locked by
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentic-dev-loop"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0a25"
|
|
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"
|
|
@@ -446,6 +446,10 @@ class PipelineConfig(BaseModel):
|
|
|
446
446
|
# "no sync-back excludes".
|
|
447
447
|
caches: list[str] = []
|
|
448
448
|
sync_excludes: list[str] = []
|
|
449
|
+
# 029: append-style logs that sync-back union-merges on a concurrent
|
|
450
|
+
# edit instead of raising. Same declared-path vocabulary as
|
|
451
|
+
# ``sync_excludes:``.
|
|
452
|
+
merge_on_conflict: list[str] = []
|
|
449
453
|
|
|
450
454
|
@field_validator("deps_policy")
|
|
451
455
|
@classmethod
|
|
@@ -460,7 +464,7 @@ class PipelineConfig(BaseModel):
|
|
|
460
464
|
def _coerce_lockfiles(cls, v: Any) -> Any:
|
|
461
465
|
return [v] if isinstance(v, str) else v
|
|
462
466
|
|
|
463
|
-
@field_validator("caches", "sync_excludes", mode="before")
|
|
467
|
+
@field_validator("caches", "sync_excludes", "merge_on_conflict", mode="before")
|
|
464
468
|
@classmethod
|
|
465
469
|
def _coerce_declared_paths(cls, v: Any) -> Any:
|
|
466
470
|
return [v] if isinstance(v, str) else v
|
|
@@ -475,6 +479,11 @@ class PipelineConfig(BaseModel):
|
|
|
475
479
|
def _valid_sync_excludes(cls, v: list[str]) -> list[str]:
|
|
476
480
|
return [_validate_declared_path(c, allow_wildcards=True) for c in v]
|
|
477
481
|
|
|
482
|
+
@field_validator("merge_on_conflict")
|
|
483
|
+
@classmethod
|
|
484
|
+
def _valid_merge_on_conflict(cls, v: list[str]) -> list[str]:
|
|
485
|
+
return [_validate_declared_path(m, allow_wildcards=True) for m in v]
|
|
486
|
+
|
|
478
487
|
@field_validator("secrets", mode="before")
|
|
479
488
|
@classmethod
|
|
480
489
|
def _reject_value_shaped_refs(cls, v: Any) -> Any:
|
|
@@ -579,6 +588,32 @@ class PipelineConfig(BaseModel):
|
|
|
579
588
|
f"{('/'.join(cp))!r}")
|
|
580
589
|
return self
|
|
581
590
|
|
|
591
|
+
@model_validator(mode="after")
|
|
592
|
+
def _merge_avoid_excludes_and_caches(self) -> "PipelineConfig":
|
|
593
|
+
"""029: a literal merge_on_conflict entry equal to or under a literal
|
|
594
|
+
sync_excludes entry or a literal caches entry is redundant - excluded
|
|
595
|
+
and cached paths never sync, so they can never conflict. Wildcard
|
|
596
|
+
merge entries are skipped (their overlap is fuzzy, 028 parity)."""
|
|
597
|
+
cache_parts = [relative_parts(c) for c in self.caches if relative_parts(c)]
|
|
598
|
+
exclude_parts = [relative_parts(e) for e in self.sync_excludes
|
|
599
|
+
if not _has_wildcards(e) and relative_parts(e)]
|
|
600
|
+
for merge in self.merge_on_conflict:
|
|
601
|
+
if _has_wildcards(merge):
|
|
602
|
+
continue
|
|
603
|
+
mp = relative_parts(merge)
|
|
604
|
+
for cp in cache_parts:
|
|
605
|
+
if mp[:len(cp)] == cp:
|
|
606
|
+
raise ValueError(
|
|
607
|
+
f"merge_on_conflict entry {merge!r} is redundant: it is "
|
|
608
|
+
f"already excluded as declared cache {('/'.join(cp))!r}")
|
|
609
|
+
for ep in exclude_parts:
|
|
610
|
+
if mp[:len(ep)] == ep:
|
|
611
|
+
raise ValueError(
|
|
612
|
+
f"merge_on_conflict entry {merge!r} is redundant: it is "
|
|
613
|
+
f"already excluded by sync_excludes entry "
|
|
614
|
+
f"{('/'.join(ep))!r}")
|
|
615
|
+
return self
|
|
616
|
+
|
|
582
617
|
@model_validator(mode="after")
|
|
583
618
|
def _sandbox_venv_not_declared_cache(self) -> "PipelineConfig":
|
|
584
619
|
"""028: the sandbox venv is engine infrastructure, never author data.
|
{agentic_dev_loop-1.0.0a23 → agentic_dev_loop-1.0.0a25}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
@@ -15,7 +15,8 @@ from openhands.tools.file_editor import FileEditorTool # noqa: F401 - registers
|
|
|
15
15
|
from openhands.tools.task_tracker import TaskTrackerTool # noqa: F401 - registers "task_tracker"
|
|
16
16
|
from openhands.tools.terminal import TerminalTool # noqa: F401 - registers "terminal"
|
|
17
17
|
|
|
18
|
-
from .isolation import SandboxExecutionRequest, command_argv,
|
|
18
|
+
from .isolation import (SandboxError, SandboxExecutionRequest, command_argv,
|
|
19
|
+
resolve_sandbox_env)
|
|
19
20
|
|
|
20
21
|
if TYPE_CHECKING:
|
|
21
22
|
from openhands.sdk.agent.agent import Agent
|
|
@@ -96,7 +97,21 @@ class SandboxTerminalExecutor(ToolExecutor):
|
|
|
96
97
|
env=self._env, timeout=action.timeout,
|
|
97
98
|
pipeline=self._pipeline, step=self._step, network=self._network,
|
|
98
99
|
mount_set=self._mount_set, egress_policy=self._egress_policy)
|
|
99
|
-
|
|
100
|
+
try:
|
|
101
|
+
result = self._sandbox.run(request)
|
|
102
|
+
except SandboxError as exc:
|
|
103
|
+
# 030, D3: a sticky infra failure proves the exec path is dead, so
|
|
104
|
+
# tell the SDK run loop to break this step instead of flapping to
|
|
105
|
+
# max_iterations. See COMPAT-SHIMS.md for the reliance + canary.
|
|
106
|
+
if (conversation is not None
|
|
107
|
+
and exc is self._sandbox.infra_error):
|
|
108
|
+
from openhands.sdk.conversation.state import (
|
|
109
|
+
ConversationExecutionStatus,
|
|
110
|
+
)
|
|
111
|
+
conversation.state.execution_status = (
|
|
112
|
+
ConversationExecutionStatus.STUCK
|
|
113
|
+
)
|
|
114
|
+
raise
|
|
100
115
|
output = result.stdout + result.stderr
|
|
101
116
|
return TerminalObservation.from_text(
|
|
102
117
|
output, is_error=not result.succeeded and not result.is_timeout,
|