agentic-dev-loop 1.0.0a42__tar.gz → 1.0.0a43__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.0a42/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a43}/PKG-INFO +3 -3
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/README.md +0 -2
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/pyproject.toml +9 -1
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/core/__init__.py +1 -3
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/core/config.py +0 -35
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/stages.py +8 -91
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43/src/agentic_dev_loop.egg-info}/PKG-INFO +3 -3
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -1
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop.egg-info/requires.txt +2 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_035_finalize_nudge.py +11 -4
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_agents.py +31 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_config.py +24 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_sdk_compat.py +7 -7
- agentic_dev_loop-1.0.0a42/tests/test_032_stop_when_green.py +0 -363
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/containers.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/graph.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_041_brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_containers.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/tests/test_viz.py +0 -0
{agentic_dev_loop-1.0.0a42/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a43}/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.0a43
|
|
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
|
|
@@ -33,6 +33,8 @@ Requires-Dist: agent-client-protocol<1.0.0
|
|
|
33
33
|
Requires-Dist: langgraph>=1.2
|
|
34
34
|
Requires-Dist: langgraph-checkpoint-sqlite>=3.0
|
|
35
35
|
Requires-Dist: filelock>=4.2.1
|
|
36
|
+
Requires-Dist: pyasn1>=0.6.5
|
|
37
|
+
Requires-Dist: pypdf>=6.21.4
|
|
36
38
|
Provides-Extra: dev
|
|
37
39
|
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
38
40
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
@@ -213,7 +215,6 @@ steps:
|
|
|
213
215
|
agent: dev
|
|
214
216
|
produces: implementation-summary.md
|
|
215
217
|
terminal_timeout: 900
|
|
216
|
-
stop_when: { green_command: ".venv-sandbox/bin/python -m pytest tests/ -q", post_green_turns: 2 }
|
|
217
218
|
on_pass: test
|
|
218
219
|
|
|
219
220
|
- name: test
|
|
@@ -249,7 +250,6 @@ steps:
|
|
|
249
250
|
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
|
|
250
251
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
251
252
|
| `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. |
|
|
252
|
-
| `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. |
|
|
253
253
|
| `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. |
|
|
254
254
|
| `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). |
|
|
255
255
|
| `custom_prompt:` | Planned (planning-docs/040). Optional agent-step value: literal text, or a path whose file contents are read at load time, appended verbatim to the agent prompt at context assembly (after the prompt, before the task/artifacts). A value that looks like a path but names no file is a validation error. Agent steps only. |
|
|
@@ -171,7 +171,6 @@ steps:
|
|
|
171
171
|
agent: dev
|
|
172
172
|
produces: implementation-summary.md
|
|
173
173
|
terminal_timeout: 900
|
|
174
|
-
stop_when: { green_command: ".venv-sandbox/bin/python -m pytest tests/ -q", post_green_turns: 2 }
|
|
175
174
|
on_pass: test
|
|
176
175
|
|
|
177
176
|
- name: test
|
|
@@ -207,7 +206,6 @@ steps:
|
|
|
207
206
|
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
|
|
208
207
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
209
208
|
| `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. |
|
|
210
|
-
| `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. |
|
|
211
209
|
| `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. |
|
|
212
210
|
| `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). |
|
|
213
211
|
| `custom_prompt:` | Planned (planning-docs/040). Optional agent-step value: literal text, or a path whose file contents are read at load time, appended verbatim to the agent prompt at context assembly (after the prompt, before the task/artifacts). A value that looks like a path but names no file is a validation error. Agent steps only. |
|
|
@@ -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.0a43"
|
|
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"
|
|
@@ -47,6 +47,14 @@ dependencies = [
|
|
|
47
47
|
# 3.32.7 carries CVE-2026-112907/-112913/-112940 (fixed in 4.2.1); pin a
|
|
48
48
|
# floor so the restore step's pip-audit stays green.
|
|
49
49
|
"filelock>=4.2.1",
|
|
50
|
+
# pyasn1 is a transitive dep (openhands-sdk, rsa, google-auth). 0.6.4
|
|
51
|
+
# carries CVE-2026-119194/-119195/-119196/-119197 (fixed in 0.6.5); pin a
|
|
52
|
+
# floor so the restore step's pip-audit stays green.
|
|
53
|
+
"pyasn1>=0.6.5",
|
|
54
|
+
# pypdf is a transitive dep (openhands-tools document loaders). 6.21.2
|
|
55
|
+
# carries CVE-2026-119203/-119204/-119205/-119206/-119207/-119208/-119209
|
|
56
|
+
# (fixed in 6.21.4); pin a floor so the restore step's pip-audit stays green.
|
|
57
|
+
"pypdf>=6.21.4",
|
|
50
58
|
]
|
|
51
59
|
|
|
52
60
|
[project.urls]
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/core/__init__.py
RENAMED
|
@@ -6,8 +6,7 @@ test fails if this package imports engine/viz or heavy deps.
|
|
|
6
6
|
|
|
7
7
|
from .config import (PipelineConfig, StepConfig, LlmConfig, IsolationConfig,
|
|
8
8
|
VerdictSpec, Route, route_target, load_config,
|
|
9
|
-
default_state_dir, TERMINALS, MountSpec, EgressSpec
|
|
10
|
-
StopWhenSpec)
|
|
9
|
+
default_state_dir, TERMINALS, MountSpec, EgressSpec)
|
|
11
10
|
from .results import Result
|
|
12
11
|
from .verdict import Verdict, VerdictError, parse_verdict, RETRY
|
|
13
12
|
|
|
@@ -15,6 +14,5 @@ __all__ = [
|
|
|
15
14
|
"PipelineConfig", "StepConfig", "LlmConfig", "IsolationConfig",
|
|
16
15
|
"VerdictSpec", "Route", "route_target", "load_config",
|
|
17
16
|
"default_state_dir", "TERMINALS", "MountSpec", "EgressSpec",
|
|
18
|
-
"StopWhenSpec",
|
|
19
17
|
"Result", "Verdict", "VerdictError", "parse_verdict", "RETRY",
|
|
20
18
|
]
|
|
@@ -306,36 +306,6 @@ class EgressSpec(BaseModel):
|
|
|
306
306
|
return [_validate_egress_target(t) for t in v]
|
|
307
307
|
|
|
308
308
|
|
|
309
|
-
class StopWhenSpec(BaseModel):
|
|
310
|
-
"""Optional ``stop_when:`` termination signal on an agent step (032).
|
|
311
|
-
|
|
312
|
-
``green_command`` is a regex matched against the agent's terminal
|
|
313
|
-
observation command; a matching observation that exits 0 - and no
|
|
314
|
-
workspace writes since - triggers one nudge turn, then
|
|
315
|
-
``post_green_turns`` further turns before the engine closes the
|
|
316
|
-
conversation. Absent ``stop_when`` keeps the current run-to-finish
|
|
317
|
-
behaviour unchanged.
|
|
318
|
-
"""
|
|
319
|
-
|
|
320
|
-
model_config = ConfigDict(extra="forbid")
|
|
321
|
-
|
|
322
|
-
green_command: str
|
|
323
|
-
post_green_turns: int = Field(default=2, ge=0)
|
|
324
|
-
|
|
325
|
-
@field_validator("green_command")
|
|
326
|
-
@classmethod
|
|
327
|
-
def _valid_regex(cls, v: str) -> str:
|
|
328
|
-
if not isinstance(v, str) or not v.strip():
|
|
329
|
-
raise ValueError(
|
|
330
|
-
"stop_when.green_command must be a non-empty string")
|
|
331
|
-
try:
|
|
332
|
-
re.compile(v)
|
|
333
|
-
except re.error as e:
|
|
334
|
-
raise ValueError(
|
|
335
|
-
f"stop_when.green_command is not a valid regex: {e}") from e
|
|
336
|
-
return v
|
|
337
|
-
|
|
338
|
-
|
|
339
309
|
class BriefInputsSpec(BaseModel):
|
|
340
310
|
"""Optional ``brief_inputs:`` on an agent step (033): the closed input
|
|
341
311
|
list a brief agent digests once per run. ``include`` is repo-relative
|
|
@@ -387,7 +357,6 @@ class StepConfig(BaseModel):
|
|
|
387
357
|
max_iterations: int | None = None
|
|
388
358
|
timeout: float | None = Field(default=None, gt=0) # seconds, command steps only
|
|
389
359
|
terminal_timeout: float | None = Field(default=None, gt=0) # seconds, agent steps only (032)
|
|
390
|
-
stop_when: StopWhenSpec | None = None # agent steps only (032)
|
|
391
360
|
finalize_nudge_turns: int | None = Field(default=None, gt=0) # agent steps only (035)
|
|
392
361
|
brief_inputs: BriefInputsSpec | None = None # agent steps only (033)
|
|
393
362
|
custom_prompt: str | None = None # agent steps only (040); file path resolved at load
|
|
@@ -488,10 +457,6 @@ class StepConfig(BaseModel):
|
|
|
488
457
|
raise ValueError(
|
|
489
458
|
f"step {self.name!r}: 'terminal_timeout' requires 'agent'"
|
|
490
459
|
)
|
|
491
|
-
if self.stop_when is not None and self.agent is None:
|
|
492
|
-
raise ValueError(
|
|
493
|
-
f"step {self.name!r}: 'stop_when' requires 'agent'"
|
|
494
|
-
)
|
|
495
460
|
if self.finalize_nudge_turns is not None and self.agent is None:
|
|
496
461
|
raise ValueError(
|
|
497
462
|
f"step {self.name!r}: 'finalize_nudge_turns' requires 'agent'"
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
@@ -21,7 +21,6 @@ from __future__ import annotations
|
|
|
21
21
|
|
|
22
22
|
import hashlib
|
|
23
23
|
import os
|
|
24
|
-
import re
|
|
25
24
|
import subprocess
|
|
26
25
|
import sys
|
|
27
26
|
import traceback
|
|
@@ -70,9 +69,9 @@ def custom_prompt_section(text: str) -> str:
|
|
|
70
69
|
|
|
71
70
|
|
|
72
71
|
class _TurnPauseController:
|
|
73
|
-
"""Turn-level pause callback
|
|
72
|
+
"""Turn-level pause callback: pause the SDK run loop after every
|
|
74
73
|
agent-sourced event so the caller can drive one turn at a time. The
|
|
75
|
-
finalize nudge (035) reuses this hook
|
|
74
|
+
finalize nudge (035) reuses this hook."""
|
|
76
75
|
|
|
77
76
|
def __init__(self) -> None:
|
|
78
77
|
self.conversation = None
|
|
@@ -108,65 +107,6 @@ class _FinalizeNudgeController(_TurnPauseController):
|
|
|
108
107
|
return self._TEMPLATE.format(n=self.turns)
|
|
109
108
|
|
|
110
109
|
|
|
111
|
-
class _StopWhenController(_TurnPauseController):
|
|
112
|
-
"""Turn-level stop-when-green observation (032).
|
|
113
|
-
|
|
114
|
-
Pure per-event state machine - no knowledge of how the conversation is
|
|
115
|
-
driven. ``on_event`` is a SDK conversation callback: it records the most
|
|
116
|
-
recent matching terminal observation and any subsequent file-editor
|
|
117
|
-
write, and (via the base pause) pauses the conversation after every
|
|
118
|
-
agent-sourced event so the caller can drive one turn at a time.
|
|
119
|
-
"""
|
|
120
|
-
|
|
121
|
-
NUDGE = "Step complete: produce your final response now"
|
|
122
|
-
_WRITE_COMMANDS = frozenset({"create", "str_replace", "insert", "undo_edit"})
|
|
123
|
-
|
|
124
|
-
def __init__(self, spec) -> None:
|
|
125
|
-
super().__init__()
|
|
126
|
-
self._pattern = re.compile(spec.green_command)
|
|
127
|
-
self.post_green_turns = spec.post_green_turns
|
|
128
|
-
self.green_hit = False
|
|
129
|
-
self.writes_since_green = False
|
|
130
|
-
self.nudged = False
|
|
131
|
-
self.post_nudge_turns = 0
|
|
132
|
-
|
|
133
|
-
@property
|
|
134
|
-
def green_signal(self) -> bool:
|
|
135
|
-
return self.green_hit and not self.writes_since_green
|
|
136
|
-
|
|
137
|
-
def on_event(self, event) -> None:
|
|
138
|
-
super().on_event(event)
|
|
139
|
-
if self.conversation is None:
|
|
140
|
-
return
|
|
141
|
-
from openhands.sdk.event import ObservationEvent
|
|
142
|
-
|
|
143
|
-
if isinstance(event, ObservationEvent):
|
|
144
|
-
self._record_observation(event)
|
|
145
|
-
|
|
146
|
-
def on_terminal_result(self, command: str | None, exit_code: int | None,
|
|
147
|
-
wrote_workspace: bool) -> None:
|
|
148
|
-
"""Feed the sandbox terminal result channel (032): a matching green
|
|
149
|
-
command is the signal; any other terminal write resets it."""
|
|
150
|
-
if command is not None and self._pattern.search(command):
|
|
151
|
-
self.green_hit = exit_code == 0
|
|
152
|
-
self.writes_since_green = False
|
|
153
|
-
elif wrote_workspace:
|
|
154
|
-
self.writes_since_green = True
|
|
155
|
-
|
|
156
|
-
def _record_observation(self, event) -> None:
|
|
157
|
-
observation = event.observation
|
|
158
|
-
tool_name = event.tool_name
|
|
159
|
-
if tool_name == "terminal":
|
|
160
|
-
command = getattr(observation, "command", None)
|
|
161
|
-
if command is not None and self._pattern.search(command):
|
|
162
|
-
self.green_hit = getattr(observation, "exit_code", None) == 0
|
|
163
|
-
self.writes_since_green = False
|
|
164
|
-
elif tool_name == "file_editor":
|
|
165
|
-
command = getattr(observation, "command", None)
|
|
166
|
-
if command in self._WRITE_COMMANDS and not observation.is_error:
|
|
167
|
-
self.writes_since_green = True
|
|
168
|
-
|
|
169
|
-
|
|
170
110
|
@dataclass
|
|
171
111
|
class Runtime:
|
|
172
112
|
"""Everything stage nodes need that isn't in LoopState: pipeline config,
|
|
@@ -201,9 +141,6 @@ class Runtime:
|
|
|
201
141
|
try:
|
|
202
142
|
from openhands.sdk.conversation.state import ConversationExecutionStatus
|
|
203
143
|
|
|
204
|
-
controller = None
|
|
205
|
-
if step is not None and step.stop_when is not None:
|
|
206
|
-
controller = _StopWhenController(step.stop_when)
|
|
207
144
|
finalize_controller = None
|
|
208
145
|
if step is not None and step.finalize_nudge_turns is not None:
|
|
209
146
|
finalize_controller = _FinalizeNudgeController(
|
|
@@ -232,29 +169,23 @@ class Runtime:
|
|
|
232
169
|
egress_policy=egress_policy,
|
|
233
170
|
terminal_timeout=(step.terminal_timeout
|
|
234
171
|
if step is not None else None),
|
|
235
|
-
on_terminal_result=(controller.on_terminal_result
|
|
236
|
-
if controller is not None else None),
|
|
237
172
|
secrets=(step_secret_env(self.cfg, step, self.secrets)
|
|
238
173
|
if step is not None else None),
|
|
239
174
|
forbidden_commands=forbidden_commands,
|
|
240
175
|
allowlist=allowlist)
|
|
241
176
|
agent = load_agent(self.agents_dir, agent_name, self.llm)
|
|
242
177
|
callbacks = []
|
|
243
|
-
if controller is not None:
|
|
244
|
-
callbacks.append(controller.on_event)
|
|
245
178
|
if finalize_controller is not None:
|
|
246
179
|
callbacks.append(finalize_controller.on_event)
|
|
247
180
|
conv = Conversation(agent=agent, workspace=str(self.workspace),
|
|
248
181
|
max_iteration_per_run=max_iterations,
|
|
249
182
|
callbacks=callbacks or None)
|
|
250
|
-
if controller is not None:
|
|
251
|
-
controller.attach(conv)
|
|
252
183
|
if finalize_controller is not None:
|
|
253
184
|
finalize_controller.attach(conv)
|
|
254
185
|
conv.send_message(message)
|
|
255
|
-
if
|
|
256
|
-
self.
|
|
257
|
-
|
|
186
|
+
if finalize_controller is not None:
|
|
187
|
+
self._run_finalize_loop(conv, max_iterations,
|
|
188
|
+
finalize_controller)
|
|
258
189
|
else:
|
|
259
190
|
conv.run()
|
|
260
191
|
|
|
@@ -295,10 +226,9 @@ class Runtime:
|
|
|
295
226
|
if conv is not None:
|
|
296
227
|
conv.close()
|
|
297
228
|
|
|
298
|
-
def
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
finalize_controller: _FinalizeNudgeController | None = None) -> None:
|
|
229
|
+
def _run_finalize_loop(self, conv,
|
|
230
|
+
max_iterations: int,
|
|
231
|
+
finalize_controller: _FinalizeNudgeController | None = None) -> None:
|
|
302
232
|
"""Drive one turn at a time so turn-level hooks can inject their
|
|
303
233
|
nudges and enforce budgets. A controller's callback pauses the SDK
|
|
304
234
|
run loop after every agent-sourced event, so each ``conv.run()`` here
|
|
@@ -327,19 +257,6 @@ class Runtime:
|
|
|
327
257
|
finalize_controller.nudged = True
|
|
328
258
|
continue
|
|
329
259
|
|
|
330
|
-
if controller is not None:
|
|
331
|
-
if not controller.nudged and controller.green_signal:
|
|
332
|
-
conv.send_message(controller.NUDGE)
|
|
333
|
-
controller.nudged = True
|
|
334
|
-
if controller.post_green_turns == 0:
|
|
335
|
-
break
|
|
336
|
-
continue
|
|
337
|
-
|
|
338
|
-
if controller.nudged:
|
|
339
|
-
controller.post_nudge_turns += 1
|
|
340
|
-
if controller.post_nudge_turns >= controller.post_green_turns:
|
|
341
|
-
break
|
|
342
|
-
|
|
343
260
|
|
|
344
261
|
def prompt_dir(runtime: Runtime) -> str:
|
|
345
262
|
"""state_dir as agents must address it. Agents resolve prompt paths
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43/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.0a43
|
|
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
|
|
@@ -33,6 +33,8 @@ Requires-Dist: agent-client-protocol<1.0.0
|
|
|
33
33
|
Requires-Dist: langgraph>=1.2
|
|
34
34
|
Requires-Dist: langgraph-checkpoint-sqlite>=3.0
|
|
35
35
|
Requires-Dist: filelock>=4.2.1
|
|
36
|
+
Requires-Dist: pyasn1>=0.6.5
|
|
37
|
+
Requires-Dist: pypdf>=6.21.4
|
|
36
38
|
Provides-Extra: dev
|
|
37
39
|
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
38
40
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
@@ -213,7 +215,6 @@ steps:
|
|
|
213
215
|
agent: dev
|
|
214
216
|
produces: implementation-summary.md
|
|
215
217
|
terminal_timeout: 900
|
|
216
|
-
stop_when: { green_command: ".venv-sandbox/bin/python -m pytest tests/ -q", post_green_turns: 2 }
|
|
217
218
|
on_pass: test
|
|
218
219
|
|
|
219
220
|
- name: test
|
|
@@ -249,7 +250,6 @@ steps:
|
|
|
249
250
|
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
|
|
250
251
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
251
252
|
| `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. |
|
|
252
|
-
| `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. |
|
|
253
253
|
| `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. |
|
|
254
254
|
| `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). |
|
|
255
255
|
| `custom_prompt:` | Planned (planning-docs/040). Optional agent-step value: literal text, or a path whose file contents are read at load time, appended verbatim to the agent prompt at context assembly (after the prompt, before the task/artifacts). A value that looks like a path but names no file is a validation error. Agent steps only. |
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
@@ -51,7 +51,6 @@ src/agentic_dev_loop/viz/__main__.py
|
|
|
51
51
|
src/agentic_dev_loop/viz/server.py
|
|
52
52
|
tests/test_028_declared_caches.py
|
|
53
53
|
tests/test_029_merge_on_conflict.py
|
|
54
|
-
tests/test_032_stop_when_green.py
|
|
55
54
|
tests/test_033_brief_step.py
|
|
56
55
|
tests/test_035_finalize_nudge.py
|
|
57
56
|
tests/test_036_banned_commands_and_lane_boundary.py
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""Finalize nudge (035): config surface, the turn-index injection, and the
|
|
2
|
-
exactly-once guard on the shared
|
|
2
|
+
exactly-once guard on the shared turn loop."""
|
|
3
3
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from types import SimpleNamespace
|
|
@@ -90,6 +90,13 @@ class TestFinalizeNudgeConfig:
|
|
|
90
90
|
with pytest.raises(ValidationError, match="requires 'agent'"):
|
|
91
91
|
_load(tmp_path, data)
|
|
92
92
|
|
|
93
|
+
def test_stop_when_key_rejected(self, tmp_path):
|
|
94
|
+
data = _base()
|
|
95
|
+
data["steps"] = [{"name": "dev", "agent": "dev",
|
|
96
|
+
"stop_when": {"green_command": "pytest"}}]
|
|
97
|
+
with pytest.raises(ValidationError, match="stop_when"):
|
|
98
|
+
_load(tmp_path, data)
|
|
99
|
+
|
|
93
100
|
|
|
94
101
|
class TestFinalizeNudgeLoop:
|
|
95
102
|
def _conv(self, finish_after):
|
|
@@ -114,7 +121,7 @@ class TestFinalizeNudgeLoop:
|
|
|
114
121
|
def test_nudge_at_configured_turn_and_only_once(self):
|
|
115
122
|
conv = self._conv(finish_after=9)
|
|
116
123
|
controller = _FinalizeNudgeController(3)
|
|
117
|
-
Runtime.
|
|
124
|
+
Runtime._run_finalize_loop(None, conv, max_iterations=10,
|
|
118
125
|
finalize_controller=controller)
|
|
119
126
|
assert controller.nudged is True
|
|
120
127
|
assert conv.sent == [(7, NUDGE)]
|
|
@@ -122,7 +129,7 @@ class TestFinalizeNudgeLoop:
|
|
|
122
129
|
def test_no_nudge_when_finished_before_threshold(self):
|
|
123
130
|
conv = self._conv(finish_after=3)
|
|
124
131
|
controller = _FinalizeNudgeController(3)
|
|
125
|
-
Runtime.
|
|
132
|
+
Runtime._run_finalize_loop(None, conv, max_iterations=10,
|
|
126
133
|
finalize_controller=controller)
|
|
127
134
|
assert controller.nudged is False
|
|
128
135
|
assert conv.sent == []
|
|
@@ -130,7 +137,7 @@ class TestFinalizeNudgeLoop:
|
|
|
130
137
|
def test_turn_cap_still_applies_when_nudge_ignored(self):
|
|
131
138
|
conv = self._conv(finish_after=999)
|
|
132
139
|
controller = _FinalizeNudgeController(3)
|
|
133
|
-
Runtime.
|
|
140
|
+
Runtime._run_finalize_loop(None, conv, max_iterations=5,
|
|
134
141
|
finalize_controller=controller)
|
|
135
142
|
assert controller.nudged is True
|
|
136
143
|
assert conv.state.execution_status == ConversationExecutionStatus.ERROR
|
|
@@ -188,6 +188,37 @@ class TestSandboxTerminalExecutor:
|
|
|
188
188
|
assert fake_sandbox.requests[0].network is False
|
|
189
189
|
|
|
190
190
|
|
|
191
|
+
class TestTerminalTimeoutOverride:
|
|
192
|
+
def test_defaults_to_terminal_timeout(self, fake_sandbox, tmp_path):
|
|
193
|
+
from openhands.tools.terminal import TerminalAction
|
|
194
|
+
|
|
195
|
+
from agentic_dev_loop.engine.agents import SandboxTerminalExecutor
|
|
196
|
+
ex = SandboxTerminalExecutor(
|
|
197
|
+
fake_sandbox, workspace=str(tmp_path), env={"PATH": "/bin"},
|
|
198
|
+
terminal_timeout=900)
|
|
199
|
+
ex(TerminalAction(command="echo hi"), None)
|
|
200
|
+
assert fake_sandbox.requests[0].timeout == 900
|
|
201
|
+
|
|
202
|
+
def test_action_timeout_wins(self, fake_sandbox, tmp_path):
|
|
203
|
+
from openhands.tools.terminal import TerminalAction
|
|
204
|
+
|
|
205
|
+
from agentic_dev_loop.engine.agents import SandboxTerminalExecutor
|
|
206
|
+
ex = SandboxTerminalExecutor(
|
|
207
|
+
fake_sandbox, workspace=str(tmp_path), env={"PATH": "/bin"},
|
|
208
|
+
terminal_timeout=900)
|
|
209
|
+
ex(TerminalAction(command="echo hi", timeout=30), None)
|
|
210
|
+
assert fake_sandbox.requests[0].timeout == 30
|
|
211
|
+
|
|
212
|
+
def test_unset_keeps_none(self, fake_sandbox, tmp_path):
|
|
213
|
+
from openhands.tools.terminal import TerminalAction
|
|
214
|
+
|
|
215
|
+
from agentic_dev_loop.engine.agents import SandboxTerminalExecutor
|
|
216
|
+
ex = SandboxTerminalExecutor(
|
|
217
|
+
fake_sandbox, workspace=str(tmp_path), env={"PATH": "/bin"})
|
|
218
|
+
ex(TerminalAction(command="echo hi"), None)
|
|
219
|
+
assert fake_sandbox.requests[0].timeout is None
|
|
220
|
+
|
|
221
|
+
|
|
191
222
|
class TestRegisterSandboxTerminal:
|
|
192
223
|
def test_resolves_to_sandbox_executor(self, fake_sandbox, tmp_path):
|
|
193
224
|
from openhands.sdk.tool.registry import resolve_tool
|
|
@@ -331,6 +331,30 @@ def test_timeout_without_command_rejected(tmp_path):
|
|
|
331
331
|
_load(tmp_path, data)
|
|
332
332
|
|
|
333
333
|
|
|
334
|
+
def test_terminal_timeout_round_trip(tmp_path):
|
|
335
|
+
data = _base()
|
|
336
|
+
data["steps"][0]["terminal_timeout"] = 900
|
|
337
|
+
assert _load(tmp_path, data).steps[0].terminal_timeout == 900
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
@pytest.mark.parametrize("step", [
|
|
341
|
+
{"name": "x", "command": "true", "terminal_timeout": 900},
|
|
342
|
+
{"name": "x", "action": "commit", "terminal_timeout": 900},
|
|
343
|
+
])
|
|
344
|
+
def test_terminal_timeout_requires_agent(tmp_path, step):
|
|
345
|
+
data = _base()
|
|
346
|
+
data["steps"] = [step]
|
|
347
|
+
with pytest.raises(ValidationError, match="'terminal_timeout' requires 'agent'"):
|
|
348
|
+
_load(tmp_path, data)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def test_negative_terminal_timeout_rejected(tmp_path):
|
|
352
|
+
data = _base()
|
|
353
|
+
data["steps"][0]["terminal_timeout"] = -1
|
|
354
|
+
with pytest.raises(ValidationError):
|
|
355
|
+
_load(tmp_path, data)
|
|
356
|
+
|
|
357
|
+
|
|
334
358
|
class TestCustomPrompt:
|
|
335
359
|
def test_defaults_none(self, tmp_path):
|
|
336
360
|
assert _load(tmp_path, _base()).steps[0].custom_prompt is None
|
|
@@ -91,29 +91,29 @@ def test_canary_sdk_terminal_seam_still_present():
|
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
def test_canary_sdk_turn_level_pause_seam_still_present():
|
|
94
|
-
"""Canary for the
|
|
94
|
+
"""Canary for the 035 finalize-nudge turn loop seam (COMPAT-SHIMS.md):
|
|
95
95
|
passes while the SDK still accepts ``callbacks`` on LocalConversation,
|
|
96
96
|
passes agent-sourced events to them via ``agent.step(on_event=...)``, and
|
|
97
97
|
breaks its run loop on PAUSED; fails once any of those disappear - the
|
|
98
|
-
|
|
98
|
+
_TurnPauseController pause loop is then dead and must be replaced with the
|
|
99
99
|
SDK's supported turn-level pause mechanism."""
|
|
100
100
|
import inspect
|
|
101
101
|
|
|
102
102
|
from openhands.sdk.conversation.impl.local_conversation import LocalConversation
|
|
103
103
|
|
|
104
104
|
assert "callbacks" in inspect.signature(LocalConversation.__init__).parameters, (
|
|
105
|
-
"The SDK's LocalConversation no longer accepts `callbacks=` - the
|
|
106
|
-
"
|
|
105
|
+
"The SDK's LocalConversation no longer accepts `callbacks=` - the 035 "
|
|
106
|
+
"finalize-nudge turn loop seam in src/agentic_dev_loop/engine/stages.py "
|
|
107
107
|
"is dead. Adopt the SDK's supported pause mechanism and update "
|
|
108
108
|
"COMPAT-SHIMS.md.")
|
|
109
109
|
run_src = inspect.getsource(LocalConversation.run)
|
|
110
110
|
assert "on_event=self._on_event" in run_src, (
|
|
111
111
|
"The SDK's LocalConversation.run no longer passes its composed callback "
|
|
112
|
-
"to agent.step - the
|
|
112
|
+
"to agent.step - the 035 callback seam no longer receives agent-sourced "
|
|
113
113
|
"events. Update COMPAT-SHIMS.md.")
|
|
114
114
|
assert "ConversationExecutionStatus.PAUSED" in run_src, (
|
|
115
|
-
"The SDK's LocalConversation.run no longer breaks on PAUSED - the
|
|
116
|
-
"
|
|
115
|
+
"The SDK's LocalConversation.run no longer breaks on PAUSED - the 035 "
|
|
116
|
+
"finalize-nudge turn loop in src/agentic_dev_loop/engine/stages.py is dead. "
|
|
117
117
|
"Adopt the SDK's supported pause mechanism and update COMPAT-SHIMS.md.")
|
|
118
118
|
|
|
119
119
|
|
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
"""Stop-when-green (032): config surface, the per-event signal state machine,
|
|
2
|
-
the one-turn run-loop budget, and the terminal timeout override."""
|
|
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
|
-
from openhands.sdk.event import ActionEvent, MessageEvent, ObservationEvent
|
|
14
|
-
from openhands.sdk.llm.message import Message, MessageToolCall, TextContent
|
|
15
|
-
from openhands.tools.file_editor import FileEditorObservation
|
|
16
|
-
from openhands.tools.terminal import TerminalAction, TerminalObservation
|
|
17
|
-
from openhands.tools.terminal.metadata import CmdOutputMetadata
|
|
18
|
-
|
|
19
|
-
from agentic_dev_loop.core.config import PipelineConfig, StopWhenSpec, load_config
|
|
20
|
-
from agentic_dev_loop.engine.agents import SandboxTerminalExecutor
|
|
21
|
-
from agentic_dev_loop.engine.registry import default_registry
|
|
22
|
-
from agentic_dev_loop.engine.stages import Runtime, _StopWhenController
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _base() -> dict:
|
|
26
|
-
return {
|
|
27
|
-
"name": "t",
|
|
28
|
-
"llm": {"model": "m", "api_key_env": "K"},
|
|
29
|
-
"agents_dir": "agents",
|
|
30
|
-
"lockfiles": ["uv.lock"],
|
|
31
|
-
"steps": [{"name": "dev", "agent": "dev", "on_pass": "success"}],
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _load(tmp_path, data):
|
|
36
|
-
p = tmp_path / "pipeline.yaml"
|
|
37
|
-
p.write_text(yaml.safe_dump(data))
|
|
38
|
-
return load_config(p)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _terminal_obs(command, exit_code):
|
|
42
|
-
return TerminalObservation.from_text(
|
|
43
|
-
"output", command=command, exit_code=exit_code,
|
|
44
|
-
metadata=CmdOutputMetadata(exit_code=exit_code))
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _terminal_event(command, exit_code, idx=0):
|
|
48
|
-
return ObservationEvent(
|
|
49
|
-
tool_name="terminal",
|
|
50
|
-
tool_call_id=f"tc-{idx}",
|
|
51
|
-
action_id=f"a-{idx}",
|
|
52
|
-
observation=_terminal_obs(command, exit_code))
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def _file_write_event(command="create", idx=0, is_error=False):
|
|
56
|
-
obs = FileEditorObservation(
|
|
57
|
-
command=command, path="src/x.py", prev_exist=False,
|
|
58
|
-
old_content=None, new_content="x")
|
|
59
|
-
obs = obs.model_copy(update={"is_error": is_error})
|
|
60
|
-
return ObservationEvent(
|
|
61
|
-
tool_name="file_editor",
|
|
62
|
-
tool_call_id=f"fc-{idx}",
|
|
63
|
-
action_id=f"fa-{idx}",
|
|
64
|
-
observation=obs)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _agent_event(idx=0):
|
|
68
|
-
return ActionEvent(
|
|
69
|
-
thought=[TextContent(type="text", text="working")],
|
|
70
|
-
tool_name="terminal",
|
|
71
|
-
tool_call_id=f"ag-{idx}",
|
|
72
|
-
tool_call=MessageToolCall(
|
|
73
|
-
id=f"ag-{idx}", name="terminal", arguments="{}",
|
|
74
|
-
origin="completion"),
|
|
75
|
-
llm_response_id=f"r-{idx}",
|
|
76
|
-
action=TerminalAction(command="echo hi"))
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _controller(green_command="pytest"):
|
|
80
|
-
return _StopWhenController(StopWhenSpec(green_command=green_command))
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class TestStopWhenConfig:
|
|
84
|
-
def test_valid_agent_step_round_trip(self, tmp_path):
|
|
85
|
-
data = _base()
|
|
86
|
-
data["steps"] = [{"name": "dev", "agent": "dev", "on_pass": "success",
|
|
87
|
-
"stop_when": {"green_command": "pytest tests/"},
|
|
88
|
-
"terminal_timeout": 900}]
|
|
89
|
-
step = _load(tmp_path, data).steps[0]
|
|
90
|
-
assert step.stop_when == StopWhenSpec(green_command="pytest tests/")
|
|
91
|
-
assert step.stop_when.post_green_turns == 2
|
|
92
|
-
assert step.terminal_timeout == 900
|
|
93
|
-
|
|
94
|
-
def test_post_green_turns_custom(self, tmp_path):
|
|
95
|
-
data = _base()
|
|
96
|
-
data["steps"] = [{"name": "dev", "agent": "dev", "on_pass": "success",
|
|
97
|
-
"stop_when": {"green_command": "pytest",
|
|
98
|
-
"post_green_turns": 0}}]
|
|
99
|
-
assert _load(tmp_path, data).steps[0].stop_when.post_green_turns == 0
|
|
100
|
-
|
|
101
|
-
@pytest.mark.parametrize("bad", ["[", ""])
|
|
102
|
-
def test_invalid_regex_rejected(self, tmp_path, bad):
|
|
103
|
-
data = _base()
|
|
104
|
-
data["steps"] = [{"name": "dev", "agent": "dev",
|
|
105
|
-
"stop_when": {"green_command": bad}}]
|
|
106
|
-
with pytest.raises(ValidationError, match="green_command"):
|
|
107
|
-
_load(tmp_path, data)
|
|
108
|
-
|
|
109
|
-
def test_negative_post_green_turns_rejected(self, tmp_path):
|
|
110
|
-
data = _base()
|
|
111
|
-
data["steps"] = [{"name": "dev", "agent": "dev",
|
|
112
|
-
"stop_when": {"green_command": "pytest",
|
|
113
|
-
"post_green_turns": -1}}]
|
|
114
|
-
with pytest.raises(ValidationError):
|
|
115
|
-
_load(tmp_path, data)
|
|
116
|
-
|
|
117
|
-
@pytest.mark.parametrize("step", [
|
|
118
|
-
{"name": "test", "command": "true", "stop_when": {"green_command": "pytest"}},
|
|
119
|
-
{"name": "commit", "action": "commit", "stop_when": {"green_command": "pytest"}},
|
|
120
|
-
{"name": "test", "command": "true", "terminal_timeout": 900},
|
|
121
|
-
{"name": "commit", "action": "commit", "terminal_timeout": 900},
|
|
122
|
-
])
|
|
123
|
-
def test_agent_only_fields_rejected_on_non_agent(self, tmp_path, step):
|
|
124
|
-
data = _base()
|
|
125
|
-
data["steps"] = [step]
|
|
126
|
-
with pytest.raises(ValidationError, match="requires 'agent'"):
|
|
127
|
-
_load(tmp_path, data)
|
|
128
|
-
|
|
129
|
-
def test_negative_terminal_timeout_rejected(self, tmp_path):
|
|
130
|
-
data = _base()
|
|
131
|
-
data["steps"] = [{"name": "dev", "agent": "dev",
|
|
132
|
-
"terminal_timeout": -1}]
|
|
133
|
-
with pytest.raises(ValidationError):
|
|
134
|
-
_load(tmp_path, data)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
class TestStopWhenController:
|
|
138
|
-
def test_matching_green_terminal_sets_signal(self):
|
|
139
|
-
c = _controller()
|
|
140
|
-
c.attach(SimpleNamespace(state=SimpleNamespace(execution_status=None)))
|
|
141
|
-
c.on_event(_agent_event(0))
|
|
142
|
-
c.on_event(_terminal_event("pytest tests/ -q", 0))
|
|
143
|
-
assert c.green_signal is True
|
|
144
|
-
|
|
145
|
-
def test_matching_failure_clears_signal(self):
|
|
146
|
-
c = _controller()
|
|
147
|
-
c.attach(SimpleNamespace(state=SimpleNamespace(execution_status=None)))
|
|
148
|
-
c.on_event(_agent_event(0))
|
|
149
|
-
c.on_event(_terminal_event("pytest tests/ -q", 1))
|
|
150
|
-
assert c.green_signal is False
|
|
151
|
-
|
|
152
|
-
def test_file_write_after_green_resets_signal(self):
|
|
153
|
-
c = _controller()
|
|
154
|
-
c.attach(SimpleNamespace(state=SimpleNamespace(execution_status=None)))
|
|
155
|
-
c.on_event(_agent_event(0))
|
|
156
|
-
c.on_event(_terminal_event("pytest tests/ -q", 0))
|
|
157
|
-
c.on_event(_file_write_event("create"))
|
|
158
|
-
assert c.green_signal is False
|
|
159
|
-
|
|
160
|
-
def test_file_view_does_not_reset_signal(self):
|
|
161
|
-
c = _controller()
|
|
162
|
-
c.attach(SimpleNamespace(state=SimpleNamespace(execution_status=None)))
|
|
163
|
-
c.on_event(_agent_event(0))
|
|
164
|
-
c.on_event(_terminal_event("pytest tests/ -q", 0))
|
|
165
|
-
c.on_event(_file_write_event("view"))
|
|
166
|
-
assert c.green_signal is True
|
|
167
|
-
|
|
168
|
-
def test_later_nonmatching_terminal_keeps_signal(self):
|
|
169
|
-
c = _controller()
|
|
170
|
-
c.attach(SimpleNamespace(state=SimpleNamespace(execution_status=None)))
|
|
171
|
-
c.on_event(_agent_event(0))
|
|
172
|
-
c.on_event(_terminal_event("pytest tests/ -q", 0))
|
|
173
|
-
c.on_event(_terminal_event("git status", 0))
|
|
174
|
-
assert c.green_signal is True
|
|
175
|
-
|
|
176
|
-
def test_later_matching_failure_replaces_signal(self):
|
|
177
|
-
c = _controller()
|
|
178
|
-
c.attach(SimpleNamespace(state=SimpleNamespace(execution_status=None)))
|
|
179
|
-
c.on_event(_agent_event(0))
|
|
180
|
-
c.on_event(_terminal_event("pytest tests/ -q", 0))
|
|
181
|
-
c.on_event(_terminal_event("pytest tests/ -q", 2))
|
|
182
|
-
assert c.green_signal is False
|
|
183
|
-
|
|
184
|
-
def test_agent_event_pauses_conversation(self):
|
|
185
|
-
c = _controller()
|
|
186
|
-
conv = SimpleNamespace(state=SimpleNamespace(execution_status=None))
|
|
187
|
-
c.attach(conv)
|
|
188
|
-
c.on_event(_agent_event(0))
|
|
189
|
-
assert conv.state.execution_status is not None
|
|
190
|
-
assert conv.state.execution_status.name == "PAUSED"
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
class TestTerminalWriteChannel:
|
|
194
|
-
def test_terminal_result_green_then_write_resets(self):
|
|
195
|
-
c = _controller()
|
|
196
|
-
c.on_terminal_result("pytest tests/ -q", 0, False)
|
|
197
|
-
assert c.green_signal is True
|
|
198
|
-
c.on_terminal_result("git diff", 0, True)
|
|
199
|
-
assert c.green_signal is False
|
|
200
|
-
|
|
201
|
-
def test_terminal_result_matching_failure_clears(self):
|
|
202
|
-
c = _controller()
|
|
203
|
-
c.on_terminal_result("pytest tests/ -q", 0, False)
|
|
204
|
-
c.on_terminal_result("pytest tests/ -q", 1, False)
|
|
205
|
-
assert c.green_signal is False
|
|
206
|
-
|
|
207
|
-
def test_executor_reports_writes(self, fake_sandbox, tmp_path):
|
|
208
|
-
seen = []
|
|
209
|
-
ex = SandboxTerminalExecutor(
|
|
210
|
-
fake_sandbox, workspace=str(tmp_path), env={"PATH": "/bin"},
|
|
211
|
-
on_terminal_result=lambda command, exit_code, wrote: seen.append(
|
|
212
|
-
(command, exit_code, wrote)))
|
|
213
|
-
fake_sandbox.script(lambda req: (0, "ok", False, True))
|
|
214
|
-
ex(TerminalAction(command="echo hi"), None)
|
|
215
|
-
assert seen == [("echo hi", 0, True)]
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
class TestStopWhenRunLoop:
|
|
219
|
-
def _conv(self, controller):
|
|
220
|
-
class Fake:
|
|
221
|
-
def __init__(self):
|
|
222
|
-
self.state = SimpleNamespace(execution_status=None)
|
|
223
|
-
self.messages = []
|
|
224
|
-
self.run_calls = 0
|
|
225
|
-
|
|
226
|
-
def run(self):
|
|
227
|
-
self.run_calls += 1
|
|
228
|
-
# The controller callback normally pauses after the turn.
|
|
229
|
-
self.state.execution_status = SimpleNamespace(name="PAUSED")
|
|
230
|
-
|
|
231
|
-
def send_message(self, message):
|
|
232
|
-
self.messages.append(message)
|
|
233
|
-
|
|
234
|
-
return Fake()
|
|
235
|
-
|
|
236
|
-
def test_nudge_then_budget_close(self):
|
|
237
|
-
controller = _controller()
|
|
238
|
-
controller.green_hit = True
|
|
239
|
-
controller.writes_since_green = False
|
|
240
|
-
conv = self._conv(controller)
|
|
241
|
-
Runtime._run_stop_when_loop(None, conv, controller, max_iterations=10)
|
|
242
|
-
assert controller.nudged is True
|
|
243
|
-
assert conv.messages == [controller.NUDGE]
|
|
244
|
-
assert conv.run_calls == 1 + controller.post_green_turns
|
|
245
|
-
assert controller.post_nudge_turns == controller.post_green_turns
|
|
246
|
-
|
|
247
|
-
def test_post_green_turns_zero_closes_after_nudge(self):
|
|
248
|
-
controller = _StopWhenController(
|
|
249
|
-
StopWhenSpec(green_command="pytest", post_green_turns=0))
|
|
250
|
-
controller.green_hit = True
|
|
251
|
-
conv = self._conv(controller)
|
|
252
|
-
Runtime._run_stop_when_loop(None, conv, controller, max_iterations=10)
|
|
253
|
-
assert controller.nudged is True
|
|
254
|
-
assert conv.messages == [controller.NUDGE]
|
|
255
|
-
assert conv.run_calls == 1
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
class TestRunAgentStopWhen:
|
|
259
|
-
def _runtime(self, tmp_path):
|
|
260
|
-
cfg = PipelineConfig.model_validate({
|
|
261
|
-
"name": "t",
|
|
262
|
-
"llm": {"model": "m", "api_key_env": "K"},
|
|
263
|
-
"agents_dir": "agents",
|
|
264
|
-
"lockfiles": ["uv.lock"],
|
|
265
|
-
"steps": [{
|
|
266
|
-
"name": "dev",
|
|
267
|
-
"agent": "dev",
|
|
268
|
-
"produces": ["implementation-summary.md"],
|
|
269
|
-
"stop_when": {"green_command": "pytest", "post_green_turns": 2},
|
|
270
|
-
"on_pass": "success",
|
|
271
|
-
}],
|
|
272
|
-
})
|
|
273
|
-
state_dir = tmp_path / ".pr"
|
|
274
|
-
state_dir.mkdir()
|
|
275
|
-
agents_dir = tmp_path / "agents"
|
|
276
|
-
agents_dir.mkdir()
|
|
277
|
-
(agents_dir / "dev.agent.md").write_text(
|
|
278
|
-
"---\nname: dev\ntools: [terminal]\n---\nDo the work.")
|
|
279
|
-
return Runtime(
|
|
280
|
-
cfg=cfg, registry=default_registry(),
|
|
281
|
-
llm=LLM(model="openai/test-model", api_key="sk-test"),
|
|
282
|
-
agents_dir=agents_dir, state_dir=state_dir,
|
|
283
|
-
workspace=tmp_path, sandbox=None)
|
|
284
|
-
|
|
285
|
-
def _fake_conv(self, script):
|
|
286
|
-
class Fake:
|
|
287
|
-
def __init__(self, script):
|
|
288
|
-
self.state = SimpleNamespace(events=[], execution_status=None)
|
|
289
|
-
self.callbacks = []
|
|
290
|
-
self.messages = []
|
|
291
|
-
self.closed = False
|
|
292
|
-
self.script = list(script)
|
|
293
|
-
self.run_calls = 0
|
|
294
|
-
|
|
295
|
-
def send_message(self, message):
|
|
296
|
-
self.messages.append(message)
|
|
297
|
-
|
|
298
|
-
def run(self):
|
|
299
|
-
self.run_calls += 1
|
|
300
|
-
events, status = self.script.pop(0)
|
|
301
|
-
for event in events:
|
|
302
|
-
self.state.events.append(event)
|
|
303
|
-
for cb in self.callbacks:
|
|
304
|
-
cb(event)
|
|
305
|
-
self.state.execution_status = status
|
|
306
|
-
|
|
307
|
-
def close(self):
|
|
308
|
-
self.closed = True
|
|
309
|
-
|
|
310
|
-
return Fake(script)
|
|
311
|
-
|
|
312
|
-
def test_green_signal_nudges_closes_and_captures(self, tmp_path, monkeypatch):
|
|
313
|
-
runtime = self._runtime(tmp_path)
|
|
314
|
-
final = MessageEvent(
|
|
315
|
-
source="agent",
|
|
316
|
-
llm_message=Message(
|
|
317
|
-
role="assistant",
|
|
318
|
-
content=[TextContent(type="text", text="final summary")]))
|
|
319
|
-
script = [
|
|
320
|
-
([_agent_event(0), _terminal_event("pytest tests/ -q", 0)],
|
|
321
|
-
ConversationExecutionStatus.PAUSED),
|
|
322
|
-
([final], ConversationExecutionStatus.FINISHED),
|
|
323
|
-
]
|
|
324
|
-
conv = self._fake_conv(script)
|
|
325
|
-
|
|
326
|
-
def factory(**kw):
|
|
327
|
-
conv.callbacks = kw.get("callbacks") or []
|
|
328
|
-
return conv
|
|
329
|
-
|
|
330
|
-
monkeypatch.setattr(
|
|
331
|
-
"agentic_dev_loop.engine.stages.Conversation", factory)
|
|
332
|
-
|
|
333
|
-
result = runtime.run_agent("dev", "the task", max_iterations=10,
|
|
334
|
-
step=runtime.cfg.steps[0])
|
|
335
|
-
|
|
336
|
-
assert result.ok is True
|
|
337
|
-
assert result.response == "final summary"
|
|
338
|
-
assert result.truncated is False
|
|
339
|
-
assert conv.messages == ["the task", _StopWhenController.NUDGE]
|
|
340
|
-
assert conv.run_calls == 2
|
|
341
|
-
assert conv.closed is True
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
class TestTerminalTimeoutOverride:
|
|
345
|
-
def test_defaults_to_terminal_timeout(self, fake_sandbox, tmp_path):
|
|
346
|
-
ex = SandboxTerminalExecutor(
|
|
347
|
-
fake_sandbox, workspace=str(tmp_path), env={"PATH": "/bin"},
|
|
348
|
-
terminal_timeout=900)
|
|
349
|
-
ex(TerminalAction(command="echo hi"), None)
|
|
350
|
-
assert fake_sandbox.requests[0].timeout == 900
|
|
351
|
-
|
|
352
|
-
def test_action_timeout_wins(self, fake_sandbox, tmp_path):
|
|
353
|
-
ex = SandboxTerminalExecutor(
|
|
354
|
-
fake_sandbox, workspace=str(tmp_path), env={"PATH": "/bin"},
|
|
355
|
-
terminal_timeout=900)
|
|
356
|
-
ex(TerminalAction(command="echo hi", timeout=30), None)
|
|
357
|
-
assert fake_sandbox.requests[0].timeout == 30
|
|
358
|
-
|
|
359
|
-
def test_unset_keeps_none(self, fake_sandbox, tmp_path):
|
|
360
|
-
ex = SandboxTerminalExecutor(
|
|
361
|
-
fake_sandbox, workspace=str(tmp_path), env={"PATH": "/bin"})
|
|
362
|
-
ex(TerminalAction(command="echo hi"), None)
|
|
363
|
-
assert fake_sandbox.requests[0].timeout is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/core/results.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/core/verdict.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/actions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/allowlist.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/containers.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/dryrun.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/egress_policy.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/egress_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/forbidden.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/graph.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/hostboundary.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/loaders.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/mounts.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/outputs.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/presets.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/secrets.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/engine/sync_back.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/src/agentic_dev_loop/viz/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a42 → agentic_dev_loop-1.0.0a43}/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.0a42 → agentic_dev_loop-1.0.0a43}/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.0a42 → agentic_dev_loop-1.0.0a43}/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
|