agentic-dev-loop 1.0.0a45__tar.gz → 1.0.0a48__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.0a45/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a48}/PKG-INFO +33 -5
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/README.md +32 -4
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/core/config.py +13 -1
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/isolation.py +66 -21
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/llm.py +14 -7
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/stages.py +10 -2
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48/src/agentic_dev_loop.egg-info}/PKG-INFO +33 -5
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_config.py +34 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_isolation.py +80 -2
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_isolation_integration.py +79 -1
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_llm.py +40 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_publish.py +39 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_trigger_script.py +49 -4
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/containers.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/graph.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_035_finalize_nudge.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_041_brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_046_brief_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_containers.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/tests/test_viz.py +0 -0
{agentic_dev_loop-1.0.0a45/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a48}/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.0a48
|
|
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
|
|
@@ -199,6 +199,7 @@ llm:
|
|
|
199
199
|
model: openai/deepseek-v4-pro
|
|
200
200
|
base_url: https://api.deepseek.com
|
|
201
201
|
api_key_env: OPENAI_API_KEY
|
|
202
|
+
# reasoning_effort: medium # optional: low | medium | high | xhigh | none
|
|
202
203
|
|
|
203
204
|
max_attempts: 10 # CLI --max-attempts overrides
|
|
204
205
|
agents_dir: sdk_agents # your <name>.agent.md files
|
|
@@ -228,6 +229,7 @@ steps:
|
|
|
228
229
|
agent: review
|
|
229
230
|
artifact: review-findings.md
|
|
230
231
|
verdicts: { pass: APPROVED, fail: NEEDS CHANGES }
|
|
232
|
+
# reasoning_effort: xhigh # per-step override; falls back to the llm block, then SDK default
|
|
231
233
|
on_pass: qa
|
|
232
234
|
on_fail: { goto: dev, retry: true }
|
|
233
235
|
|
|
@@ -253,6 +255,7 @@ steps:
|
|
|
253
255
|
| `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
256
|
| `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
257
|
| `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. |
|
|
258
|
+
| `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block (pipeline-global) or per-step (overrides the block). Unset anywhere = the field is not passed and the SDK default (`high`) applies, so today's behavior is unchanged. Forwarded only for models the SDK/LiteLLM mark as supporting it; an unsupported value fails at the first LLM call. See [Reasoning effort](#reasoning-effort-opt-in). |
|
|
256
259
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
257
260
|
| `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). |
|
|
258
261
|
| `setup_probe:` | Optional consumer-authored in-container probe run on a warm `setup:` stamp instead of the venv probe. Exit 0 trusts the stamp (skips); any nonzero re-provisions with network on (fail toward freshness). The probe text is NOT part of the stamp key, so a probe-only change needs `--refresh` or `--clean-stamps` to take effect. Example (asserting a warmed esbuild binary is Linux-shaped): `file frontend-editor/node_modules/esbuild/bin/esbuild | grep -q ELF`. |
|
|
@@ -266,6 +269,30 @@ steps:
|
|
|
266
269
|
| `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from the real workspace (auto-created if missing); a `setup:` hook can warm them (the provision container carries the same cache mounts); `sync_excludes:` names scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
|
|
267
270
|
| `merge_on_conflict:` | Top-level run-wide list of append-style logs (same path vocabulary as `sync_excludes:`, default `[]`). A concurrent host edit to a listed path is union-merged instead of raising: the host file wins and the sandbox's export-only lines are appended in order; both sides converge and the run continues. See [docs/setup.md](docs/setup.md). |
|
|
268
271
|
|
|
272
|
+
### Reasoning effort (opt-in)
|
|
273
|
+
|
|
274
|
+
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block and
|
|
275
|
+
on individual agent steps. It takes one of `low`, `medium`, `high`, `xhigh`,
|
|
276
|
+
or `none`; unset means the field is never passed and the SDK default
|
|
277
|
+
(`high`) applies, so a pipeline that omits it behaves exactly as before. A
|
|
278
|
+
step's value overrides the `llm` block's value, so the review lane can hold
|
|
279
|
+
a higher effort than the rest.
|
|
280
|
+
|
|
281
|
+
The value is forwarded only for models the SDK/LiteLLM mark as supporting
|
|
282
|
+
it; an unsupported value fails at the first LLM call (the schema rejects
|
|
283
|
+
unknown names at load time, but cannot know provider capability). No
|
|
284
|
+
pipeline ships a value: adopt one only after an observed run proves lower
|
|
285
|
+
per-turn latency with a green suite and no provider 400s.
|
|
286
|
+
|
|
287
|
+
Recommended lane defaults (guidance only, not enforced):
|
|
288
|
+
|
|
289
|
+
| Lane | reasoning_effort | Why |
|
|
290
|
+
| --- | --- | --- |
|
|
291
|
+
| brief | medium | digest and classify, no design work |
|
|
292
|
+
| dev | high | hold the line: dev caught a real plan bug and a real sandbox triage this unit |
|
|
293
|
+
| qa | medium | classification discipline must stay reliable |
|
|
294
|
+
| review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
|
|
295
|
+
|
|
269
296
|
A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
|
|
270
297
|
run start (or when its inputs change), its `produces:` artifact survives attempt
|
|
271
298
|
starts, and the dev/review agents read that digest instead of re-deriving repo
|
|
@@ -357,10 +384,11 @@ steps:
|
|
|
357
384
|
structurally enforced.** A networked step runs on a per-run internal
|
|
358
385
|
docker network with no route to the host. Its traffic is forced
|
|
359
386
|
through a containerized egress proxy (injected via
|
|
360
|
-
`HTTP_PROXY`/`HTTPS_PROXY`, pointed at the proxy
|
|
361
|
-
on the internal network
|
|
362
|
-
|
|
363
|
-
|
|
387
|
+
`HTTP_PROXY`/`HTTPS_PROXY`, pointed at the proxy's internal IP
|
|
388
|
+
on the internal network; each distinct egress policy gets its own
|
|
389
|
+
proxy with a distinct internal IP); a request to an undeclared host
|
|
390
|
+
is denied and logged as an enforcement event. The engine never adds
|
|
391
|
+
an undeclared target. `egress: { class: browser }` is the explicit
|
|
364
392
|
broad-but-logged class (every connection logged, nothing filtered); command
|
|
365
393
|
and agent steps default to a strict allowlist.
|
|
366
394
|
- **Command, agent, and sandbox-action steps** may declare it. The host-side
|
|
@@ -155,6 +155,7 @@ llm:
|
|
|
155
155
|
model: openai/deepseek-v4-pro
|
|
156
156
|
base_url: https://api.deepseek.com
|
|
157
157
|
api_key_env: OPENAI_API_KEY
|
|
158
|
+
# reasoning_effort: medium # optional: low | medium | high | xhigh | none
|
|
158
159
|
|
|
159
160
|
max_attempts: 10 # CLI --max-attempts overrides
|
|
160
161
|
agents_dir: sdk_agents # your <name>.agent.md files
|
|
@@ -184,6 +185,7 @@ steps:
|
|
|
184
185
|
agent: review
|
|
185
186
|
artifact: review-findings.md
|
|
186
187
|
verdicts: { pass: APPROVED, fail: NEEDS CHANGES }
|
|
188
|
+
# reasoning_effort: xhigh # per-step override; falls back to the llm block, then SDK default
|
|
187
189
|
on_pass: qa
|
|
188
190
|
on_fail: { goto: dev, retry: true }
|
|
189
191
|
|
|
@@ -209,6 +211,7 @@ steps:
|
|
|
209
211
|
| `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. |
|
|
210
212
|
| `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). |
|
|
211
213
|
| `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. |
|
|
214
|
+
| `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block (pipeline-global) or per-step (overrides the block). Unset anywhere = the field is not passed and the SDK default (`high`) applies, so today's behavior is unchanged. Forwarded only for models the SDK/LiteLLM mark as supporting it; an unsupported value fails at the first LLM call. See [Reasoning effort](#reasoning-effort-opt-in). |
|
|
212
215
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
213
216
|
| `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). |
|
|
214
217
|
| `setup_probe:` | Optional consumer-authored in-container probe run on a warm `setup:` stamp instead of the venv probe. Exit 0 trusts the stamp (skips); any nonzero re-provisions with network on (fail toward freshness). The probe text is NOT part of the stamp key, so a probe-only change needs `--refresh` or `--clean-stamps` to take effect. Example (asserting a warmed esbuild binary is Linux-shaped): `file frontend-editor/node_modules/esbuild/bin/esbuild | grep -q ELF`. |
|
|
@@ -222,6 +225,30 @@ steps:
|
|
|
222
225
|
| `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from the real workspace (auto-created if missing); a `setup:` hook can warm them (the provision container carries the same cache mounts); `sync_excludes:` names scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
|
|
223
226
|
| `merge_on_conflict:` | Top-level run-wide list of append-style logs (same path vocabulary as `sync_excludes:`, default `[]`). A concurrent host edit to a listed path is union-merged instead of raising: the host file wins and the sandbox's export-only lines are appended in order; both sides converge and the run continues. See [docs/setup.md](docs/setup.md). |
|
|
224
227
|
|
|
228
|
+
### Reasoning effort (opt-in)
|
|
229
|
+
|
|
230
|
+
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block and
|
|
231
|
+
on individual agent steps. It takes one of `low`, `medium`, `high`, `xhigh`,
|
|
232
|
+
or `none`; unset means the field is never passed and the SDK default
|
|
233
|
+
(`high`) applies, so a pipeline that omits it behaves exactly as before. A
|
|
234
|
+
step's value overrides the `llm` block's value, so the review lane can hold
|
|
235
|
+
a higher effort than the rest.
|
|
236
|
+
|
|
237
|
+
The value is forwarded only for models the SDK/LiteLLM mark as supporting
|
|
238
|
+
it; an unsupported value fails at the first LLM call (the schema rejects
|
|
239
|
+
unknown names at load time, but cannot know provider capability). No
|
|
240
|
+
pipeline ships a value: adopt one only after an observed run proves lower
|
|
241
|
+
per-turn latency with a green suite and no provider 400s.
|
|
242
|
+
|
|
243
|
+
Recommended lane defaults (guidance only, not enforced):
|
|
244
|
+
|
|
245
|
+
| Lane | reasoning_effort | Why |
|
|
246
|
+
| --- | --- | --- |
|
|
247
|
+
| brief | medium | digest and classify, no design work |
|
|
248
|
+
| dev | high | hold the line: dev caught a real plan bug and a real sandbox triage this unit |
|
|
249
|
+
| qa | medium | classification discipline must stay reliable |
|
|
250
|
+
| review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
|
|
251
|
+
|
|
225
252
|
A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
|
|
226
253
|
run start (or when its inputs change), its `produces:` artifact survives attempt
|
|
227
254
|
starts, and the dev/review agents read that digest instead of re-deriving repo
|
|
@@ -313,10 +340,11 @@ steps:
|
|
|
313
340
|
structurally enforced.** A networked step runs on a per-run internal
|
|
314
341
|
docker network with no route to the host. Its traffic is forced
|
|
315
342
|
through a containerized egress proxy (injected via
|
|
316
|
-
`HTTP_PROXY`/`HTTPS_PROXY`, pointed at the proxy
|
|
317
|
-
on the internal network
|
|
318
|
-
|
|
319
|
-
|
|
343
|
+
`HTTP_PROXY`/`HTTPS_PROXY`, pointed at the proxy's internal IP
|
|
344
|
+
on the internal network; each distinct egress policy gets its own
|
|
345
|
+
proxy with a distinct internal IP); a request to an undeclared host
|
|
346
|
+
is denied and logged as an enforcement event. The engine never adds
|
|
347
|
+
an undeclared target. `egress: { class: browser }` is the explicit
|
|
320
348
|
broad-but-logged class (every connection logged, nothing filtered); command
|
|
321
349
|
and agent steps default to a strict allowlist.
|
|
322
350
|
- **Command, agent, and sandbox-action steps** may declare it. The host-side
|
|
@@ -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.0a48"
|
|
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"
|
|
@@ -6,7 +6,7 @@ import hashlib
|
|
|
6
6
|
import re
|
|
7
7
|
import warnings
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any, Literal
|
|
10
10
|
|
|
11
11
|
import yaml
|
|
12
12
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
@@ -35,6 +35,11 @@ DEFAULT_BRIEF_SECTIONS = [
|
|
|
35
35
|
"Open risks",
|
|
36
36
|
]
|
|
37
37
|
|
|
38
|
+
# 047: the full SDK reasoning_effort literal, passed through verbatim. The
|
|
39
|
+
# engine is provider-generic, so it validates shape only - whether a given
|
|
40
|
+
# provider accepts every value (notably "none") is operator risk (D3).
|
|
41
|
+
ReasoningEffort = Literal["low", "medium", "high", "xhigh", "none"]
|
|
42
|
+
|
|
38
43
|
|
|
39
44
|
def _validate_mount_path(path: str) -> str:
|
|
40
45
|
"""Validate one repo-relative mount path (BR-1.3).
|
|
@@ -385,6 +390,9 @@ class StepConfig(BaseModel):
|
|
|
385
390
|
terminal_timeout: float | None = Field(default=None, gt=0) # seconds, agent steps only (032)
|
|
386
391
|
finalize_nudge_turns: int | None = Field(default=None, gt=0) # agent steps only (035)
|
|
387
392
|
brief_inputs: BriefInputsSpec | None = None # agent steps only (033)
|
|
393
|
+
# 047 (D7): per-step opt-in override; unset falls back to the llm block,
|
|
394
|
+
# then the SDK default. Full SDK literal, passthrough semantics.
|
|
395
|
+
reasoning_effort: ReasoningEffort | None = None
|
|
388
396
|
custom_prompt: str | None = None # agent steps only (040); file path resolved at load
|
|
389
397
|
env: dict[str, str | None] | None = None # NFR-1: null = inherit host value
|
|
390
398
|
setup: str | None = None # BR-13: command steps only
|
|
@@ -552,6 +560,10 @@ class LlmConfig(BaseModel):
|
|
|
552
560
|
base_url: str | None = None
|
|
553
561
|
api_key_env: str
|
|
554
562
|
timeout: float | None = None # omitted → opinionated no-timeout default
|
|
563
|
+
# 047 (D1): opt-in passthrough; unset means the field is not passed at
|
|
564
|
+
# all, so the SDK default ("high") applies and today's behavior is
|
|
565
|
+
# byte-identical. No default may move without an observed run (D6).
|
|
566
|
+
reasoning_effort: ReasoningEffort | None = None
|
|
555
567
|
|
|
556
568
|
|
|
557
569
|
class PipelineConfig(BaseModel):
|
{agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
@@ -31,7 +31,7 @@ import sys
|
|
|
31
31
|
import time
|
|
32
32
|
import uuid
|
|
33
33
|
from abc import ABC, abstractmethod
|
|
34
|
-
from collections.abc import Mapping
|
|
34
|
+
from collections.abc import Iterable, Mapping
|
|
35
35
|
from dataclasses import dataclass
|
|
36
36
|
from datetime import datetime, timezone
|
|
37
37
|
from pathlib import Path, PurePosixPath
|
|
@@ -299,10 +299,10 @@ def proxy_container_name(run_id: str, profile_key: str) -> str:
|
|
|
299
299
|
return f"{run_id}-proxy-{profile_key}"
|
|
300
300
|
|
|
301
301
|
|
|
302
|
-
def proxy_url(
|
|
303
|
-
"""The sandbox-reachable proxy URL: the proxy
|
|
304
|
-
|
|
305
|
-
return f"http://{
|
|
302
|
+
def proxy_url(proxy_host: str, port: int = EGRESS_PROXY_PORT) -> str:
|
|
303
|
+
"""The sandbox-reachable proxy URL: the proxy's internal address on the
|
|
304
|
+
internal network (038, D3). Never 127.0.0.1 or the host-gateway."""
|
|
305
|
+
return f"http://{proxy_host}:{port}"
|
|
306
306
|
|
|
307
307
|
|
|
308
308
|
def egress_events_path(iso_state_dir: str | Path, run_id: str) -> Path:
|
|
@@ -310,8 +310,10 @@ def egress_events_path(iso_state_dir: str | Path, run_id: str) -> Path:
|
|
|
310
310
|
return Path(iso_state_dir) / f"{EGRESS_EVENTS_PREFIX}-{run_id}.jsonl"
|
|
311
311
|
|
|
312
312
|
|
|
313
|
-
def
|
|
314
|
-
"""
|
|
313
|
+
def proxy_usable_ips(subnets: str) -> list[str]:
|
|
314
|
+
"""Usable non-gateway IPv4 host addresses from the first viable subnet
|
|
315
|
+
(048 D1): ``hosts[1:]``, skipping the gateway at ``hosts[0]``. Empty when
|
|
316
|
+
no subnet offers a non-gateway host."""
|
|
315
317
|
for token in subnets.split():
|
|
316
318
|
try:
|
|
317
319
|
network = ipaddress.ip_network(token, strict=False)
|
|
@@ -322,8 +324,8 @@ def first_usable_ip(subnets: str) -> str | None:
|
|
|
322
324
|
hosts = list(network.hosts())
|
|
323
325
|
if len(hosts) < 2:
|
|
324
326
|
continue
|
|
325
|
-
return str(hosts[1]
|
|
326
|
-
return
|
|
327
|
+
return [str(host) for host in hosts[1:]]
|
|
328
|
+
return []
|
|
327
329
|
|
|
328
330
|
|
|
329
331
|
|
|
@@ -964,6 +966,7 @@ class DockerBackend(Sandbox):
|
|
|
964
966
|
self._run_net = run_network_name(self._run_id)
|
|
965
967
|
self._network_created = False
|
|
966
968
|
self._proxy_containers: list[str] = []
|
|
969
|
+
self._proxy_ips: dict[str, str] = {}
|
|
967
970
|
self._egress_events_path = egress_events_path(self._iso_state_dir,
|
|
968
971
|
self._run_id)
|
|
969
972
|
self._sandbox_venv_dir = sandbox_venv_dir # 022 relocated venv (opt-in)
|
|
@@ -1309,9 +1312,9 @@ class DockerBackend(Sandbox):
|
|
|
1309
1312
|
proxy_url_value: str | None = None
|
|
1310
1313
|
if network and self._export_root is not None:
|
|
1311
1314
|
proxy_name = proxy_container_name(self._run_id, key[0][:16])
|
|
1312
|
-
self._start_proxy_container(proxy_name, policy,
|
|
1313
|
-
|
|
1314
|
-
proxy_url_value = proxy_url(
|
|
1315
|
+
proxy_ip = self._start_proxy_container(proxy_name, policy,
|
|
1316
|
+
self._dial_rewrites)
|
|
1317
|
+
proxy_url_value = proxy_url(proxy_ip)
|
|
1315
1318
|
payload = docker_create_payload(
|
|
1316
1319
|
run_id=self._run_id, image=self._image, profile=self._profile,
|
|
1317
1320
|
workspace=self._workspace, network=network,
|
|
@@ -1334,7 +1337,7 @@ class DockerBackend(Sandbox):
|
|
|
1334
1337
|
started=True)
|
|
1335
1338
|
|
|
1336
1339
|
def _start_proxy_container(self, name: str, policy: EgressPolicy,
|
|
1337
|
-
dial_rewrites: dict[str, str] | None = None) ->
|
|
1340
|
+
dial_rewrites: dict[str, str] | None = None) -> str:
|
|
1338
1341
|
"""Create one containerized proxy (038 D2/D5, 039 D1-D4): host-access
|
|
1339
1342
|
network at create time with a pinned internal IP, then attach the internal
|
|
1340
1343
|
network; the proxy binds that address only and is readiness probed from
|
|
@@ -1350,7 +1353,7 @@ class DockerBackend(Sandbox):
|
|
|
1350
1353
|
policy_json = egress_proxy_module.policy_to_json(policy)
|
|
1351
1354
|
dial_rewrites = dict(dial_rewrites or {})
|
|
1352
1355
|
dial_rewrites_json = json.dumps(dial_rewrites, sort_keys=True)
|
|
1353
|
-
bind_host = self._proxy_internal_ip()
|
|
1356
|
+
bind_host = self._proxy_internal_ip(name)
|
|
1354
1357
|
payload = docker_proxy_payload(
|
|
1355
1358
|
run_id=self._run_id, image=self._image, name=name,
|
|
1356
1359
|
policy_json=policy_json, dial_rewrites_json=dial_rewrites_json,
|
|
@@ -1379,22 +1382,63 @@ class DockerBackend(Sandbox):
|
|
|
1379
1382
|
f"{bind_host}:{EGRESS_PROXY_PORT} after "
|
|
1380
1383
|
f"{PROXY_READY_ATTEMPTS} readiness probes")
|
|
1381
1384
|
self._proxy_containers.append(name)
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
+
return bind_host
|
|
1386
|
+
|
|
1387
|
+
def _proxy_internal_ip(self, proxy_name: str) -> str:
|
|
1388
|
+
"""Return the per-proxy pinned IPv4 address on the internal network
|
|
1389
|
+
(048 D1): inspect the subnet and the addresses containers already hold
|
|
1390
|
+
on it, then allocate the next free host (profile containers join the
|
|
1391
|
+
run network with a daemon auto-assigned IP, so those must be skipped
|
|
1392
|
+
or the later ``network connect --ip`` collides)."""
|
|
1385
1393
|
inspect = self._runner.run(
|
|
1386
1394
|
["docker", "network", "inspect", "-f",
|
|
1387
|
-
"{{range .IPAM.Config}}{{.Subnet}} {{end}}"
|
|
1395
|
+
"{{range .IPAM.Config}}{{.Subnet}} {{end}}|"
|
|
1396
|
+
"{{range .Containers}}{{.IPv4Address}} {{end}}",
|
|
1397
|
+
self._run_net],
|
|
1388
1398
|
timeout=30)
|
|
1389
1399
|
if inspect.exit_code != 0:
|
|
1390
1400
|
raise SandboxError(
|
|
1391
1401
|
f"AE-SBX-021: internal network inspect failed: "
|
|
1392
1402
|
f"{inspect.stderr.strip() or inspect.stdout.strip()}")
|
|
1393
|
-
|
|
1394
|
-
if
|
|
1403
|
+
subnets, sep, in_use_part = inspect.stdout.partition("|")
|
|
1404
|
+
in_use = in_use_part.split() if sep else ()
|
|
1405
|
+
return self._allocate_proxy_ip(subnets, proxy_name, in_use)
|
|
1406
|
+
|
|
1407
|
+
def _allocate_proxy_ip(self, subnets: str, proxy_name: str,
|
|
1408
|
+
in_use: Iterable[str] = ()) -> str:
|
|
1409
|
+
"""Allocate one internal IPv4 address per proxy (048 D1/D2). An
|
|
1410
|
+
existing allocation for ``proxy_name`` is reused; otherwise the first
|
|
1411
|
+
free non-gateway host wins (the first proxy keeps ``hosts[1]``).
|
|
1412
|
+
Addresses in ``in_use`` (held by other containers on the network) are
|
|
1413
|
+
skipped alongside the already-allocated proxy IPs. Raises when the
|
|
1414
|
+
subnet has no free host instead of letting the daemon emit a
|
|
1415
|
+
confusing collision error."""
|
|
1416
|
+
existing = self._proxy_ips.get(proxy_name)
|
|
1417
|
+
if existing is not None:
|
|
1418
|
+
return existing
|
|
1419
|
+
hosts = proxy_usable_ips(subnets)
|
|
1420
|
+
if not hosts:
|
|
1395
1421
|
raise SandboxError(
|
|
1396
1422
|
f"AE-SBX-021: internal network has no usable IPv4 subnet: "
|
|
1397
|
-
f"{
|
|
1423
|
+
f"{subnets.strip()!r}")
|
|
1424
|
+
taken = set(self._proxy_ips.values())
|
|
1425
|
+
for token in in_use:
|
|
1426
|
+
addr = token.split("/", 1)[0].strip()
|
|
1427
|
+
try:
|
|
1428
|
+
ip = ipaddress.ip_address(addr)
|
|
1429
|
+
except ValueError:
|
|
1430
|
+
continue
|
|
1431
|
+
if isinstance(ip, ipaddress.IPv4Address):
|
|
1432
|
+
taken.add(str(ip))
|
|
1433
|
+
free = [host for host in hosts if host not in taken]
|
|
1434
|
+
if not free:
|
|
1435
|
+
raise SandboxError(
|
|
1436
|
+
f"AE-SBX-021: internal network {subnets.strip()!r} has "
|
|
1437
|
+
f"{len(hosts)} usable proxy address(es) but "
|
|
1438
|
+
f"{len(self._proxy_ips) + 1} egress proxies were requested - "
|
|
1439
|
+
f"no free address to assign")
|
|
1440
|
+
ip = free[0]
|
|
1441
|
+
self._proxy_ips[proxy_name] = ip
|
|
1398
1442
|
return ip
|
|
1399
1443
|
|
|
1400
1444
|
def _wait_proxy_ready(self, name: str, host: str) -> bool:
|
|
@@ -1541,6 +1585,7 @@ class DockerBackend(Sandbox):
|
|
|
1541
1585
|
for name in self._proxy_containers:
|
|
1542
1586
|
self._rm_container(name)
|
|
1543
1587
|
self._proxy_containers.clear()
|
|
1588
|
+
self._proxy_ips.clear()
|
|
1544
1589
|
if self._network_created:
|
|
1545
1590
|
self._runner.run(["docker", "network", "rm", self._run_net], timeout=60)
|
|
1546
1591
|
self._network_created = False
|
|
@@ -48,13 +48,20 @@ if not getattr(_ORIGINAL_FORMAT_MESSAGES, "_agentic_dev_loop_reasoning_repair",
|
|
|
48
48
|
LLM.format_messages_for_llm = _repair_reasoning_passback
|
|
49
49
|
|
|
50
50
|
|
|
51
|
-
def build_llm(cfg: LlmConfig) -> LLM:
|
|
51
|
+
def build_llm(cfg: LlmConfig, reasoning_effort: str | None = None) -> LLM:
|
|
52
52
|
api_key = os.environ.get(cfg.api_key_env)
|
|
53
53
|
if not api_key:
|
|
54
54
|
raise RuntimeError(f"{cfg.api_key_env} environment variable is required")
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
# 047 (D2/D7): an explicit per-step override wins, else the llm block's
|
|
56
|
+
# value applies, else the field is omitted entirely and the SDK default
|
|
57
|
+
# ("high") is in force. The reasoning_content shims above stay untouched.
|
|
58
|
+
effort = reasoning_effort if reasoning_effort is not None else cfg.reasoning_effort
|
|
59
|
+
kwargs = {
|
|
60
|
+
"model": cfg.model,
|
|
61
|
+
"api_key": api_key,
|
|
62
|
+
"base_url": cfg.base_url,
|
|
63
|
+
"timeout": cfg.timeout, # policy: no LLM timeout unless explicitly set
|
|
64
|
+
}
|
|
65
|
+
if effort is not None:
|
|
66
|
+
kwargs["reasoning_effort"] = effort
|
|
67
|
+
return LLM(**kwargs)
|
{agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
@@ -42,6 +42,7 @@ from .egress_policy import assemble_egress_policy
|
|
|
42
42
|
from .forbidden import resolve_forbidden_rules
|
|
43
43
|
from .isolation import (SandboxError, SandboxExecutionRequest, command_argv,
|
|
44
44
|
resolve_sandbox_env)
|
|
45
|
+
from .llm import build_llm
|
|
45
46
|
from .mounts import resolve_mount_set
|
|
46
47
|
from .outputs import CapturedOutput
|
|
47
48
|
from .secrets import redact_values, step_secret_env
|
|
@@ -174,11 +175,18 @@ class Runtime:
|
|
|
174
175
|
if step is not None else None),
|
|
175
176
|
forbidden_commands=forbidden_commands,
|
|
176
177
|
allowlist=allowlist)
|
|
178
|
+
# 047 (D7): a step's reasoning_effort overrides the llm block's;
|
|
179
|
+
# self.llm already carries the block value (or the SDK default),
|
|
180
|
+
# so only build a fresh LLM when the step overrides it.
|
|
181
|
+
llm = self.llm
|
|
182
|
+
if step is not None and step.reasoning_effort is not None:
|
|
183
|
+
llm = build_llm(self.cfg.llm,
|
|
184
|
+
reasoning_effort=step.reasoning_effort)
|
|
177
185
|
if step is not None and step.brief_inputs is not None:
|
|
178
186
|
agent = load_brief_agent(
|
|
179
|
-
agent_name, step.brief_inputs.sections,
|
|
187
|
+
agent_name, step.brief_inputs.sections, llm)
|
|
180
188
|
else:
|
|
181
|
-
agent = load_agent(self.agents_dir, agent_name,
|
|
189
|
+
agent = load_agent(self.agents_dir, agent_name, llm)
|
|
182
190
|
callbacks = []
|
|
183
191
|
if finalize_controller is not None:
|
|
184
192
|
callbacks.append(finalize_controller.on_event)
|
{agentic_dev_loop-1.0.0a45 → agentic_dev_loop-1.0.0a48/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.0a48
|
|
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
|
|
@@ -199,6 +199,7 @@ llm:
|
|
|
199
199
|
model: openai/deepseek-v4-pro
|
|
200
200
|
base_url: https://api.deepseek.com
|
|
201
201
|
api_key_env: OPENAI_API_KEY
|
|
202
|
+
# reasoning_effort: medium # optional: low | medium | high | xhigh | none
|
|
202
203
|
|
|
203
204
|
max_attempts: 10 # CLI --max-attempts overrides
|
|
204
205
|
agents_dir: sdk_agents # your <name>.agent.md files
|
|
@@ -228,6 +229,7 @@ steps:
|
|
|
228
229
|
agent: review
|
|
229
230
|
artifact: review-findings.md
|
|
230
231
|
verdicts: { pass: APPROVED, fail: NEEDS CHANGES }
|
|
232
|
+
# reasoning_effort: xhigh # per-step override; falls back to the llm block, then SDK default
|
|
231
233
|
on_pass: qa
|
|
232
234
|
on_fail: { goto: dev, retry: true }
|
|
233
235
|
|
|
@@ -253,6 +255,7 @@ steps:
|
|
|
253
255
|
| `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
256
|
| `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
257
|
| `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. |
|
|
258
|
+
| `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block (pipeline-global) or per-step (overrides the block). Unset anywhere = the field is not passed and the SDK default (`high`) applies, so today's behavior is unchanged. Forwarded only for models the SDK/LiteLLM mark as supporting it; an unsupported value fails at the first LLM call. See [Reasoning effort](#reasoning-effort-opt-in). |
|
|
256
259
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
257
260
|
| `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). |
|
|
258
261
|
| `setup_probe:` | Optional consumer-authored in-container probe run on a warm `setup:` stamp instead of the venv probe. Exit 0 trusts the stamp (skips); any nonzero re-provisions with network on (fail toward freshness). The probe text is NOT part of the stamp key, so a probe-only change needs `--refresh` or `--clean-stamps` to take effect. Example (asserting a warmed esbuild binary is Linux-shaped): `file frontend-editor/node_modules/esbuild/bin/esbuild | grep -q ELF`. |
|
|
@@ -266,6 +269,30 @@ steps:
|
|
|
266
269
|
| `caches:` / `sync_excludes:` | Top-level run-wide lists. `caches:` names repo-relative toolchain cache dirs bind-mounted rw from the real workspace (auto-created if missing); a `setup:` hook can warm them (the provision container carries the same cache mounts); `sync_excludes:` names scratch paths skipped from both the export copy and sync-back (wildcard `*` spans separators, so `**/bin/**` matches a `/bin/` segment at any depth). Both default to `[]`. See [docs/setup.md](docs/setup.md). |
|
|
267
270
|
| `merge_on_conflict:` | Top-level run-wide list of append-style logs (same path vocabulary as `sync_excludes:`, default `[]`). A concurrent host edit to a listed path is union-merged instead of raising: the host file wins and the sandbox's export-only lines are appended in order; both sides converge and the run continues. See [docs/setup.md](docs/setup.md). |
|
|
268
271
|
|
|
272
|
+
### Reasoning effort (opt-in)
|
|
273
|
+
|
|
274
|
+
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block and
|
|
275
|
+
on individual agent steps. It takes one of `low`, `medium`, `high`, `xhigh`,
|
|
276
|
+
or `none`; unset means the field is never passed and the SDK default
|
|
277
|
+
(`high`) applies, so a pipeline that omits it behaves exactly as before. A
|
|
278
|
+
step's value overrides the `llm` block's value, so the review lane can hold
|
|
279
|
+
a higher effort than the rest.
|
|
280
|
+
|
|
281
|
+
The value is forwarded only for models the SDK/LiteLLM mark as supporting
|
|
282
|
+
it; an unsupported value fails at the first LLM call (the schema rejects
|
|
283
|
+
unknown names at load time, but cannot know provider capability). No
|
|
284
|
+
pipeline ships a value: adopt one only after an observed run proves lower
|
|
285
|
+
per-turn latency with a green suite and no provider 400s.
|
|
286
|
+
|
|
287
|
+
Recommended lane defaults (guidance only, not enforced):
|
|
288
|
+
|
|
289
|
+
| Lane | reasoning_effort | Why |
|
|
290
|
+
| --- | --- | --- |
|
|
291
|
+
| brief | medium | digest and classify, no design work |
|
|
292
|
+
| dev | high | hold the line: dev caught a real plan bug and a real sandbox triage this unit |
|
|
293
|
+
| qa | medium | classification discipline must stay reliable |
|
|
294
|
+
| review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
|
|
295
|
+
|
|
269
296
|
A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
|
|
270
297
|
run start (or when its inputs change), its `produces:` artifact survives attempt
|
|
271
298
|
starts, and the dev/review agents read that digest instead of re-deriving repo
|
|
@@ -357,10 +384,11 @@ steps:
|
|
|
357
384
|
structurally enforced.** A networked step runs on a per-run internal
|
|
358
385
|
docker network with no route to the host. Its traffic is forced
|
|
359
386
|
through a containerized egress proxy (injected via
|
|
360
|
-
`HTTP_PROXY`/`HTTPS_PROXY`, pointed at the proxy
|
|
361
|
-
on the internal network
|
|
362
|
-
|
|
363
|
-
|
|
387
|
+
`HTTP_PROXY`/`HTTPS_PROXY`, pointed at the proxy's internal IP
|
|
388
|
+
on the internal network; each distinct egress policy gets its own
|
|
389
|
+
proxy with a distinct internal IP); a request to an undeclared host
|
|
390
|
+
is denied and logged as an enforcement event. The engine never adds
|
|
391
|
+
an undeclared target. `egress: { class: browser }` is the explicit
|
|
364
392
|
broad-but-logged class (every connection logged, nothing filtered); command
|
|
365
393
|
and agent steps default to a strict allowlist.
|
|
366
394
|
- **Command, agent, and sandbox-action steps** may declare it. The host-side
|
|
@@ -180,6 +180,40 @@ def test_extra_body_llm_key_rejected(tmp_path):
|
|
|
180
180
|
_load(tmp_path, data)
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
def test_llm_reasoning_effort_round_trips(tmp_path):
|
|
184
|
+
data = _base()
|
|
185
|
+
data["llm"]["reasoning_effort"] = "medium"
|
|
186
|
+
assert _load(tmp_path, data).llm.reasoning_effort == "medium"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_llm_reasoning_effort_defaults_none(tmp_path):
|
|
190
|
+
assert _load(tmp_path, _base()).llm.reasoning_effort is None
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_llm_reasoning_effort_unknown_value_rejected(tmp_path):
|
|
194
|
+
data = _base()
|
|
195
|
+
data["llm"]["reasoning_effort"] = "maximum"
|
|
196
|
+
with pytest.raises(ValidationError):
|
|
197
|
+
_load(tmp_path, data)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_step_reasoning_effort_round_trips(tmp_path):
|
|
201
|
+
data = _base()
|
|
202
|
+
data["steps"][0]["reasoning_effort"] = "xhigh"
|
|
203
|
+
assert _load(tmp_path, data).steps[0].reasoning_effort == "xhigh"
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_step_reasoning_effort_defaults_none(tmp_path):
|
|
207
|
+
assert _load(tmp_path, _base()).steps[0].reasoning_effort is None
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_step_reasoning_effort_unknown_value_rejected(tmp_path):
|
|
211
|
+
data = _base()
|
|
212
|
+
data["steps"][0]["reasoning_effort"] = "banana"
|
|
213
|
+
with pytest.raises(ValidationError):
|
|
214
|
+
_load(tmp_path, data)
|
|
215
|
+
|
|
216
|
+
|
|
183
217
|
def test_unknown_route_key_rejected(tmp_path):
|
|
184
218
|
data = _base()
|
|
185
219
|
data["steps"][0]["on_pass"] = {"goto": "success", "bogus": 1}
|