agentic-dev-loop 0.9.2__tar.gz → 0.10.0__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.
- {agentic_dev_loop-0.9.2/src/agentic_dev_loop.egg-info → agentic_dev_loop-0.10.0}/PKG-INFO +44 -22
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/README.md +43 -21
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/pyproject.toml +1 -1
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/core/config.py +39 -5
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/graph.py +14 -0
- agentic_dev_loop-0.10.0/src/agentic_dev_loop/engine/llm.py +101 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/stages.py +14 -6
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0/src/agentic_dev_loop.egg-info}/PKG-INFO +44 -22
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_config.py +88 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_graph.py +66 -1
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_llm.py +89 -36
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_sdk_compat.py +6 -5
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_stages.py +80 -0
- agentic_dev_loop-0.9.2/src/agentic_dev_loop/engine/llm.py +0 -67
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/LICENSE +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/setup.cfg +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/containers.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_035_finalize_nudge.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_041_brief_coverage.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_046_brief_prompt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_actions.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_agents.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_cli.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_containers.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_deps.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_proc.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_publish.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_registry.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_results.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.0}/tests/test_viz.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
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
|
|
@@ -202,6 +202,17 @@ llm:
|
|
|
202
202
|
api_key_env: OPENAI_API_KEY
|
|
203
203
|
# reasoning_effort: medium # optional: low | medium | high | xhigh | none
|
|
204
204
|
|
|
205
|
+
# Named LLM profiles (054): a step names one with `llm: <name>` to move the
|
|
206
|
+
# whole profile (model, endpoint, key, effort default) together - no partial
|
|
207
|
+
# merges. The `llm:` block above remains the default. Profile keys are
|
|
208
|
+
# host-side: sandbox access still goes through `secrets:`, never a profile key.
|
|
209
|
+
# llms:
|
|
210
|
+
# review:
|
|
211
|
+
# model: openai/deepseek-v4-pro
|
|
212
|
+
# base_url: https://api.deepseek.com
|
|
213
|
+
# api_key_env: REVIEW_API_KEY
|
|
214
|
+
# reasoning_effort: xhigh
|
|
215
|
+
|
|
205
216
|
max_attempts: 10 # CLI --max-attempts overrides
|
|
206
217
|
agents_dir: sdk_agents # your <name>.agent.md files
|
|
207
218
|
|
|
@@ -231,7 +242,8 @@ steps:
|
|
|
231
242
|
agent: review
|
|
232
243
|
artifact: review-findings.md
|
|
233
244
|
verdicts: { pass: APPROVED, fail: NEEDS CHANGES }
|
|
234
|
-
# reasoning_effort: xhigh # per-step override
|
|
245
|
+
# reasoning_effort: xhigh # per-step override of the effective profile's effort
|
|
246
|
+
# llm: review # (054) select a named `llms:` profile for this step
|
|
235
247
|
on_pass: qa
|
|
236
248
|
on_fail: { goto: dev, retry: true }
|
|
237
249
|
|
|
@@ -257,7 +269,8 @@ steps:
|
|
|
257
269
|
| `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. |
|
|
258
270
|
| `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). |
|
|
259
271
|
| `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. |
|
|
260
|
-
| `
|
|
272
|
+
| `llms:` (top-level) / step `llm:` | Named LLM profiles, each a full `LlmConfig` (`model`, `base_url`, `api_key_env`, `timeout`, `reasoning_effort`). A step names one with `llm: <profile-name>`; the whole profile moves together (no partial merges). Allowed only on agent and brief steps. The top-level `llm:` block remains the default. Profile keys are host-side - sandbox access still goes through `secrets:`. |
|
|
273
|
+
| `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block, a `llms:` profile, or per-step (a step value overrides the effective profile's). Unset anywhere = reasoning off: nothing is forwarded and reasoning is stripped from requests. A thinking model without an effort level is invalid config and fails at the first request (crap in, crap out). See [Reasoning effort](#reasoning-effort-opt-in). |
|
|
261
274
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
262
275
|
| `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` runs fresh at every step boundary in a one-shot provision container targeting the export (command or agent steps; network ON; no stamping); `network: true` lets a command/agent step reach the hosts its `egress:` allowlist names (default off) - see [Per-stage networking](#per-stage-networking). |
|
|
263
276
|
| `mounts:` / `egress:` | Per-step workspace isolation. `mounts:` lists `{path, mode}` entries (`rw`/`ro`, default `ro`) and is **strict-by-default** - empty/missing mounts nothing; only declared-rw paths sync back to the repo. `egress:` is `{class, presets, targets}` (`class: browser` = broad-but-logged; otherwise a strict allowlist). See [Per-stage networking](#per-stage-networking) and [docs/setup.md](docs/setup.md). |
|
|
@@ -272,17 +285,25 @@ steps:
|
|
|
272
285
|
|
|
273
286
|
### Reasoning effort (opt-in)
|
|
274
287
|
|
|
275
|
-
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block
|
|
276
|
-
|
|
277
|
-
or `none`; unset means
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
288
|
+
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block, a
|
|
289
|
+
named `llms:` profile, and individual agent steps. It takes one of `low`,
|
|
290
|
+
`medium`, `high`, `xhigh`, or `none`; unset means reasoning is off - the
|
|
291
|
+
field is forwarded as none, reasoning is stripped from requests, and a
|
|
292
|
+
thinking model whose history still carries reasoning fails fast with an
|
|
293
|
+
actionable engine error rather than a provider 400. A step's value overrides
|
|
294
|
+
the effective profile's value, so the review lane can hold a higher effort
|
|
295
|
+
than the rest, or a named profile can move an entire lane to a different
|
|
296
|
+
provider.
|
|
297
|
+
|
|
298
|
+
Reasoning is opt-in and provider-generic: any openai-compatible endpoint
|
|
299
|
+
expressible as an `LlmConfig` works, and reasoning passback is gated on the
|
|
300
|
+
configured effort level, never on a model-name list. A thinking model without
|
|
301
|
+
an effort level (or with `none` where the provider ignores it) is invalid
|
|
302
|
+
config - it still reasons but its API requires the passback the engine strips,
|
|
303
|
+
so the engine raises before the doomed request is sent. Set
|
|
304
|
+
`reasoning_effort` on the step or its profile, or use a non-thinking model.
|
|
305
|
+
The schema rejects unknown effort names at load time, but cannot know provider
|
|
306
|
+
capability; adopt an effort value only after an observed run proves lower
|
|
286
307
|
per-turn latency with a green suite and no provider 400s.
|
|
287
308
|
|
|
288
309
|
Recommended lane defaults (guidance only, not enforced):
|
|
@@ -515,22 +536,23 @@ adapt per repo.
|
|
|
515
536
|
Adaptation checklist for a new repo:
|
|
516
537
|
|
|
517
538
|
1. `llm:` - model, `base_url`, `api_key_env`.
|
|
518
|
-
2. `
|
|
539
|
+
2. `llms:` (optional) - named profiles for multi-provider lanes (keep review on the strongest model); a step opts in with `llm: <name>`.
|
|
540
|
+
3. `isolation:` - toolchain image override (the consumer repo pins a
|
|
519
541
|
toolchain image by digest) and `seccomp_profile: bundled:chromium-userns`
|
|
520
542
|
only if a stage runs Chromium.
|
|
521
|
-
|
|
543
|
+
4. `caches:` - toolchain cache dirs as rw bind-mounts shared across runs
|
|
522
544
|
(`.nuget-packages`, `qa/node_modules`, `qa/.browsers`,
|
|
523
545
|
`frontend-editor/node_modules`); caches are never synced back, and a
|
|
524
546
|
step's `setup:` hook runs with them mounted so it can warm them.
|
|
525
|
-
|
|
526
|
-
|
|
547
|
+
5. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
|
|
548
|
+
6. `secrets:` - refs only, resolved from host env; each step opts in to the
|
|
527
549
|
refs it needs.
|
|
528
|
-
|
|
550
|
+
7. `egress:` - `presets: [package-registries]` for fetch steps;
|
|
529
551
|
`class: browser` (broad-but-logged) for browser-tier qa only.
|
|
530
|
-
|
|
552
|
+
8. `deps_policy:` and `lockfiles:` - see
|
|
531
553
|
[Dependency policy](#dependency-policy-locked-by-default).
|
|
532
|
-
|
|
533
|
-
|
|
554
|
+
9. The dev prompt's impossible set - write it for your toolchain.
|
|
555
|
+
10. `additional_files:` - the repo's `AGENTS.md`, coding standards, design
|
|
534
556
|
docs: shared context for every agent step.
|
|
535
557
|
|
|
536
558
|
Validate before the first run - dry-run loads and prints the resolved plan
|
|
@@ -158,6 +158,17 @@ llm:
|
|
|
158
158
|
api_key_env: OPENAI_API_KEY
|
|
159
159
|
# reasoning_effort: medium # optional: low | medium | high | xhigh | none
|
|
160
160
|
|
|
161
|
+
# Named LLM profiles (054): a step names one with `llm: <name>` to move the
|
|
162
|
+
# whole profile (model, endpoint, key, effort default) together - no partial
|
|
163
|
+
# merges. The `llm:` block above remains the default. Profile keys are
|
|
164
|
+
# host-side: sandbox access still goes through `secrets:`, never a profile key.
|
|
165
|
+
# llms:
|
|
166
|
+
# review:
|
|
167
|
+
# model: openai/deepseek-v4-pro
|
|
168
|
+
# base_url: https://api.deepseek.com
|
|
169
|
+
# api_key_env: REVIEW_API_KEY
|
|
170
|
+
# reasoning_effort: xhigh
|
|
171
|
+
|
|
161
172
|
max_attempts: 10 # CLI --max-attempts overrides
|
|
162
173
|
agents_dir: sdk_agents # your <name>.agent.md files
|
|
163
174
|
|
|
@@ -187,7 +198,8 @@ steps:
|
|
|
187
198
|
agent: review
|
|
188
199
|
artifact: review-findings.md
|
|
189
200
|
verdicts: { pass: APPROVED, fail: NEEDS CHANGES }
|
|
190
|
-
# reasoning_effort: xhigh # per-step override
|
|
201
|
+
# reasoning_effort: xhigh # per-step override of the effective profile's effort
|
|
202
|
+
# llm: review # (054) select a named `llms:` profile for this step
|
|
191
203
|
on_pass: qa
|
|
192
204
|
on_fail: { goto: dev, retry: true }
|
|
193
205
|
|
|
@@ -213,7 +225,8 @@ steps:
|
|
|
213
225
|
| `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. |
|
|
214
226
|
| `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). |
|
|
215
227
|
| `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. |
|
|
216
|
-
| `
|
|
228
|
+
| `llms:` (top-level) / step `llm:` | Named LLM profiles, each a full `LlmConfig` (`model`, `base_url`, `api_key_env`, `timeout`, `reasoning_effort`). A step names one with `llm: <profile-name>`; the whole profile moves together (no partial merges). Allowed only on agent and brief steps. The top-level `llm:` block remains the default. Profile keys are host-side - sandbox access still goes through `secrets:`. |
|
|
229
|
+
| `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block, a `llms:` profile, or per-step (a step value overrides the effective profile's). Unset anywhere = reasoning off: nothing is forwarded and reasoning is stripped from requests. A thinking model without an effort level is invalid config and fails at the first request (crap in, crap out). See [Reasoning effort](#reasoning-effort-opt-in). |
|
|
217
230
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
218
231
|
| `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` runs fresh at every step boundary in a one-shot provision container targeting the export (command or agent steps; network ON; no stamping); `network: true` lets a command/agent step reach the hosts its `egress:` allowlist names (default off) - see [Per-stage networking](#per-stage-networking). |
|
|
219
232
|
| `mounts:` / `egress:` | Per-step workspace isolation. `mounts:` lists `{path, mode}` entries (`rw`/`ro`, default `ro`) and is **strict-by-default** - empty/missing mounts nothing; only declared-rw paths sync back to the repo. `egress:` is `{class, presets, targets}` (`class: browser` = broad-but-logged; otherwise a strict allowlist). See [Per-stage networking](#per-stage-networking) and [docs/setup.md](docs/setup.md). |
|
|
@@ -228,17 +241,25 @@ steps:
|
|
|
228
241
|
|
|
229
242
|
### Reasoning effort (opt-in)
|
|
230
243
|
|
|
231
|
-
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block
|
|
232
|
-
|
|
233
|
-
or `none`; unset means
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
244
|
+
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block, a
|
|
245
|
+
named `llms:` profile, and individual agent steps. It takes one of `low`,
|
|
246
|
+
`medium`, `high`, `xhigh`, or `none`; unset means reasoning is off - the
|
|
247
|
+
field is forwarded as none, reasoning is stripped from requests, and a
|
|
248
|
+
thinking model whose history still carries reasoning fails fast with an
|
|
249
|
+
actionable engine error rather than a provider 400. A step's value overrides
|
|
250
|
+
the effective profile's value, so the review lane can hold a higher effort
|
|
251
|
+
than the rest, or a named profile can move an entire lane to a different
|
|
252
|
+
provider.
|
|
253
|
+
|
|
254
|
+
Reasoning is opt-in and provider-generic: any openai-compatible endpoint
|
|
255
|
+
expressible as an `LlmConfig` works, and reasoning passback is gated on the
|
|
256
|
+
configured effort level, never on a model-name list. A thinking model without
|
|
257
|
+
an effort level (or with `none` where the provider ignores it) is invalid
|
|
258
|
+
config - it still reasons but its API requires the passback the engine strips,
|
|
259
|
+
so the engine raises before the doomed request is sent. Set
|
|
260
|
+
`reasoning_effort` on the step or its profile, or use a non-thinking model.
|
|
261
|
+
The schema rejects unknown effort names at load time, but cannot know provider
|
|
262
|
+
capability; adopt an effort value only after an observed run proves lower
|
|
242
263
|
per-turn latency with a green suite and no provider 400s.
|
|
243
264
|
|
|
244
265
|
Recommended lane defaults (guidance only, not enforced):
|
|
@@ -471,22 +492,23 @@ adapt per repo.
|
|
|
471
492
|
Adaptation checklist for a new repo:
|
|
472
493
|
|
|
473
494
|
1. `llm:` - model, `base_url`, `api_key_env`.
|
|
474
|
-
2. `
|
|
495
|
+
2. `llms:` (optional) - named profiles for multi-provider lanes (keep review on the strongest model); a step opts in with `llm: <name>`.
|
|
496
|
+
3. `isolation:` - toolchain image override (the consumer repo pins a
|
|
475
497
|
toolchain image by digest) and `seccomp_profile: bundled:chromium-userns`
|
|
476
498
|
only if a stage runs Chromium.
|
|
477
|
-
|
|
499
|
+
4. `caches:` - toolchain cache dirs as rw bind-mounts shared across runs
|
|
478
500
|
(`.nuget-packages`, `qa/node_modules`, `qa/.browsers`,
|
|
479
501
|
`frontend-editor/node_modules`); caches are never synced back, and a
|
|
480
502
|
step's `setup:` hook runs with them mounted so it can warm them.
|
|
481
|
-
|
|
482
|
-
|
|
503
|
+
5. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
|
|
504
|
+
6. `secrets:` - refs only, resolved from host env; each step opts in to the
|
|
483
505
|
refs it needs.
|
|
484
|
-
|
|
506
|
+
7. `egress:` - `presets: [package-registries]` for fetch steps;
|
|
485
507
|
`class: browser` (broad-but-logged) for browser-tier qa only.
|
|
486
|
-
|
|
508
|
+
8. `deps_policy:` and `lockfiles:` - see
|
|
487
509
|
[Dependency policy](#dependency-policy-locked-by-default).
|
|
488
|
-
|
|
489
|
-
|
|
510
|
+
9. The dev prompt's impossible set - write it for your toolchain.
|
|
511
|
+
10. `additional_files:` - the repo's `AGENTS.md`, coding standards, design
|
|
490
512
|
docs: shared context for every agent step.
|
|
491
513
|
|
|
492
514
|
Validate before the first run - dry-run loads and prints the resolved plan
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentic-dev-loop"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.10.0"
|
|
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"
|
|
@@ -396,9 +396,13 @@ class StepConfig(BaseModel):
|
|
|
396
396
|
terminal_timeout: float | None = Field(default=None, gt=0) # seconds, agent steps only (032)
|
|
397
397
|
finalize_nudge_turns: int | None = Field(default=None, gt=0) # agent steps only (035)
|
|
398
398
|
brief_inputs: BriefInputsSpec | None = None # agent steps only (033)
|
|
399
|
-
# 047 (D7): per-step opt-in override; unset falls back to the
|
|
400
|
-
#
|
|
399
|
+
# 047 (D7) / 054 (D6): per-step opt-in override; unset falls back to the
|
|
400
|
+
# effective profile's value (the named `llms:` profile, else the `llm:`
|
|
401
|
+
# block), else reasoning is off. Full SDK literal, passthrough semantics.
|
|
401
402
|
reasoning_effort: ReasoningEffort | None = None
|
|
403
|
+
# 054 (D3): names a top-level `llms:` profile; the whole profile moves
|
|
404
|
+
# together (model, endpoint, key, effort default). None = the `llm:` block.
|
|
405
|
+
llm: str | None = None
|
|
402
406
|
custom_prompt: str | None = None # agent steps only (040); file path resolved at load
|
|
403
407
|
env: dict[str, str | None] | None = None # NFR-1: null = inherit host value
|
|
404
408
|
setup: str | None = None # command or agent steps; runs fresh at each step entry (052)
|
|
@@ -486,6 +490,16 @@ class StepConfig(BaseModel):
|
|
|
486
490
|
f"step {self.name!r}: exactly one of 'agent', 'action', "
|
|
487
491
|
"'command', or 'brief_inputs' is required"
|
|
488
492
|
)
|
|
493
|
+
if self.llm is not None:
|
|
494
|
+
if not self.llm.strip():
|
|
495
|
+
raise ValueError(
|
|
496
|
+
f"step {self.name!r}: 'llm' must be a non-empty profile name"
|
|
497
|
+
)
|
|
498
|
+
if self.kind not in ("agent", "brief"):
|
|
499
|
+
raise ValueError(
|
|
500
|
+
f"step {self.name!r}: 'llm' names an LLM profile and is "
|
|
501
|
+
"only allowed on 'agent' or 'brief_inputs' steps"
|
|
502
|
+
)
|
|
489
503
|
if self.brief_inputs is not None:
|
|
490
504
|
if self.produces:
|
|
491
505
|
raise ValueError(
|
|
@@ -587,9 +601,9 @@ class LlmConfig(BaseModel):
|
|
|
587
601
|
base_url: str | None = None
|
|
588
602
|
api_key_env: str
|
|
589
603
|
timeout: float | None = None # omitted → opinionated no-timeout default
|
|
590
|
-
# 047 (D1): opt-in passthrough; unset means
|
|
591
|
-
#
|
|
592
|
-
#
|
|
604
|
+
# 047 (D1) / 054 (D6): opt-in passthrough; unset means reasoning is off
|
|
605
|
+
# (the engine forwards none and strips reasoning passback). The SDK
|
|
606
|
+
# "high" default never engages because build_llm always passes the value.
|
|
593
607
|
reasoning_effort: ReasoningEffort | None = None
|
|
594
608
|
|
|
595
609
|
|
|
@@ -606,6 +620,9 @@ class PipelineConfig(BaseModel):
|
|
|
606
620
|
extensions_dir: str = "~/.agentic-dev-loop/extensions"
|
|
607
621
|
env_allowlist: list[str] = []
|
|
608
622
|
llm: LlmConfig
|
|
623
|
+
# 054 (D1): named LLM profiles, each a strict LlmConfig. Optional and
|
|
624
|
+
# purely additive - the `llm:` block above remains the default.
|
|
625
|
+
llms: dict[str, LlmConfig] = {}
|
|
609
626
|
isolation: IsolationConfig | None = None # None = always-on defaults
|
|
610
627
|
state_dir: str = ".pr" # default relocated to ~/.agentic-dev-loop/runs/<hash>/ at load
|
|
611
628
|
secrets: dict[str, SecretSpec] = {} # ref name -> locator; values never appear here
|
|
@@ -690,6 +707,23 @@ class PipelineConfig(BaseModel):
|
|
|
690
707
|
"enforces, or set 'deps_policy: open'")
|
|
691
708
|
return self
|
|
692
709
|
|
|
710
|
+
@model_validator(mode="after")
|
|
711
|
+
def _check_llm_profiles(self) -> "PipelineConfig":
|
|
712
|
+
"""054 (D5): the registry is additive and referential. A profile
|
|
713
|
+
literally named 'default' is rejected (the `llm:` block already is the
|
|
714
|
+
default), and any step naming an undeclared profile fails at load,
|
|
715
|
+
never mid-run."""
|
|
716
|
+
if "default" in self.llms:
|
|
717
|
+
raise ValueError(
|
|
718
|
+
"'llms' cannot declare a profile named 'default' - the "
|
|
719
|
+
"top-level 'llm:' block is already the default")
|
|
720
|
+
for step in self.steps:
|
|
721
|
+
if step.llm is not None and step.llm not in self.llms:
|
|
722
|
+
raise ValueError(
|
|
723
|
+
f"step {step.name!r}: unknown LLM profile {step.llm!r} - "
|
|
724
|
+
f"declared profiles: {sorted(self.llms)}")
|
|
725
|
+
return self
|
|
726
|
+
|
|
693
727
|
@model_validator(mode="after")
|
|
694
728
|
def _mounts_avoid_state_dir(self) -> "PipelineConfig":
|
|
695
729
|
"""BR-1.3: the host-only run-state dir is never a mount path.
|
|
@@ -177,11 +177,25 @@ def run_pipeline(cfg_path, args, *, viz_bus=None,
|
|
|
177
177
|
sandbox.close()
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
def _build_profile_llms(cfg) -> dict:
|
|
181
|
+
"""054 (D7): build the LLM for each profile referenced by any step,
|
|
182
|
+
eagerly, so a missing api_key_env fails at run start, not mid-run.
|
|
183
|
+
Declared-but-unreferenced profiles are shape-validated at load; their
|
|
184
|
+
keys are not checked here."""
|
|
185
|
+
referenced = {step.llm for step in cfg.steps if step.llm is not None}
|
|
186
|
+
return {name: build_llm(cfg.llms[name]) for name in sorted(referenced)}
|
|
187
|
+
|
|
188
|
+
|
|
180
189
|
def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
181
190
|
sandbox, resolved_secrets, tooling_paths) -> tuple[bool, int]:
|
|
182
191
|
export_root = getattr(sandbox, "export_root", None)
|
|
183
192
|
engine_allowlist = resolve_engine_allowlist(registry, args)
|
|
193
|
+
# 054 (D7): build the default LLM plus one per profile referenced by any
|
|
194
|
+
# step, eagerly, so a missing api_key_env for a referenced profile fails
|
|
195
|
+
# at run start, not mid-run.
|
|
196
|
+
profile_llms = _build_profile_llms(cfg)
|
|
184
197
|
runtime = Runtime(cfg=cfg, registry=registry, llm=build_llm(cfg.llm),
|
|
198
|
+
llms=profile_llms,
|
|
185
199
|
agents_dir=base / cfg.agents_dir, state_dir=state_dir,
|
|
186
200
|
workspace=Path.cwd(), sandbox=sandbox,
|
|
187
201
|
sandbox_cwd=Path(export_root) if export_root else None,
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""LLM construction from pipeline config. Fail fast on a missing key -
|
|
2
|
+
never fall back to another env var (no silent fallbacks)."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from openhands.sdk import LLM
|
|
7
|
+
|
|
8
|
+
from agentic_dev_loop.core.config import LlmConfig
|
|
9
|
+
|
|
10
|
+
# 054 (D8): reasoning passback is config-gated, never name-list-gated. The
|
|
11
|
+
# SDK gates reasoning_content passback on a closed model-name list
|
|
12
|
+
# (SEND_REASONING_CONTENT_MODELS) the engine never consults; instead each
|
|
13
|
+
# LLM instance carries the engine-resolved effort on
|
|
14
|
+
# ``_agentic_dev_loop_reasoning_effort`` and the serializer below gates
|
|
15
|
+
# passback on it. Gate on = the effort is set and not "none"; gate off =
|
|
16
|
+
# reasoning is stripped and, when history still carries reasoning_content,
|
|
17
|
+
# a clear engine error fires before the doomed request is sent (D12).
|
|
18
|
+
_ORIGINAL_FORMAT_MESSAGES = LLM.format_messages_for_llm
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _reasoning_gate_on(llm: LLM) -> bool:
|
|
22
|
+
"""The engine-configured effort for this instance, if any. The SDK's own
|
|
23
|
+
``reasoning_effort`` field defaults to "high", so a direct (non-engine)
|
|
24
|
+
instance would read as gate-on; the dedicated attribute is only set by
|
|
25
|
+
``build_llm``, keeping absent effort = reasoning off."""
|
|
26
|
+
effort = getattr(llm, "_agentic_dev_loop_reasoning_effort", None)
|
|
27
|
+
return effort is not None and effort != "none"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _reasoning_stripped_error(llm: LLM) -> RuntimeError:
|
|
31
|
+
"""054 (D12): gate off while the conversation still carries
|
|
32
|
+
reasoning_content - a request that strips it would 400 at the provider."""
|
|
33
|
+
effort = getattr(llm, "_agentic_dev_loop_reasoning_effort", None)
|
|
34
|
+
return RuntimeError(
|
|
35
|
+
f"model {llm.model!r} is configured with reasoning off "
|
|
36
|
+
f"(reasoning_effort={effort!r}) but the conversation history carries "
|
|
37
|
+
"reasoning_content that would be stripped from the request. Set "
|
|
38
|
+
"'reasoning_effort' (low/medium/high/xhigh) on the step or its llm "
|
|
39
|
+
"profile, use 'none' only where the provider honors it, or use a "
|
|
40
|
+
"non-thinking model."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if not getattr(_ORIGINAL_FORMAT_MESSAGES, "_agentic_dev_loop_reasoning_repair", False):
|
|
45
|
+
def _config_gated_reasoning_serializer(self, messages: list) -> list[dict]:
|
|
46
|
+
"""The engine-owned reasoning passback serializer (054 D8).
|
|
47
|
+
|
|
48
|
+
Gate on: every assistant message that carries reasoning_content
|
|
49
|
+
writes it back (the SDK only does so for listed models and only on
|
|
50
|
+
the first message of a split tool-call turn), and a tool-call
|
|
51
|
+
message without its own reasoning borrows the turn's last reasoning
|
|
52
|
+
(reset on user turns). Gate off: reasoning is stripped from the
|
|
53
|
+
outgoing request and, if history still carries reasoning_content,
|
|
54
|
+
the D12 error fires instead of sending a doomed request.
|
|
55
|
+
"""
|
|
56
|
+
out = _ORIGINAL_FORMAT_MESSAGES(self, messages)
|
|
57
|
+
if not _reasoning_gate_on(self):
|
|
58
|
+
for d in out:
|
|
59
|
+
d.pop("reasoning_content", None)
|
|
60
|
+
if any(getattr(m, "reasoning_content", None) for m in messages):
|
|
61
|
+
raise _reasoning_stripped_error(self)
|
|
62
|
+
return out
|
|
63
|
+
|
|
64
|
+
last = None
|
|
65
|
+
for msg, d in zip(messages, out):
|
|
66
|
+
if msg.role == "user":
|
|
67
|
+
last = None
|
|
68
|
+
continue
|
|
69
|
+
if msg.role != "assistant":
|
|
70
|
+
continue
|
|
71
|
+
rc = getattr(msg, "reasoning_content", None)
|
|
72
|
+
if msg.tool_calls:
|
|
73
|
+
if rc is not None:
|
|
74
|
+
last = rc
|
|
75
|
+
if last is not None:
|
|
76
|
+
d["reasoning_content"] = last
|
|
77
|
+
elif rc:
|
|
78
|
+
d["reasoning_content"] = rc
|
|
79
|
+
return out
|
|
80
|
+
|
|
81
|
+
_config_gated_reasoning_serializer._agentic_dev_loop_reasoning_repair = True
|
|
82
|
+
LLM.format_messages_for_llm = _config_gated_reasoning_serializer
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_llm(cfg: LlmConfig, reasoning_effort: str | None = None) -> LLM:
|
|
86
|
+
api_key = os.environ.get(cfg.api_key_env)
|
|
87
|
+
if not api_key:
|
|
88
|
+
raise RuntimeError(f"{cfg.api_key_env} environment variable is required")
|
|
89
|
+
# 054 (D6): a step's value wins, else the effective profile's value, else
|
|
90
|
+
# off. reasoning_effort is always passed (None when unset) so the SDK
|
|
91
|
+
# "high" default cannot engage; absent effort = reasoning off.
|
|
92
|
+
effort = reasoning_effort if reasoning_effort is not None else cfg.reasoning_effort
|
|
93
|
+
llm = LLM(
|
|
94
|
+
model=cfg.model,
|
|
95
|
+
api_key=api_key,
|
|
96
|
+
base_url=cfg.base_url,
|
|
97
|
+
timeout=cfg.timeout, # policy: no LLM timeout unless explicitly set
|
|
98
|
+
reasoning_effort=effort,
|
|
99
|
+
)
|
|
100
|
+
llm._agentic_dev_loop_reasoning_effort = effort
|
|
101
|
+
return llm
|
|
@@ -120,6 +120,9 @@ class Runtime:
|
|
|
120
120
|
agents_dir: Path
|
|
121
121
|
state_dir: Path
|
|
122
122
|
workspace: Path
|
|
123
|
+
# 054 (D7): named-profile LLMs built eagerly at run start, keyed by the
|
|
124
|
+
# `llms:` profile name. `llm` above remains the default (`llm:` block).
|
|
125
|
+
llms: dict[str, "LLM"] = field(default_factory=dict)
|
|
123
126
|
sandbox: Sandbox | None = None
|
|
124
127
|
# unit-5: the .git-free export root sandboxed steps run in (BR-2); None =
|
|
125
128
|
# the real workspace (no export materialised - fake/legacy paths).
|
|
@@ -176,13 +179,18 @@ class Runtime:
|
|
|
176
179
|
if step is not None else None),
|
|
177
180
|
forbidden_commands=forbidden_commands,
|
|
178
181
|
allowlist=allowlist)
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
+
# 054 (D6/D7): a named profile selects the whole config (model,
|
|
183
|
+
# endpoint, key, effort default move together); the step's own
|
|
184
|
+
# reasoning_effort then overrides the effective profile's value.
|
|
182
185
|
llm = self.llm
|
|
183
|
-
if step is not None
|
|
184
|
-
|
|
185
|
-
|
|
186
|
+
if step is not None:
|
|
187
|
+
effective_cfg = (self.cfg.llms[step.llm]
|
|
188
|
+
if step.llm is not None else self.cfg.llm)
|
|
189
|
+
if step.llm is not None:
|
|
190
|
+
llm = self.llms.get(step.llm, self.llm)
|
|
191
|
+
if step.reasoning_effort is not None:
|
|
192
|
+
llm = build_llm(effective_cfg,
|
|
193
|
+
reasoning_effort=step.reasoning_effort)
|
|
186
194
|
if step is not None and step.kind == "brief":
|
|
187
195
|
agent = load_brief_agent(
|
|
188
196
|
agent_name, step.brief_inputs.sections, llm)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.10.0
|
|
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
|
|
@@ -202,6 +202,17 @@ llm:
|
|
|
202
202
|
api_key_env: OPENAI_API_KEY
|
|
203
203
|
# reasoning_effort: medium # optional: low | medium | high | xhigh | none
|
|
204
204
|
|
|
205
|
+
# Named LLM profiles (054): a step names one with `llm: <name>` to move the
|
|
206
|
+
# whole profile (model, endpoint, key, effort default) together - no partial
|
|
207
|
+
# merges. The `llm:` block above remains the default. Profile keys are
|
|
208
|
+
# host-side: sandbox access still goes through `secrets:`, never a profile key.
|
|
209
|
+
# llms:
|
|
210
|
+
# review:
|
|
211
|
+
# model: openai/deepseek-v4-pro
|
|
212
|
+
# base_url: https://api.deepseek.com
|
|
213
|
+
# api_key_env: REVIEW_API_KEY
|
|
214
|
+
# reasoning_effort: xhigh
|
|
215
|
+
|
|
205
216
|
max_attempts: 10 # CLI --max-attempts overrides
|
|
206
217
|
agents_dir: sdk_agents # your <name>.agent.md files
|
|
207
218
|
|
|
@@ -231,7 +242,8 @@ steps:
|
|
|
231
242
|
agent: review
|
|
232
243
|
artifact: review-findings.md
|
|
233
244
|
verdicts: { pass: APPROVED, fail: NEEDS CHANGES }
|
|
234
|
-
# reasoning_effort: xhigh # per-step override
|
|
245
|
+
# reasoning_effort: xhigh # per-step override of the effective profile's effort
|
|
246
|
+
# llm: review # (054) select a named `llms:` profile for this step
|
|
235
247
|
on_pass: qa
|
|
236
248
|
on_fail: { goto: dev, retry: true }
|
|
237
249
|
|
|
@@ -257,7 +269,8 @@ steps:
|
|
|
257
269
|
| `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. |
|
|
258
270
|
| `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). |
|
|
259
271
|
| `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. |
|
|
260
|
-
| `
|
|
272
|
+
| `llms:` (top-level) / step `llm:` | Named LLM profiles, each a full `LlmConfig` (`model`, `base_url`, `api_key_env`, `timeout`, `reasoning_effort`). A step names one with `llm: <profile-name>`; the whole profile moves together (no partial merges). Allowed only on agent and brief steps. The top-level `llm:` block remains the default. Profile keys are host-side - sandbox access still goes through `secrets:`. |
|
|
273
|
+
| `reasoning_effort:` | Optional `low` / `medium` / `high` / `xhigh` / `none`. Set on the `llm` block, a `llms:` profile, or per-step (a step value overrides the effective profile's). Unset anywhere = reasoning off: nothing is forwarded and reasoning is stripped from requests. A thinking model without an effort level is invalid config and fails at the first request (crap in, crap out). See [Reasoning effort](#reasoning-effort-opt-in). |
|
|
261
274
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
262
275
|
| `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` runs fresh at every step boundary in a one-shot provision container targeting the export (command or agent steps; network ON; no stamping); `network: true` lets a command/agent step reach the hosts its `egress:` allowlist names (default off) - see [Per-stage networking](#per-stage-networking). |
|
|
263
276
|
| `mounts:` / `egress:` | Per-step workspace isolation. `mounts:` lists `{path, mode}` entries (`rw`/`ro`, default `ro`) and is **strict-by-default** - empty/missing mounts nothing; only declared-rw paths sync back to the repo. `egress:` is `{class, presets, targets}` (`class: browser` = broad-but-logged; otherwise a strict allowlist). See [Per-stage networking](#per-stage-networking) and [docs/setup.md](docs/setup.md). |
|
|
@@ -272,17 +285,25 @@ steps:
|
|
|
272
285
|
|
|
273
286
|
### Reasoning effort (opt-in)
|
|
274
287
|
|
|
275
|
-
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block
|
|
276
|
-
|
|
277
|
-
or `none`; unset means
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
288
|
+
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block, a
|
|
289
|
+
named `llms:` profile, and individual agent steps. It takes one of `low`,
|
|
290
|
+
`medium`, `high`, `xhigh`, or `none`; unset means reasoning is off - the
|
|
291
|
+
field is forwarded as none, reasoning is stripped from requests, and a
|
|
292
|
+
thinking model whose history still carries reasoning fails fast with an
|
|
293
|
+
actionable engine error rather than a provider 400. A step's value overrides
|
|
294
|
+
the effective profile's value, so the review lane can hold a higher effort
|
|
295
|
+
than the rest, or a named profile can move an entire lane to a different
|
|
296
|
+
provider.
|
|
297
|
+
|
|
298
|
+
Reasoning is opt-in and provider-generic: any openai-compatible endpoint
|
|
299
|
+
expressible as an `LlmConfig` works, and reasoning passback is gated on the
|
|
300
|
+
configured effort level, never on a model-name list. A thinking model without
|
|
301
|
+
an effort level (or with `none` where the provider ignores it) is invalid
|
|
302
|
+
config - it still reasons but its API requires the passback the engine strips,
|
|
303
|
+
so the engine raises before the doomed request is sent. Set
|
|
304
|
+
`reasoning_effort` on the step or its profile, or use a non-thinking model.
|
|
305
|
+
The schema rejects unknown effort names at load time, but cannot know provider
|
|
306
|
+
capability; adopt an effort value only after an observed run proves lower
|
|
286
307
|
per-turn latency with a green suite and no provider 400s.
|
|
287
308
|
|
|
288
309
|
Recommended lane defaults (guidance only, not enforced):
|
|
@@ -515,22 +536,23 @@ adapt per repo.
|
|
|
515
536
|
Adaptation checklist for a new repo:
|
|
516
537
|
|
|
517
538
|
1. `llm:` - model, `base_url`, `api_key_env`.
|
|
518
|
-
2. `
|
|
539
|
+
2. `llms:` (optional) - named profiles for multi-provider lanes (keep review on the strongest model); a step opts in with `llm: <name>`.
|
|
540
|
+
3. `isolation:` - toolchain image override (the consumer repo pins a
|
|
519
541
|
toolchain image by digest) and `seccomp_profile: bundled:chromium-userns`
|
|
520
542
|
only if a stage runs Chromium.
|
|
521
|
-
|
|
543
|
+
4. `caches:` - toolchain cache dirs as rw bind-mounts shared across runs
|
|
522
544
|
(`.nuget-packages`, `qa/node_modules`, `qa/.browsers`,
|
|
523
545
|
`frontend-editor/node_modules`); caches are never synced back, and a
|
|
524
546
|
step's `setup:` hook runs with them mounted so it can warm them.
|
|
525
|
-
|
|
526
|
-
|
|
547
|
+
5. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
|
|
548
|
+
6. `secrets:` - refs only, resolved from host env; each step opts in to the
|
|
527
549
|
refs it needs.
|
|
528
|
-
|
|
550
|
+
7. `egress:` - `presets: [package-registries]` for fetch steps;
|
|
529
551
|
`class: browser` (broad-but-logged) for browser-tier qa only.
|
|
530
|
-
|
|
552
|
+
8. `deps_policy:` and `lockfiles:` - see
|
|
531
553
|
[Dependency policy](#dependency-policy-locked-by-default).
|
|
532
|
-
|
|
533
|
-
|
|
554
|
+
9. The dev prompt's impossible set - write it for your toolchain.
|
|
555
|
+
10. `additional_files:` - the repo's `AGENTS.md`, coding standards, design
|
|
534
556
|
docs: shared context for every agent step.
|
|
535
557
|
|
|
536
558
|
Validate before the first run - dry-run loads and prints the resolved plan
|