agentic-dev-loop 1.0.0a31__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.0a31/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a32}/PKG-INFO +9 -5
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/README.md +8 -4
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/config.py +16 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/stages.py +85 -33
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32/src/agentic_dev_loop.egg-info}/PKG-INFO +9 -5
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/SOURCES.txt +1 -0
- agentic_dev_loop-1.0.0a32/tests/test_035_finalize_nudge.py +211 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/graph.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_032_stop_when_green.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_viz.py +0 -0
{agentic_dev_loop-1.0.0a31/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:
|
|
@@ -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:
|
|
@@ -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"
|
|
@@ -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'"
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
@@ -58,49 +58,79 @@ LAST_ERROR = "last-error.md"
|
|
|
58
58
|
ARTIFACT_CONTEXT_CAP = 24_000 # chars per artifact injected into a step's context
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
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):
|
|
62
101
|
"""Turn-level stop-when-green observation (032).
|
|
63
102
|
|
|
64
103
|
Pure per-event state machine - no knowledge of how the conversation is
|
|
65
104
|
driven. ``on_event`` is a SDK conversation callback: it records the most
|
|
66
105
|
recent matching terminal observation and any subsequent file-editor
|
|
67
|
-
write, and pauses the conversation after every
|
|
68
|
-
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.
|
|
69
108
|
"""
|
|
70
109
|
|
|
71
110
|
NUDGE = "Step complete: produce your final response now"
|
|
72
111
|
_WRITE_COMMANDS = frozenset({"create", "str_replace", "insert", "undo_edit"})
|
|
73
112
|
|
|
74
113
|
def __init__(self, spec) -> None:
|
|
114
|
+
super().__init__()
|
|
75
115
|
self._pattern = re.compile(spec.green_command)
|
|
76
116
|
self.post_green_turns = spec.post_green_turns
|
|
77
117
|
self.green_hit = False
|
|
78
118
|
self.writes_since_green = False
|
|
79
119
|
self.nudged = False
|
|
80
120
|
self.post_nudge_turns = 0
|
|
81
|
-
self.conversation = None
|
|
82
|
-
|
|
83
|
-
def attach(self, conversation) -> None:
|
|
84
|
-
self.conversation = conversation
|
|
85
121
|
|
|
86
122
|
@property
|
|
87
123
|
def green_signal(self) -> bool:
|
|
88
124
|
return self.green_hit and not self.writes_since_green
|
|
89
125
|
|
|
90
126
|
def on_event(self, event) -> None:
|
|
127
|
+
super().on_event(event)
|
|
91
128
|
if self.conversation is None:
|
|
92
129
|
return
|
|
93
130
|
from openhands.sdk.event import ObservationEvent
|
|
94
131
|
|
|
95
132
|
if isinstance(event, ObservationEvent):
|
|
96
133
|
self._record_observation(event)
|
|
97
|
-
if getattr(event, "source", None) == "agent":
|
|
98
|
-
from openhands.sdk.conversation.state import (
|
|
99
|
-
ConversationExecutionStatus,
|
|
100
|
-
)
|
|
101
|
-
self.conversation.state.execution_status = (
|
|
102
|
-
ConversationExecutionStatus.PAUSED
|
|
103
|
-
)
|
|
104
134
|
|
|
105
135
|
def on_terminal_result(self, command: str | None, exit_code: int | None,
|
|
106
136
|
wrote_workspace: bool) -> None:
|
|
@@ -160,6 +190,10 @@ class Runtime:
|
|
|
160
190
|
controller = None
|
|
161
191
|
if step is not None and step.stop_when is not None:
|
|
162
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)
|
|
163
197
|
|
|
164
198
|
if self.sandbox is not None:
|
|
165
199
|
if step is not None:
|
|
@@ -182,15 +216,22 @@ class Runtime:
|
|
|
182
216
|
secrets=(step_secret_env(self.cfg, step, self.secrets)
|
|
183
217
|
if step is not None else None))
|
|
184
218
|
agent = load_agent(self.agents_dir, agent_name, self.llm)
|
|
185
|
-
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)
|
|
186
224
|
conv = Conversation(agent=agent, workspace=str(self.workspace),
|
|
187
225
|
max_iteration_per_run=max_iterations,
|
|
188
|
-
callbacks=callbacks)
|
|
226
|
+
callbacks=callbacks or None)
|
|
189
227
|
if controller is not None:
|
|
190
228
|
controller.attach(conv)
|
|
229
|
+
if finalize_controller is not None:
|
|
230
|
+
finalize_controller.attach(conv)
|
|
191
231
|
conv.send_message(message)
|
|
192
|
-
if controller is not None:
|
|
193
|
-
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)
|
|
194
235
|
else:
|
|
195
236
|
conv.run()
|
|
196
237
|
|
|
@@ -231,15 +272,19 @@ class Runtime:
|
|
|
231
272
|
if conv is not None:
|
|
232
273
|
conv.close()
|
|
233
274
|
|
|
234
|
-
def _run_stop_when_loop(self, conv,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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."""
|
|
240
283
|
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
241
284
|
|
|
242
285
|
turns = 0
|
|
286
|
+
nudge_at = (max_iterations - finalize_controller.turns
|
|
287
|
+
if finalize_controller is not None else None)
|
|
243
288
|
while True:
|
|
244
289
|
conv.run()
|
|
245
290
|
status = conv.state.execution_status
|
|
@@ -253,17 +298,24 @@ class Runtime:
|
|
|
253
298
|
conv.state.execution_status = ConversationExecutionStatus.ERROR
|
|
254
299
|
break
|
|
255
300
|
|
|
256
|
-
if not
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
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
|
|
261
305
|
continue
|
|
262
306
|
|
|
263
|
-
if controller
|
|
264
|
-
controller.
|
|
265
|
-
|
|
266
|
-
|
|
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
|
|
267
319
|
|
|
268
320
|
|
|
269
321
|
def prompt_dir(runtime: Runtime) -> str:
|
{agentic_dev_loop-1.0.0a31 → 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:
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""Finalize nudge (035): config surface, the turn-index injection, and the
|
|
2
|
+
exactly-once guard on the shared 032 turn loop."""
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
import yaml
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
|
|
11
|
+
from openhands.sdk import LLM
|
|
12
|
+
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
13
|
+
|
|
14
|
+
from agentic_dev_loop.core.config import PipelineConfig, load_config
|
|
15
|
+
from agentic_dev_loop.engine.registry import default_registry
|
|
16
|
+
from agentic_dev_loop.engine.stages import Runtime, _FinalizeNudgeController
|
|
17
|
+
|
|
18
|
+
NUDGE = "3 turns remain: produce your final response now"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _base() -> dict:
|
|
22
|
+
return {
|
|
23
|
+
"name": "t",
|
|
24
|
+
"llm": {"model": "m", "api_key_env": "K"},
|
|
25
|
+
"agents_dir": "agents",
|
|
26
|
+
"lockfiles": ["uv.lock"],
|
|
27
|
+
"steps": [{"name": "dev", "agent": "dev", "on_pass": "success"}],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load(tmp_path, data):
|
|
32
|
+
p = tmp_path / "pipeline.yaml"
|
|
33
|
+
p.write_text(yaml.safe_dump(data))
|
|
34
|
+
return load_config(p)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _verdict_step(**extra) -> dict:
|
|
38
|
+
step = {
|
|
39
|
+
"name": "qa",
|
|
40
|
+
"agent": "qa",
|
|
41
|
+
"artifact": "qa-report.md",
|
|
42
|
+
"verdicts": {"pass": "PASS", "fail": "FAIL"},
|
|
43
|
+
"max_iterations": 100,
|
|
44
|
+
"finalize_nudge_turns": 10,
|
|
45
|
+
"on_pass": "success",
|
|
46
|
+
}
|
|
47
|
+
step.update(extra)
|
|
48
|
+
return step
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestFinalizeNudgeConfig:
|
|
52
|
+
def test_valid_agent_step_round_trip(self, tmp_path):
|
|
53
|
+
data = _base()
|
|
54
|
+
data["steps"] = [_verdict_step()]
|
|
55
|
+
step = _load(tmp_path, data).steps[0]
|
|
56
|
+
assert step.finalize_nudge_turns == 10
|
|
57
|
+
assert step.max_iterations == 100
|
|
58
|
+
|
|
59
|
+
def test_absent_keeps_none(self, tmp_path):
|
|
60
|
+
data = _base()
|
|
61
|
+
assert _load(tmp_path, data).steps[0].finalize_nudge_turns is None
|
|
62
|
+
|
|
63
|
+
def test_requires_max_iterations(self, tmp_path):
|
|
64
|
+
data = _base()
|
|
65
|
+
data["steps"] = [_verdict_step(max_iterations=None)]
|
|
66
|
+
with pytest.raises(ValidationError, match="finalize_nudge_turns"):
|
|
67
|
+
_load(tmp_path, data)
|
|
68
|
+
|
|
69
|
+
def test_must_be_less_than_max_iterations(self, tmp_path):
|
|
70
|
+
data = _base()
|
|
71
|
+
data["steps"] = [_verdict_step(max_iterations=10,
|
|
72
|
+
finalize_nudge_turns=10)]
|
|
73
|
+
with pytest.raises(ValidationError, match="finalize_nudge_turns"):
|
|
74
|
+
_load(tmp_path, data)
|
|
75
|
+
|
|
76
|
+
@pytest.mark.parametrize("bad", [0, -1])
|
|
77
|
+
def test_nonpositive_rejected(self, tmp_path, bad):
|
|
78
|
+
data = _base()
|
|
79
|
+
data["steps"] = [_verdict_step(finalize_nudge_turns=bad)]
|
|
80
|
+
with pytest.raises(ValidationError, match="finalize_nudge_turns"):
|
|
81
|
+
_load(tmp_path, data)
|
|
82
|
+
|
|
83
|
+
@pytest.mark.parametrize("step", [
|
|
84
|
+
{"name": "test", "command": "true", "finalize_nudge_turns": 2},
|
|
85
|
+
{"name": "commit", "action": "commit", "finalize_nudge_turns": 2},
|
|
86
|
+
])
|
|
87
|
+
def test_agent_only_rejected_on_non_agent(self, tmp_path, step):
|
|
88
|
+
data = _base()
|
|
89
|
+
data["steps"] = [step]
|
|
90
|
+
with pytest.raises(ValidationError, match="requires 'agent'"):
|
|
91
|
+
_load(tmp_path, data)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TestFinalizeNudgeLoop:
|
|
95
|
+
def _conv(self, finish_after):
|
|
96
|
+
class Fake:
|
|
97
|
+
def __init__(self):
|
|
98
|
+
self.state = SimpleNamespace(execution_status=None)
|
|
99
|
+
self.run_calls = 0
|
|
100
|
+
self.sent = []
|
|
101
|
+
|
|
102
|
+
def run(self):
|
|
103
|
+
self.run_calls += 1
|
|
104
|
+
self.state.execution_status = (
|
|
105
|
+
ConversationExecutionStatus.FINISHED
|
|
106
|
+
if self.run_calls >= finish_after
|
|
107
|
+
else ConversationExecutionStatus.PAUSED)
|
|
108
|
+
|
|
109
|
+
def send_message(self, message):
|
|
110
|
+
self.sent.append((self.run_calls, message))
|
|
111
|
+
|
|
112
|
+
return Fake()
|
|
113
|
+
|
|
114
|
+
def test_nudge_at_configured_turn_and_only_once(self):
|
|
115
|
+
conv = self._conv(finish_after=9)
|
|
116
|
+
controller = _FinalizeNudgeController(3)
|
|
117
|
+
Runtime._run_stop_when_loop(None, conv, None, max_iterations=10,
|
|
118
|
+
finalize_controller=controller)
|
|
119
|
+
assert controller.nudged is True
|
|
120
|
+
assert conv.sent == [(7, NUDGE)]
|
|
121
|
+
|
|
122
|
+
def test_no_nudge_when_finished_before_threshold(self):
|
|
123
|
+
conv = self._conv(finish_after=3)
|
|
124
|
+
controller = _FinalizeNudgeController(3)
|
|
125
|
+
Runtime._run_stop_when_loop(None, conv, None, max_iterations=10,
|
|
126
|
+
finalize_controller=controller)
|
|
127
|
+
assert controller.nudged is False
|
|
128
|
+
assert conv.sent == []
|
|
129
|
+
|
|
130
|
+
def test_turn_cap_still_applies_when_nudge_ignored(self):
|
|
131
|
+
conv = self._conv(finish_after=999)
|
|
132
|
+
controller = _FinalizeNudgeController(3)
|
|
133
|
+
Runtime._run_stop_when_loop(None, conv, None, max_iterations=5,
|
|
134
|
+
finalize_controller=controller)
|
|
135
|
+
assert controller.nudged is True
|
|
136
|
+
assert conv.state.execution_status == ConversationExecutionStatus.ERROR
|
|
137
|
+
assert conv.sent == [(2, NUDGE)]
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class TestRunAgentFinalizeNudge:
|
|
141
|
+
def _runtime(self, tmp_path):
|
|
142
|
+
cfg = PipelineConfig.model_validate({
|
|
143
|
+
"name": "t",
|
|
144
|
+
"llm": {"model": "m", "api_key_env": "K"},
|
|
145
|
+
"agents_dir": "agents",
|
|
146
|
+
"lockfiles": ["uv.lock"],
|
|
147
|
+
"steps": [{
|
|
148
|
+
"name": "qa",
|
|
149
|
+
"agent": "qa",
|
|
150
|
+
"max_iterations": 10,
|
|
151
|
+
"finalize_nudge_turns": 3,
|
|
152
|
+
"on_pass": "success",
|
|
153
|
+
}],
|
|
154
|
+
})
|
|
155
|
+
state_dir = tmp_path / ".pr"
|
|
156
|
+
state_dir.mkdir()
|
|
157
|
+
agents_dir = tmp_path / "agents"
|
|
158
|
+
agents_dir.mkdir()
|
|
159
|
+
(agents_dir / "qa.agent.md").write_text(
|
|
160
|
+
"---\nname: qa\ntools: [terminal]\n---\nProbe.")
|
|
161
|
+
return Runtime(
|
|
162
|
+
cfg=cfg, registry=default_registry(),
|
|
163
|
+
llm=LLM(model="openai/test-model", api_key="sk-test"),
|
|
164
|
+
agents_dir=agents_dir, state_dir=state_dir,
|
|
165
|
+
workspace=tmp_path, sandbox=None)
|
|
166
|
+
|
|
167
|
+
def _fake_conv(self, finish_after):
|
|
168
|
+
class Fake:
|
|
169
|
+
def __init__(self):
|
|
170
|
+
self.state = SimpleNamespace(events=[], execution_status=None)
|
|
171
|
+
self.callbacks = []
|
|
172
|
+
self.messages = []
|
|
173
|
+
self.closed = False
|
|
174
|
+
self.run_calls = 0
|
|
175
|
+
self.finish_after = finish_after
|
|
176
|
+
|
|
177
|
+
def send_message(self, message):
|
|
178
|
+
self.messages.append(message)
|
|
179
|
+
|
|
180
|
+
def run(self):
|
|
181
|
+
self.run_calls += 1
|
|
182
|
+
self.state.execution_status = (
|
|
183
|
+
ConversationExecutionStatus.FINISHED
|
|
184
|
+
if self.run_calls >= self.finish_after
|
|
185
|
+
else ConversationExecutionStatus.PAUSED)
|
|
186
|
+
|
|
187
|
+
def close(self):
|
|
188
|
+
self.closed = True
|
|
189
|
+
|
|
190
|
+
return Fake()
|
|
191
|
+
|
|
192
|
+
def test_run_agent_attaches_pause_and_sends_nudge(self, tmp_path, monkeypatch):
|
|
193
|
+
runtime = self._runtime(tmp_path)
|
|
194
|
+
conv = self._fake_conv(finish_after=9)
|
|
195
|
+
|
|
196
|
+
def factory(**kw):
|
|
197
|
+
conv.callbacks = kw.get("callbacks") or []
|
|
198
|
+
return conv
|
|
199
|
+
|
|
200
|
+
monkeypatch.setattr(
|
|
201
|
+
"agentic_dev_loop.engine.stages.Conversation", factory)
|
|
202
|
+
|
|
203
|
+
result = runtime.run_agent("qa", "the task", max_iterations=10,
|
|
204
|
+
step=runtime.cfg.steps[0])
|
|
205
|
+
|
|
206
|
+
assert result.ok is True
|
|
207
|
+
assert result.truncated is False
|
|
208
|
+
assert conv.callbacks
|
|
209
|
+
assert conv.messages == ["the task", NUDGE]
|
|
210
|
+
assert conv.run_calls == 9
|
|
211
|
+
assert conv.closed is True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/results.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/core/verdict.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/actions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/dryrun.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/egress_policy.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/egress_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/graph.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/hostboundary.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/loaders.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/mounts.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/outputs.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/presets.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/secrets.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/engine/sync_back.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/viz/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop/viz/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/src/agentic_dev_loop.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a31 → agentic_dev_loop-1.0.0a32}/tests/test_hostboundary_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|