agentic-dev-loop 1.0.0a30__tar.gz → 1.0.0a32__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.0a30/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a32}/PKG-INFO +31 -5
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/README.md +30 -4
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/pyproject.toml +2 -1
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/config.py +16 -0
- agentic_dev_loop-1.0.0a32/src/agentic_dev_loop/engine/agent_error_guide.md +6 -0
- agentic_dev_loop-1.0.0a32/src/agentic_dev_loop/engine/agent_error_guide.py +26 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/stages.py +99 -45
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32/src/agentic_dev_loop.egg-info}/PKG-INFO +31 -5
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/SOURCES.txt +4 -0
- agentic_dev_loop-1.0.0a32/tests/test_035_finalize_nudge.py +211 -0
- agentic_dev_loop-1.0.0a32/tests/test_agent_error_guide.py +184 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/graph.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_032_stop_when_green.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/tests/test_viz.py +0 -0
{agentic_dev_loop-1.0.0a30/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a32}/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.0a32
|
|
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
|
|
@@ -224,6 +224,8 @@ steps:
|
|
|
224
224
|
agent: qa
|
|
225
225
|
artifact: qa-report.md
|
|
226
226
|
verdicts: { pass: PASS, fail: FAIL }
|
|
227
|
+
max_iterations: 100
|
|
228
|
+
finalize_nudge_turns: 10
|
|
227
229
|
on_pass: commit
|
|
228
230
|
on_fail: { goto: dev, retry: true }
|
|
229
231
|
|
|
@@ -239,6 +241,7 @@ steps:
|
|
|
239
241
|
| `brief_inputs:` | Optional agent-step input spec: `{ include: [<paths/globs>], env_facts: [<strings>] }`. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as that step's task message, and regenerates the step only when those inputs change. A produces-only agent step such as `brief` is the documented pattern for per-run context digestion. |
|
|
240
242
|
| `stop_when:` | Optional agent-step termination: `{ green_command: <argv regex>, post_green_turns: <int, default 2> }`. When the agent's most recent terminal observation matching `green_command` exits 0 and no workspace writes have happened since, the engine injects one nudge turn, allows `post_green_turns` further turns, then closes the conversation. Absent = run to finish. |
|
|
241
243
|
| `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. |
|
|
244
|
+
| `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). |
|
|
242
245
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
243
246
|
| `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). |
|
|
244
247
|
| `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). |
|
|
@@ -421,9 +424,10 @@ an agent burned budget learning it mid-run:
|
|
|
421
424
|
the dev agent's next context; the dev prompt makes every listed issue a
|
|
422
425
|
required fix.
|
|
423
426
|
- **No git mutations, ever.** The `commit` step is the single commit point.
|
|
424
|
-
- **`max_iterations` per role.** qa is bounded probing (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
+
- **`max_iterations` per role.** qa is bounded probing (100 in this
|
|
428
|
+
repo's dogfood loop, with `finalize_nudge_turns: 10` so it never ends
|
|
429
|
+
silently at the cap - both provisional); dev is open-ended construction
|
|
430
|
+
and stays uncapped (the engine default).
|
|
427
431
|
|
|
428
432
|
### The pipeline file
|
|
429
433
|
|
|
@@ -438,7 +442,7 @@ adapt per repo.
|
|
|
438
442
|
| 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 |
|
|
439
443
|
| conformance | command | network + `presets: [package-registries]` | repo-specific fitness checks (the consumer repo: jscpd + DDD rules) |
|
|
440
444
|
| review | agent | ro, offline | static conformance verdict |
|
|
441
|
-
| qa | agent | per repo (see table above) | live-behaviour probes; `max_iterations:
|
|
445
|
+
| 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 |
|
|
442
446
|
| commit | action | host-side | the single commit point |
|
|
443
447
|
|
|
444
448
|
Adaptation checklist for a new repo:
|
|
@@ -541,6 +545,28 @@ dotnet restore --locked-mode # installs exactly packages.lock.json
|
|
|
541
545
|
Keep the lockfile tracked (do not gitignore it) and re-run the locked restore
|
|
542
546
|
to prove it holds from a clean cache before setting `deps_policy: locked`.
|
|
543
547
|
|
|
548
|
+
## Agent-facing error guide
|
|
549
|
+
|
|
550
|
+
The engine bundles an always-on guide for the engine error codes an agent
|
|
551
|
+
can actually observe while running terminal commands. The guide lives at
|
|
552
|
+
`src/agentic_dev_loop/engine/agent_error_guide.md`, versioned with the engine
|
|
553
|
+
code that emits the codes, and is injected into every agent step's task
|
|
554
|
+
context at assembly time. There is no opt-in and no opt-out in v1; command
|
|
555
|
+
and action steps never receive it because no agent reads them.
|
|
556
|
+
|
|
557
|
+
Each agent-visible code has exactly one table row, shaped like this:
|
|
558
|
+
|
|
559
|
+
| Code | Meaning | Correct response |
|
|
560
|
+
| --- | --- | --- |
|
|
561
|
+
| 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. |
|
|
562
|
+
|
|
563
|
+
The guide is consumer-agnostic: it explains the general mechanism and never
|
|
564
|
+
names a consumer or a consumer domain rule ID. If an ID must appear inside a
|
|
565
|
+
quoted engine message, the placeholder `<domain-rule-id>` is used. Any engine
|
|
566
|
+
change that adds or changes an agent-visible error code must update the guide
|
|
567
|
+
in the same commit; the test suite enforces the pairing by parsing the
|
|
568
|
+
agent-visible `SandboxError` raise sites against the bundled table.
|
|
569
|
+
|
|
544
570
|
## Host boundary (security model)
|
|
545
571
|
|
|
546
572
|
The orchestrator, tool layer, and the `commit` action run in the **host
|
|
@@ -182,6 +182,8 @@ steps:
|
|
|
182
182
|
agent: qa
|
|
183
183
|
artifact: qa-report.md
|
|
184
184
|
verdicts: { pass: PASS, fail: FAIL }
|
|
185
|
+
max_iterations: 100
|
|
186
|
+
finalize_nudge_turns: 10
|
|
185
187
|
on_pass: commit
|
|
186
188
|
on_fail: { goto: dev, retry: true }
|
|
187
189
|
|
|
@@ -197,6 +199,7 @@ steps:
|
|
|
197
199
|
| `brief_inputs:` | Optional agent-step input spec: `{ include: [<paths/globs>], env_facts: [<strings>] }`. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as that step's task message, and regenerates the step only when those inputs change. A produces-only agent step such as `brief` is the documented pattern for per-run context digestion. |
|
|
198
200
|
| `stop_when:` | Optional agent-step termination: `{ green_command: <argv regex>, post_green_turns: <int, default 2> }`. When the agent's most recent terminal observation matching `green_command` exits 0 and no workspace writes have happened since, the engine injects one nudge turn, allows `post_green_turns` further turns, then closes the conversation. Absent = run to finish. |
|
|
199
201
|
| `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. |
|
|
202
|
+
| `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). |
|
|
200
203
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
201
204
|
| `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). |
|
|
202
205
|
| `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). |
|
|
@@ -379,9 +382,10 @@ an agent burned budget learning it mid-run:
|
|
|
379
382
|
the dev agent's next context; the dev prompt makes every listed issue a
|
|
380
383
|
required fix.
|
|
381
384
|
- **No git mutations, ever.** The `commit` step is the single commit point.
|
|
382
|
-
- **`max_iterations` per role.** qa is bounded probing (
|
|
383
|
-
|
|
384
|
-
|
|
385
|
+
- **`max_iterations` per role.** qa is bounded probing (100 in this
|
|
386
|
+
repo's dogfood loop, with `finalize_nudge_turns: 10` so it never ends
|
|
387
|
+
silently at the cap - both provisional); dev is open-ended construction
|
|
388
|
+
and stays uncapped (the engine default).
|
|
385
389
|
|
|
386
390
|
### The pipeline file
|
|
387
391
|
|
|
@@ -396,7 +400,7 @@ adapt per repo.
|
|
|
396
400
|
| 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 |
|
|
397
401
|
| conformance | command | network + `presets: [package-registries]` | repo-specific fitness checks (the consumer repo: jscpd + DDD rules) |
|
|
398
402
|
| review | agent | ro, offline | static conformance verdict |
|
|
399
|
-
| qa | agent | per repo (see table above) | live-behaviour probes; `max_iterations:
|
|
403
|
+
| 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 |
|
|
400
404
|
| commit | action | host-side | the single commit point |
|
|
401
405
|
|
|
402
406
|
Adaptation checklist for a new repo:
|
|
@@ -499,6 +503,28 @@ dotnet restore --locked-mode # installs exactly packages.lock.json
|
|
|
499
503
|
Keep the lockfile tracked (do not gitignore it) and re-run the locked restore
|
|
500
504
|
to prove it holds from a clean cache before setting `deps_policy: locked`.
|
|
501
505
|
|
|
506
|
+
## Agent-facing error guide
|
|
507
|
+
|
|
508
|
+
The engine bundles an always-on guide for the engine error codes an agent
|
|
509
|
+
can actually observe while running terminal commands. The guide lives at
|
|
510
|
+
`src/agentic_dev_loop/engine/agent_error_guide.md`, versioned with the engine
|
|
511
|
+
code that emits the codes, and is injected into every agent step's task
|
|
512
|
+
context at assembly time. There is no opt-in and no opt-out in v1; command
|
|
513
|
+
and action steps never receive it because no agent reads them.
|
|
514
|
+
|
|
515
|
+
Each agent-visible code has exactly one table row, shaped like this:
|
|
516
|
+
|
|
517
|
+
| Code | Meaning | Correct response |
|
|
518
|
+
| --- | --- | --- |
|
|
519
|
+
| 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. |
|
|
520
|
+
|
|
521
|
+
The guide is consumer-agnostic: it explains the general mechanism and never
|
|
522
|
+
names a consumer or a consumer domain rule ID. If an ID must appear inside a
|
|
523
|
+
quoted engine message, the placeholder `<domain-rule-id>` is used. Any engine
|
|
524
|
+
change that adds or changes an agent-visible error code must update the guide
|
|
525
|
+
in the same commit; the test suite enforces the pairing by parsing the
|
|
526
|
+
agent-visible `SandboxError` raise sites against the bundled table.
|
|
527
|
+
|
|
502
528
|
## Host boundary (security model)
|
|
503
529
|
|
|
504
530
|
The orchestrator, tool layer, and the `commit` action run in the **host
|
|
@@ -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.0a32"
|
|
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"
|
|
@@ -68,6 +68,7 @@ where = ["src"]
|
|
|
68
68
|
|
|
69
69
|
[tool.setuptools.package-data]
|
|
70
70
|
agentic_dev_loop = ["py.typed"]
|
|
71
|
+
"agentic_dev_loop.engine" = ["*.md"]
|
|
71
72
|
"agentic_dev_loop.engine.seccomp" = ["*.json"]
|
|
72
73
|
|
|
73
74
|
[tool.pytest.ini_options]
|
|
@@ -387,6 +387,7 @@ class StepConfig(BaseModel):
|
|
|
387
387
|
timeout: float | None = Field(default=None, gt=0) # seconds, command steps only
|
|
388
388
|
terminal_timeout: float | None = Field(default=None, gt=0) # seconds, agent steps only (032)
|
|
389
389
|
stop_when: StopWhenSpec | None = None # agent steps only (032)
|
|
390
|
+
finalize_nudge_turns: int | None = Field(default=None, gt=0) # agent steps only (035)
|
|
390
391
|
brief_inputs: BriefInputsSpec | None = None # agent steps only (033)
|
|
391
392
|
env: dict[str, str | None] | None = None # NFR-1: null = inherit host value
|
|
392
393
|
setup: str | None = None # BR-13: command steps only
|
|
@@ -460,6 +461,21 @@ class StepConfig(BaseModel):
|
|
|
460
461
|
raise ValueError(
|
|
461
462
|
f"step {self.name!r}: 'stop_when' requires 'agent'"
|
|
462
463
|
)
|
|
464
|
+
if self.finalize_nudge_turns is not None and self.agent is None:
|
|
465
|
+
raise ValueError(
|
|
466
|
+
f"step {self.name!r}: 'finalize_nudge_turns' requires 'agent'"
|
|
467
|
+
)
|
|
468
|
+
if self.finalize_nudge_turns is not None and self.max_iterations is None:
|
|
469
|
+
raise ValueError(
|
|
470
|
+
f"step {self.name!r}: 'finalize_nudge_turns' requires "
|
|
471
|
+
"'max_iterations'"
|
|
472
|
+
)
|
|
473
|
+
if (self.finalize_nudge_turns is not None
|
|
474
|
+
and self.finalize_nudge_turns >= self.max_iterations):
|
|
475
|
+
raise ValueError(
|
|
476
|
+
f"step {self.name!r}: 'finalize_nudge_turns' must be less "
|
|
477
|
+
"than 'max_iterations'"
|
|
478
|
+
)
|
|
463
479
|
if self.brief_inputs is not None and self.agent is None:
|
|
464
480
|
raise ValueError(
|
|
465
481
|
f"step {self.name!r}: 'brief_inputs' requires 'agent'"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
| Code | Meaning | Correct response |
|
|
2
|
+
| --- | --- | --- |
|
|
3
|
+
| AE-SBX-008 | The sandbox refused a git mutation command. Only the single commit step may change git state; agents never commit, branch, tag, or otherwise mutate git. | Do not run the command or any git mutation again. Continue without touching git and leave committing to the commit step. |
|
|
4
|
+
| AE-SBX-010 | The sandbox could not create the execution container for the command, so the command did not run. | The command is not the problem: retry once; if it persists, mention the code in your final response and stop. |
|
|
5
|
+
| 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. |
|
|
6
|
+
| AE-SBX-018 | The network egress proxy for this step stopped, so the sandbox refused the command rather than run without enforcement. | The command is not the problem: do not retry it, mention the code in your final response, and stop. |
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Bundled agent-facing error guide (034).
|
|
2
|
+
|
|
3
|
+
The guide table is an engine-owned resource, versioned with the engine so the
|
|
4
|
+
codes and their guidance cannot drift. Every agent step's task context gets
|
|
5
|
+
the table injected at assembly time; command and action steps never do (no
|
|
6
|
+
agent reads them).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from importlib.resources import files
|
|
12
|
+
|
|
13
|
+
GUIDE_RESOURCE = "agent_error_guide.md"
|
|
14
|
+
GUIDE_HEADING = "Agent-facing error guide"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_guide() -> str:
|
|
18
|
+
"""The bundled guide table text."""
|
|
19
|
+
return (files("agentic_dev_loop.engine")
|
|
20
|
+
.joinpath(GUIDE_RESOURCE)
|
|
21
|
+
.read_text(encoding="utf-8"))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def guide_section() -> str:
|
|
25
|
+
"""The guide block injected into an agent step's task context."""
|
|
26
|
+
return f"## {GUIDE_HEADING}\n\n{load_guide()}"
|
{agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
@@ -32,6 +32,7 @@ from typing import TYPE_CHECKING
|
|
|
32
32
|
from langgraph.graph import END
|
|
33
33
|
from openhands.sdk import Conversation
|
|
34
34
|
|
|
35
|
+
from .agent_error_guide import guide_section
|
|
35
36
|
from .agents import load_agent, register_sandbox_terminal
|
|
36
37
|
from agentic_dev_loop.core.config import TERMINALS, PipelineConfig, Route, StepConfig, route_target
|
|
37
38
|
from agentic_dev_loop.core.paths import _has_wildcards
|
|
@@ -57,49 +58,79 @@ LAST_ERROR = "last-error.md"
|
|
|
57
58
|
ARTIFACT_CONTEXT_CAP = 24_000 # chars per artifact injected into a step's context
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
class
|
|
61
|
+
class _TurnPauseController:
|
|
62
|
+
"""Turn-level pause callback (032): pause the SDK run loop after every
|
|
63
|
+
agent-sourced event so the caller can drive one turn at a time. The
|
|
64
|
+
finalize nudge (035) reuses this hook without any stop-when state."""
|
|
65
|
+
|
|
66
|
+
def __init__(self) -> None:
|
|
67
|
+
self.conversation = None
|
|
68
|
+
|
|
69
|
+
def attach(self, conversation) -> None:
|
|
70
|
+
self.conversation = conversation
|
|
71
|
+
|
|
72
|
+
def on_event(self, event) -> None:
|
|
73
|
+
if self.conversation is None:
|
|
74
|
+
return
|
|
75
|
+
if getattr(event, "source", None) == "agent":
|
|
76
|
+
from openhands.sdk.conversation.state import (
|
|
77
|
+
ConversationExecutionStatus,
|
|
78
|
+
)
|
|
79
|
+
self.conversation.state.execution_status = (
|
|
80
|
+
ConversationExecutionStatus.PAUSED
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class _FinalizeNudgeController(_TurnPauseController):
|
|
85
|
+
"""Finalize nudge (035): template text, never step-named. The turn loop
|
|
86
|
+
asks for the message when the conversation reaches the configured index;
|
|
87
|
+
``nudged`` guards against re-injection on a same-turn retry."""
|
|
88
|
+
|
|
89
|
+
_TEMPLATE = "{n} turns remain: produce your final response now"
|
|
90
|
+
|
|
91
|
+
def __init__(self, turns: int) -> None:
|
|
92
|
+
super().__init__()
|
|
93
|
+
self.turns = turns
|
|
94
|
+
self.nudged = False
|
|
95
|
+
|
|
96
|
+
def message(self) -> str:
|
|
97
|
+
return self._TEMPLATE.format(n=self.turns)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class _StopWhenController(_TurnPauseController):
|
|
61
101
|
"""Turn-level stop-when-green observation (032).
|
|
62
102
|
|
|
63
103
|
Pure per-event state machine - no knowledge of how the conversation is
|
|
64
104
|
driven. ``on_event`` is a SDK conversation callback: it records the most
|
|
65
105
|
recent matching terminal observation and any subsequent file-editor
|
|
66
|
-
write, and pauses the conversation after every
|
|
67
|
-
caller can drive one turn at a time.
|
|
106
|
+
write, and (via the base pause) pauses the conversation after every
|
|
107
|
+
agent-sourced event so the caller can drive one turn at a time.
|
|
68
108
|
"""
|
|
69
109
|
|
|
70
110
|
NUDGE = "Step complete: produce your final response now"
|
|
71
111
|
_WRITE_COMMANDS = frozenset({"create", "str_replace", "insert", "undo_edit"})
|
|
72
112
|
|
|
73
113
|
def __init__(self, spec) -> None:
|
|
114
|
+
super().__init__()
|
|
74
115
|
self._pattern = re.compile(spec.green_command)
|
|
75
116
|
self.post_green_turns = spec.post_green_turns
|
|
76
117
|
self.green_hit = False
|
|
77
118
|
self.writes_since_green = False
|
|
78
119
|
self.nudged = False
|
|
79
120
|
self.post_nudge_turns = 0
|
|
80
|
-
self.conversation = None
|
|
81
|
-
|
|
82
|
-
def attach(self, conversation) -> None:
|
|
83
|
-
self.conversation = conversation
|
|
84
121
|
|
|
85
122
|
@property
|
|
86
123
|
def green_signal(self) -> bool:
|
|
87
124
|
return self.green_hit and not self.writes_since_green
|
|
88
125
|
|
|
89
126
|
def on_event(self, event) -> None:
|
|
127
|
+
super().on_event(event)
|
|
90
128
|
if self.conversation is None:
|
|
91
129
|
return
|
|
92
130
|
from openhands.sdk.event import ObservationEvent
|
|
93
131
|
|
|
94
132
|
if isinstance(event, ObservationEvent):
|
|
95
133
|
self._record_observation(event)
|
|
96
|
-
if getattr(event, "source", None) == "agent":
|
|
97
|
-
from openhands.sdk.conversation.state import (
|
|
98
|
-
ConversationExecutionStatus,
|
|
99
|
-
)
|
|
100
|
-
self.conversation.state.execution_status = (
|
|
101
|
-
ConversationExecutionStatus.PAUSED
|
|
102
|
-
)
|
|
103
134
|
|
|
104
135
|
def on_terminal_result(self, command: str | None, exit_code: int | None,
|
|
105
136
|
wrote_workspace: bool) -> None:
|
|
@@ -159,6 +190,10 @@ class Runtime:
|
|
|
159
190
|
controller = None
|
|
160
191
|
if step is not None and step.stop_when is not None:
|
|
161
192
|
controller = _StopWhenController(step.stop_when)
|
|
193
|
+
finalize_controller = None
|
|
194
|
+
if step is not None and step.finalize_nudge_turns is not None:
|
|
195
|
+
finalize_controller = _FinalizeNudgeController(
|
|
196
|
+
step.finalize_nudge_turns)
|
|
162
197
|
|
|
163
198
|
if self.sandbox is not None:
|
|
164
199
|
if step is not None:
|
|
@@ -181,15 +216,22 @@ class Runtime:
|
|
|
181
216
|
secrets=(step_secret_env(self.cfg, step, self.secrets)
|
|
182
217
|
if step is not None else None))
|
|
183
218
|
agent = load_agent(self.agents_dir, agent_name, self.llm)
|
|
184
|
-
callbacks = [
|
|
219
|
+
callbacks = []
|
|
220
|
+
if controller is not None:
|
|
221
|
+
callbacks.append(controller.on_event)
|
|
222
|
+
if finalize_controller is not None:
|
|
223
|
+
callbacks.append(finalize_controller.on_event)
|
|
185
224
|
conv = Conversation(agent=agent, workspace=str(self.workspace),
|
|
186
225
|
max_iteration_per_run=max_iterations,
|
|
187
|
-
callbacks=callbacks)
|
|
226
|
+
callbacks=callbacks or None)
|
|
188
227
|
if controller is not None:
|
|
189
228
|
controller.attach(conv)
|
|
229
|
+
if finalize_controller is not None:
|
|
230
|
+
finalize_controller.attach(conv)
|
|
190
231
|
conv.send_message(message)
|
|
191
|
-
if controller is not None:
|
|
192
|
-
self._run_stop_when_loop(conv, controller, max_iterations
|
|
232
|
+
if controller is not None or finalize_controller is not None:
|
|
233
|
+
self._run_stop_when_loop(conv, controller, max_iterations,
|
|
234
|
+
finalize_controller)
|
|
193
235
|
else:
|
|
194
236
|
conv.run()
|
|
195
237
|
|
|
@@ -230,15 +272,19 @@ class Runtime:
|
|
|
230
272
|
if conv is not None:
|
|
231
273
|
conv.close()
|
|
232
274
|
|
|
233
|
-
def _run_stop_when_loop(self, conv,
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
275
|
+
def _run_stop_when_loop(self, conv,
|
|
276
|
+
controller: _StopWhenController | None,
|
|
277
|
+
max_iterations: int,
|
|
278
|
+
finalize_controller: _FinalizeNudgeController | None = None) -> None:
|
|
279
|
+
"""Drive one turn at a time so turn-level hooks can inject their
|
|
280
|
+
nudges and enforce budgets. A controller's callback pauses the SDK
|
|
281
|
+
run loop after every agent-sourced event, so each ``conv.run()`` here
|
|
282
|
+
advances exactly one agent turn."""
|
|
239
283
|
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
240
284
|
|
|
241
285
|
turns = 0
|
|
286
|
+
nudge_at = (max_iterations - finalize_controller.turns
|
|
287
|
+
if finalize_controller is not None else None)
|
|
242
288
|
while True:
|
|
243
289
|
conv.run()
|
|
244
290
|
status = conv.state.execution_status
|
|
@@ -252,17 +298,24 @@ class Runtime:
|
|
|
252
298
|
conv.state.execution_status = ConversationExecutionStatus.ERROR
|
|
253
299
|
break
|
|
254
300
|
|
|
255
|
-
if not
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
break
|
|
301
|
+
if (finalize_controller is not None and turns == nudge_at
|
|
302
|
+
and not finalize_controller.nudged):
|
|
303
|
+
conv.send_message(finalize_controller.message())
|
|
304
|
+
finalize_controller.nudged = True
|
|
260
305
|
continue
|
|
261
306
|
|
|
262
|
-
if controller
|
|
263
|
-
controller.
|
|
264
|
-
|
|
265
|
-
|
|
307
|
+
if controller is not None:
|
|
308
|
+
if not controller.nudged and controller.green_signal:
|
|
309
|
+
conv.send_message(controller.NUDGE)
|
|
310
|
+
controller.nudged = True
|
|
311
|
+
if controller.post_green_turns == 0:
|
|
312
|
+
break
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
if controller.nudged:
|
|
316
|
+
controller.post_nudge_turns += 1
|
|
317
|
+
if controller.post_nudge_turns >= controller.post_green_turns:
|
|
318
|
+
break
|
|
266
319
|
|
|
267
320
|
|
|
268
321
|
def prompt_dir(runtime: Runtime) -> str:
|
|
@@ -364,12 +417,12 @@ def _artifact_file(runtime: Runtime, step_name: str) -> str | None:
|
|
|
364
417
|
def build_context(runtime: Runtime, docs: dict[str, str], subject: str,
|
|
365
418
|
notes: list[str] | None = None,
|
|
366
419
|
artifacts: dict[str, str] | None = None) -> str:
|
|
367
|
-
"""Assemble the attempt's shared message:
|
|
368
|
-
|
|
369
|
-
section generated from step defs (a prompt
|
|
370
|
-
skip them), accumulated stage artifacts
|
|
371
|
-
notes."""
|
|
372
|
-
parts = [f"You are working on {subject}.\n"]
|
|
420
|
+
"""Assemble the attempt's shared message: the always-on agent-facing error
|
|
421
|
+
guide (034), subject, doc bundle, the canonical dependency-policy paragraph
|
|
422
|
+
(021), the expected-output-paths section generated from step defs (a prompt
|
|
423
|
+
merely naming files lets agents skip them), accumulated stage artifacts
|
|
424
|
+
(027), and previous-attempt notes."""
|
|
425
|
+
parts = [f"You are working on {subject}.\n", guide_section()]
|
|
373
426
|
for name, content in docs.items():
|
|
374
427
|
parts.append(f"## {name}\n\n{content}")
|
|
375
428
|
if artifacts:
|
|
@@ -541,13 +594,14 @@ def brief_inputs_hash(runtime: Runtime, step: StepConfig,
|
|
|
541
594
|
def build_brief_context(runtime: Runtime, step: StepConfig,
|
|
542
595
|
docs: dict[str, str],
|
|
543
596
|
doc_sources: dict[str, str] | None = None) -> str:
|
|
544
|
-
"""Assemble the brief agent's task message (033): the
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
and
|
|
548
|
-
attempt."""
|
|
597
|
+
"""Assemble the brief agent's task message (033): the always-on
|
|
598
|
+
agent-facing error guide (034), the resolved unit/plan docs (re-read from
|
|
599
|
+
disk where sources are known), the closed ``brief_inputs.include`` file
|
|
600
|
+
set, and the env-facts block. Prior verdicts and feedback are deliberately
|
|
601
|
+
absent - the notes channel delivers them per attempt."""
|
|
549
602
|
sections, env_facts = _brief_sections(runtime, step, docs, doc_sources)
|
|
550
|
-
parts = [
|
|
603
|
+
parts = [guide_section()]
|
|
604
|
+
parts += [f"## {name}\n\n{content}" for name, content in sections.items()]
|
|
551
605
|
if env_facts:
|
|
552
606
|
parts.append("## Environment facts\n\n"
|
|
553
607
|
+ "\n".join(f"- {fact}" for fact in env_facts))
|
{agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32/src/agentic_dev_loop.egg-info}/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.0a32
|
|
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
|
|
@@ -224,6 +224,8 @@ steps:
|
|
|
224
224
|
agent: qa
|
|
225
225
|
artifact: qa-report.md
|
|
226
226
|
verdicts: { pass: PASS, fail: FAIL }
|
|
227
|
+
max_iterations: 100
|
|
228
|
+
finalize_nudge_turns: 10
|
|
227
229
|
on_pass: commit
|
|
228
230
|
on_fail: { goto: dev, retry: true }
|
|
229
231
|
|
|
@@ -239,6 +241,7 @@ steps:
|
|
|
239
241
|
| `brief_inputs:` | Optional agent-step input spec: `{ include: [<paths/globs>], env_facts: [<strings>] }`. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as that step's task message, and regenerates the step only when those inputs change. A produces-only agent step such as `brief` is the documented pattern for per-run context digestion. |
|
|
240
242
|
| `stop_when:` | Optional agent-step termination: `{ green_command: <argv regex>, post_green_turns: <int, default 2> }`. When the agent's most recent terminal observation matching `green_command` exits 0 and no workspace writes have happened since, the engine injects one nudge turn, allows `post_green_turns` further turns, then closes the conversation. Absent = run to finish. |
|
|
241
243
|
| `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. |
|
|
244
|
+
| `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). |
|
|
242
245
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
243
246
|
| `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). |
|
|
244
247
|
| `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). |
|
|
@@ -421,9 +424,10 @@ an agent burned budget learning it mid-run:
|
|
|
421
424
|
the dev agent's next context; the dev prompt makes every listed issue a
|
|
422
425
|
required fix.
|
|
423
426
|
- **No git mutations, ever.** The `commit` step is the single commit point.
|
|
424
|
-
- **`max_iterations` per role.** qa is bounded probing (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
+
- **`max_iterations` per role.** qa is bounded probing (100 in this
|
|
428
|
+
repo's dogfood loop, with `finalize_nudge_turns: 10` so it never ends
|
|
429
|
+
silently at the cap - both provisional); dev is open-ended construction
|
|
430
|
+
and stays uncapped (the engine default).
|
|
427
431
|
|
|
428
432
|
### The pipeline file
|
|
429
433
|
|
|
@@ -438,7 +442,7 @@ adapt per repo.
|
|
|
438
442
|
| 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 |
|
|
439
443
|
| conformance | command | network + `presets: [package-registries]` | repo-specific fitness checks (the consumer repo: jscpd + DDD rules) |
|
|
440
444
|
| review | agent | ro, offline | static conformance verdict |
|
|
441
|
-
| qa | agent | per repo (see table above) | live-behaviour probes; `max_iterations:
|
|
445
|
+
| 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 |
|
|
442
446
|
| commit | action | host-side | the single commit point |
|
|
443
447
|
|
|
444
448
|
Adaptation checklist for a new repo:
|
|
@@ -541,6 +545,28 @@ dotnet restore --locked-mode # installs exactly packages.lock.json
|
|
|
541
545
|
Keep the lockfile tracked (do not gitignore it) and re-run the locked restore
|
|
542
546
|
to prove it holds from a clean cache before setting `deps_policy: locked`.
|
|
543
547
|
|
|
548
|
+
## Agent-facing error guide
|
|
549
|
+
|
|
550
|
+
The engine bundles an always-on guide for the engine error codes an agent
|
|
551
|
+
can actually observe while running terminal commands. The guide lives at
|
|
552
|
+
`src/agentic_dev_loop/engine/agent_error_guide.md`, versioned with the engine
|
|
553
|
+
code that emits the codes, and is injected into every agent step's task
|
|
554
|
+
context at assembly time. There is no opt-in and no opt-out in v1; command
|
|
555
|
+
and action steps never receive it because no agent reads them.
|
|
556
|
+
|
|
557
|
+
Each agent-visible code has exactly one table row, shaped like this:
|
|
558
|
+
|
|
559
|
+
| Code | Meaning | Correct response |
|
|
560
|
+
| --- | --- | --- |
|
|
561
|
+
| 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. |
|
|
562
|
+
|
|
563
|
+
The guide is consumer-agnostic: it explains the general mechanism and never
|
|
564
|
+
names a consumer or a consumer domain rule ID. If an ID must appear inside a
|
|
565
|
+
quoted engine message, the placeholder `<domain-rule-id>` is used. Any engine
|
|
566
|
+
change that adds or changes an agent-visible error code must update the guide
|
|
567
|
+
in the same commit; the test suite enforces the pairing by parsing the
|
|
568
|
+
agent-visible `SandboxError` raise sites against the bundled table.
|
|
569
|
+
|
|
544
570
|
## Host boundary (security model)
|
|
545
571
|
|
|
546
572
|
The orchestrator, tool layer, and the `commit` action run in the **host
|
{agentic_dev_loop-1.0.0a30 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
@@ -16,6 +16,8 @@ src/agentic_dev_loop/core/results.py
|
|
|
16
16
|
src/agentic_dev_loop/core/verdict.py
|
|
17
17
|
src/agentic_dev_loop/engine/__init__.py
|
|
18
18
|
src/agentic_dev_loop/engine/actions.py
|
|
19
|
+
src/agentic_dev_loop/engine/agent_error_guide.md
|
|
20
|
+
src/agentic_dev_loop/engine/agent_error_guide.py
|
|
19
21
|
src/agentic_dev_loop/engine/agents.py
|
|
20
22
|
src/agentic_dev_loop/engine/cli.py
|
|
21
23
|
src/agentic_dev_loop/engine/deps.py
|
|
@@ -47,7 +49,9 @@ tests/test_028_declared_caches.py
|
|
|
47
49
|
tests/test_029_merge_on_conflict.py
|
|
48
50
|
tests/test_032_stop_when_green.py
|
|
49
51
|
tests/test_033_brief_step.py
|
|
52
|
+
tests/test_035_finalize_nudge.py
|
|
50
53
|
tests/test_actions.py
|
|
54
|
+
tests/test_agent_error_guide.py
|
|
51
55
|
tests/test_agents.py
|
|
52
56
|
tests/test_aidlc.py
|
|
53
57
|
tests/test_cli.py
|