agentic-dev-loop 0.9.2__tar.gz → 0.10.2__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.2}/PKG-INFO +81 -32
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/README.md +80 -31
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/pyproject.toml +3 -1
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/core/config.py +58 -5
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/core/profiles/__init__.py +178 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/core/profiles/data/dotnet-node.yaml +22 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/core/profiles/data/python.yaml +8 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/cli.py +6 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/graph.py +14 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/hostboundary.py +3 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/llm.py +101 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/__init__.py +6 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/checks.py +272 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/doctor.py +84 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/init.py +432 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/recipes/dotnet-node.yaml +85 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/recipes/python.yaml +57 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/recipes.py +77 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/templates/dev.agent.md +133 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/templates/env.example +11 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/templates/qa.agent.md +181 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/templates/review.agent.md +53 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/templates/trigger-agentic-dev-loop.sh +97 -0
- agentic_dev_loop-0.10.2/src/agentic_dev_loop/engine/onboarding/templates.py +62 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/stages.py +14 -6
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2/src/agentic_dev_loop.egg-info}/PKG-INFO +81 -32
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop.egg-info/SOURCES.txt +24 -0
- agentic_dev_loop-0.10.2/tests/test_055_cli.py +51 -0
- agentic_dev_loop-0.10.2/tests/test_055_doctor.py +163 -0
- agentic_dev_loop-0.10.2/tests/test_055_init.py +135 -0
- agentic_dev_loop-0.10.2/tests/test_055_profile_loading.py +73 -0
- agentic_dev_loop-0.10.2/tests/test_055_profiles.py +156 -0
- agentic_dev_loop-0.10.2/tests/test_055_recipes.py +84 -0
- agentic_dev_loop-0.10.2/tests/test_055_schema_lock.py +42 -0
- agentic_dev_loop-0.10.2/tests/test_055_templates.py +80 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_config.py +88 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_graph.py +66 -1
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_hostboundary.py +10 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_llm.py +89 -36
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_module_contract.py +20 -1
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_publish.py +38 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_sdk_compat.py +6 -5
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_stages.py +80 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_trigger_script.py +84 -32
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_viz.py +1 -1
- 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.2}/LICENSE +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/setup.cfg +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/containers.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/isolation.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_035_finalize_nudge.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_041_brief_coverage.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_046_brief_prompt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_actions.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_agents.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_cli.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_containers.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_deps.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_proc.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_registry.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_results.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-0.9.2 → agentic_dev_loop-0.10.2}/tests/test_verdict.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.2
|
|
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,11 +269,13 @@ 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). |
|
|
264
277
|
| `secrets:` (top-level) / step `secrets:` | Declares secret **refs only - never values**; each step opts in to the refs it needs. Resolved host-side and injected into that step's exec env only. See [Secrets](#secrets--refs-only-never-values). |
|
|
278
|
+
| `profile:` (top-level) | Names a bundled toolchain profile (`python`, `dotnet-node`) or a consumer profile file path. `load_config` merges the profile at load time: isolation key-wise with the pipeline winning, caches unioned profile-first. Absent = today's behavior. |
|
|
265
279
|
| `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`, `seccomp_profile`. It can never disable it. See [browser sandbox](docs/browser-sandbox.md). |
|
|
266
280
|
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` - artifacts and checkpoints live outside the sandbox mount. |
|
|
267
281
|
| `extensions_dir` | Where consumer `extensions:` modules resolve - defaults to `~/.agentic-dev-loop/extensions`, **outside** the workspace. Workspace-resident extension/tool modules are refused at load. |
|
|
@@ -272,17 +286,25 @@ steps:
|
|
|
272
286
|
|
|
273
287
|
### Reasoning effort (opt-in)
|
|
274
288
|
|
|
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
|
-
|
|
289
|
+
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block, a
|
|
290
|
+
named `llms:` profile, and individual agent steps. It takes one of `low`,
|
|
291
|
+
`medium`, `high`, `xhigh`, or `none`; unset means reasoning is off - the
|
|
292
|
+
field is forwarded as none, reasoning is stripped from requests, and a
|
|
293
|
+
thinking model whose history still carries reasoning fails fast with an
|
|
294
|
+
actionable engine error rather than a provider 400. A step's value overrides
|
|
295
|
+
the effective profile's value, so the review lane can hold a higher effort
|
|
296
|
+
than the rest, or a named profile can move an entire lane to a different
|
|
297
|
+
provider.
|
|
298
|
+
|
|
299
|
+
Reasoning is opt-in and provider-generic: any openai-compatible endpoint
|
|
300
|
+
expressible as an `LlmConfig` works, and reasoning passback is gated on the
|
|
301
|
+
configured effort level, never on a model-name list. A thinking model without
|
|
302
|
+
an effort level (or with `none` where the provider ignores it) is invalid
|
|
303
|
+
config - it still reasons but its API requires the passback the engine strips,
|
|
304
|
+
so the engine raises before the doomed request is sent. Set
|
|
305
|
+
`reasoning_effort` on the step or its profile, or use a non-thinking model.
|
|
306
|
+
The schema rejects unknown effort names at load time, but cannot know provider
|
|
307
|
+
capability; adopt an effort value only after an observed run proves lower
|
|
286
308
|
per-turn latency with a green suite and no provider 400s.
|
|
287
309
|
|
|
288
310
|
Recommended lane defaults (guidance only, not enforced):
|
|
@@ -425,12 +447,39 @@ console.
|
|
|
425
447
|
|
|
426
448
|
## Authoring a loop for your own repo
|
|
427
449
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
450
|
+
### Start with `agentic-dev-loop init`
|
|
451
|
+
|
|
452
|
+
`init` writes the onboarding surface from a bundled toolchain profile:
|
|
453
|
+
|
|
454
|
+
```bash
|
|
455
|
+
agentic-dev-loop init --profile python # or: --profile dotnet-node
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
It detects solution files, `package.json` directories, lockfiles, README, and
|
|
459
|
+
AGENTS.md, asks only what detection cannot answer, and writes
|
|
460
|
+
`agentic-dev-loop/pipeline.yaml`, the three agent files, an executable
|
|
461
|
+
`trigger-agentic-dev-loop.sh`, `agentic-dev-loop/.engine-version` (the exact
|
|
462
|
+
installed engine version), any missing premise files, gitignore additions, and
|
|
463
|
+
an env-file template. It refuses to overwrite an existing
|
|
464
|
+
`agentic-dev-loop/` directory. A consumer-authored profile file can be passed
|
|
465
|
+
by path instead of a bundled name.
|
|
466
|
+
|
|
467
|
+
Profiles carry the environment facts every app on a toolchain shares (image,
|
|
468
|
+
cpus, caches, premise files, gitignore lines); recipes carry the gate shape and
|
|
469
|
+
command vocabulary. The pipeline references a profile with the top-level
|
|
470
|
+
`profile:` key, and `load_config` merges it at load time: the pipeline wins on
|
|
471
|
+
scalars, caches union profile-first, and absence of the key behaves exactly as
|
|
472
|
+
before.
|
|
473
|
+
|
|
474
|
+
`agentic-dev-loop doctor --fast` is the per-run gate the trigger runs first.
|
|
475
|
+
Hard failures abort the run (engine pin mismatch, engine runtime availability,
|
|
476
|
+
API version, git repo, `.env*` files); warnings print and continue. `doctor
|
|
477
|
+
--json` emits the same results mechanically. The trigger repairs a pin/install
|
|
478
|
+
mismatch exactly once, then fails hard.
|
|
479
|
+
|
|
480
|
+
A consumer repo needs a pipeline file and the agent files it names, both
|
|
481
|
+
committed before the first run (the host-boundary check refuses dirty tooling,
|
|
482
|
+
and the `commit` step never commits it). `init` writes them; the layout is:
|
|
434
483
|
|
|
435
484
|
```
|
|
436
485
|
<repo>/agentic-dev-loop/pipeline.yaml
|
|
@@ -515,22 +564,22 @@ adapt per repo.
|
|
|
515
564
|
Adaptation checklist for a new repo:
|
|
516
565
|
|
|
517
566
|
1. `llm:` - model, `base_url`, `api_key_env`.
|
|
518
|
-
2. `
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
567
|
+
2. `llms:` (optional) - named profiles for multi-provider lanes (keep review on the strongest model); a step opts in with `llm: <name>`.
|
|
568
|
+
3. `profile:` - name a bundled profile (`python`, `dotnet-node`) or a
|
|
569
|
+
consumer profile path. The profile supplies `isolation:` (image,
|
|
570
|
+
`seccomp_profile`, cpus) and `caches:`; pipeline keys override its scalars.
|
|
571
|
+
4. `caches:` - profile-supplied toolchain caches plus any run-specific dirs
|
|
572
|
+
(`.nuget-packages`, `qa/.browsers`); caches are never synced back, and a
|
|
524
573
|
step's `setup:` hook runs with them mounted so it can warm them.
|
|
525
|
-
|
|
526
|
-
|
|
574
|
+
5. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
|
|
575
|
+
6. `secrets:` - refs only, resolved from host env; each step opts in to the
|
|
527
576
|
refs it needs.
|
|
528
|
-
|
|
577
|
+
7. `egress:` - `presets: [package-registries]` for fetch steps;
|
|
529
578
|
`class: browser` (broad-but-logged) for browser-tier qa only.
|
|
530
|
-
|
|
579
|
+
8. `deps_policy:` and `lockfiles:` - see
|
|
531
580
|
[Dependency policy](#dependency-policy-locked-by-default).
|
|
532
|
-
|
|
533
|
-
|
|
581
|
+
9. The dev prompt's impossible set - write it for your toolchain.
|
|
582
|
+
10. `additional_files:` - the repo's `AGENTS.md`, coding standards, design
|
|
534
583
|
docs: shared context for every agent step.
|
|
535
584
|
|
|
536
585
|
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,11 +225,13 @@ 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). |
|
|
220
233
|
| `secrets:` (top-level) / step `secrets:` | Declares secret **refs only - never values**; each step opts in to the refs it needs. Resolved host-side and injected into that step's exec env only. See [Secrets](#secrets--refs-only-never-values). |
|
|
234
|
+
| `profile:` (top-level) | Names a bundled toolchain profile (`python`, `dotnet-node`) or a consumer profile file path. `load_config` merges the profile at load time: isolation key-wise with the pipeline winning, caches unioned profile-first. Absent = today's behavior. |
|
|
221
235
|
| `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`, `seccomp_profile`. It can never disable it. See [browser sandbox](docs/browser-sandbox.md). |
|
|
222
236
|
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` - artifacts and checkpoints live outside the sandbox mount. |
|
|
223
237
|
| `extensions_dir` | Where consumer `extensions:` modules resolve - defaults to `~/.agentic-dev-loop/extensions`, **outside** the workspace. Workspace-resident extension/tool modules are refused at load. |
|
|
@@ -228,17 +242,25 @@ steps:
|
|
|
228
242
|
|
|
229
243
|
### Reasoning effort (opt-in)
|
|
230
244
|
|
|
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
|
-
|
|
245
|
+
`reasoning_effort` is an opt-in latency dial on the pipeline `llm` block, a
|
|
246
|
+
named `llms:` profile, and individual agent steps. It takes one of `low`,
|
|
247
|
+
`medium`, `high`, `xhigh`, or `none`; unset means reasoning is off - the
|
|
248
|
+
field is forwarded as none, reasoning is stripped from requests, and a
|
|
249
|
+
thinking model whose history still carries reasoning fails fast with an
|
|
250
|
+
actionable engine error rather than a provider 400. A step's value overrides
|
|
251
|
+
the effective profile's value, so the review lane can hold a higher effort
|
|
252
|
+
than the rest, or a named profile can move an entire lane to a different
|
|
253
|
+
provider.
|
|
254
|
+
|
|
255
|
+
Reasoning is opt-in and provider-generic: any openai-compatible endpoint
|
|
256
|
+
expressible as an `LlmConfig` works, and reasoning passback is gated on the
|
|
257
|
+
configured effort level, never on a model-name list. A thinking model without
|
|
258
|
+
an effort level (or with `none` where the provider ignores it) is invalid
|
|
259
|
+
config - it still reasons but its API requires the passback the engine strips,
|
|
260
|
+
so the engine raises before the doomed request is sent. Set
|
|
261
|
+
`reasoning_effort` on the step or its profile, or use a non-thinking model.
|
|
262
|
+
The schema rejects unknown effort names at load time, but cannot know provider
|
|
263
|
+
capability; adopt an effort value only after an observed run proves lower
|
|
242
264
|
per-turn latency with a green suite and no provider 400s.
|
|
243
265
|
|
|
244
266
|
Recommended lane defaults (guidance only, not enforced):
|
|
@@ -381,12 +403,39 @@ console.
|
|
|
381
403
|
|
|
382
404
|
## Authoring a loop for your own repo
|
|
383
405
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
406
|
+
### Start with `agentic-dev-loop init`
|
|
407
|
+
|
|
408
|
+
`init` writes the onboarding surface from a bundled toolchain profile:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
agentic-dev-loop init --profile python # or: --profile dotnet-node
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
It detects solution files, `package.json` directories, lockfiles, README, and
|
|
415
|
+
AGENTS.md, asks only what detection cannot answer, and writes
|
|
416
|
+
`agentic-dev-loop/pipeline.yaml`, the three agent files, an executable
|
|
417
|
+
`trigger-agentic-dev-loop.sh`, `agentic-dev-loop/.engine-version` (the exact
|
|
418
|
+
installed engine version), any missing premise files, gitignore additions, and
|
|
419
|
+
an env-file template. It refuses to overwrite an existing
|
|
420
|
+
`agentic-dev-loop/` directory. A consumer-authored profile file can be passed
|
|
421
|
+
by path instead of a bundled name.
|
|
422
|
+
|
|
423
|
+
Profiles carry the environment facts every app on a toolchain shares (image,
|
|
424
|
+
cpus, caches, premise files, gitignore lines); recipes carry the gate shape and
|
|
425
|
+
command vocabulary. The pipeline references a profile with the top-level
|
|
426
|
+
`profile:` key, and `load_config` merges it at load time: the pipeline wins on
|
|
427
|
+
scalars, caches union profile-first, and absence of the key behaves exactly as
|
|
428
|
+
before.
|
|
429
|
+
|
|
430
|
+
`agentic-dev-loop doctor --fast` is the per-run gate the trigger runs first.
|
|
431
|
+
Hard failures abort the run (engine pin mismatch, engine runtime availability,
|
|
432
|
+
API version, git repo, `.env*` files); warnings print and continue. `doctor
|
|
433
|
+
--json` emits the same results mechanically. The trigger repairs a pin/install
|
|
434
|
+
mismatch exactly once, then fails hard.
|
|
435
|
+
|
|
436
|
+
A consumer repo needs a pipeline file and the agent files it names, both
|
|
437
|
+
committed before the first run (the host-boundary check refuses dirty tooling,
|
|
438
|
+
and the `commit` step never commits it). `init` writes them; the layout is:
|
|
390
439
|
|
|
391
440
|
```
|
|
392
441
|
<repo>/agentic-dev-loop/pipeline.yaml
|
|
@@ -471,22 +520,22 @@ adapt per repo.
|
|
|
471
520
|
Adaptation checklist for a new repo:
|
|
472
521
|
|
|
473
522
|
1. `llm:` - model, `base_url`, `api_key_env`.
|
|
474
|
-
2. `
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
523
|
+
2. `llms:` (optional) - named profiles for multi-provider lanes (keep review on the strongest model); a step opts in with `llm: <name>`.
|
|
524
|
+
3. `profile:` - name a bundled profile (`python`, `dotnet-node`) or a
|
|
525
|
+
consumer profile path. The profile supplies `isolation:` (image,
|
|
526
|
+
`seccomp_profile`, cpus) and `caches:`; pipeline keys override its scalars.
|
|
527
|
+
4. `caches:` - profile-supplied toolchain caches plus any run-specific dirs
|
|
528
|
+
(`.nuget-packages`, `qa/.browsers`); caches are never synced back, and a
|
|
480
529
|
step's `setup:` hook runs with them mounted so it can warm them.
|
|
481
|
-
|
|
482
|
-
|
|
530
|
+
5. `sync_excludes:` - build output trees (`**/bin/**`, `**/obj/**`).
|
|
531
|
+
6. `secrets:` - refs only, resolved from host env; each step opts in to the
|
|
483
532
|
refs it needs.
|
|
484
|
-
|
|
533
|
+
7. `egress:` - `presets: [package-registries]` for fetch steps;
|
|
485
534
|
`class: browser` (broad-but-logged) for browser-tier qa only.
|
|
486
|
-
|
|
535
|
+
8. `deps_policy:` and `lockfiles:` - see
|
|
487
536
|
[Dependency policy](#dependency-policy-locked-by-default).
|
|
488
|
-
|
|
489
|
-
|
|
537
|
+
9. The dev prompt's impossible set - write it for your toolchain.
|
|
538
|
+
10. `additional_files:` - the repo's `AGENTS.md`, coding standards, design
|
|
490
539
|
docs: shared context for every agent step.
|
|
491
540
|
|
|
492
541
|
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.2"
|
|
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"
|
|
@@ -76,8 +76,10 @@ where = ["src"]
|
|
|
76
76
|
|
|
77
77
|
[tool.setuptools.package-data]
|
|
78
78
|
agentic_dev_loop = ["py.typed"]
|
|
79
|
+
"agentic_dev_loop.core.profiles" = ["data/*.yaml"]
|
|
79
80
|
"agentic_dev_loop.engine" = ["*.md"]
|
|
80
81
|
"agentic_dev_loop.engine.seccomp" = ["*.json"]
|
|
82
|
+
"agentic_dev_loop.engine.onboarding" = ["recipes/*.yaml", "templates/*"]
|
|
81
83
|
|
|
82
84
|
[tool.pytest.ini_options]
|
|
83
85
|
markers = [
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import hashlib
|
|
6
|
+
import importlib.metadata
|
|
6
7
|
import re
|
|
7
8
|
import warnings
|
|
8
9
|
from pathlib import Path
|
|
@@ -396,9 +397,13 @@ class StepConfig(BaseModel):
|
|
|
396
397
|
terminal_timeout: float | None = Field(default=None, gt=0) # seconds, agent steps only (032)
|
|
397
398
|
finalize_nudge_turns: int | None = Field(default=None, gt=0) # agent steps only (035)
|
|
398
399
|
brief_inputs: BriefInputsSpec | None = None # agent steps only (033)
|
|
399
|
-
# 047 (D7): per-step opt-in override; unset falls back to the
|
|
400
|
-
#
|
|
400
|
+
# 047 (D7) / 054 (D6): per-step opt-in override; unset falls back to the
|
|
401
|
+
# effective profile's value (the named `llms:` profile, else the `llm:`
|
|
402
|
+
# block), else reasoning is off. Full SDK literal, passthrough semantics.
|
|
401
403
|
reasoning_effort: ReasoningEffort | None = None
|
|
404
|
+
# 054 (D3): names a top-level `llms:` profile; the whole profile moves
|
|
405
|
+
# together (model, endpoint, key, effort default). None = the `llm:` block.
|
|
406
|
+
llm: str | None = None
|
|
402
407
|
custom_prompt: str | None = None # agent steps only (040); file path resolved at load
|
|
403
408
|
env: dict[str, str | None] | None = None # NFR-1: null = inherit host value
|
|
404
409
|
setup: str | None = None # command or agent steps; runs fresh at each step entry (052)
|
|
@@ -486,6 +491,16 @@ class StepConfig(BaseModel):
|
|
|
486
491
|
f"step {self.name!r}: exactly one of 'agent', 'action', "
|
|
487
492
|
"'command', or 'brief_inputs' is required"
|
|
488
493
|
)
|
|
494
|
+
if self.llm is not None:
|
|
495
|
+
if not self.llm.strip():
|
|
496
|
+
raise ValueError(
|
|
497
|
+
f"step {self.name!r}: 'llm' must be a non-empty profile name"
|
|
498
|
+
)
|
|
499
|
+
if self.kind not in ("agent", "brief"):
|
|
500
|
+
raise ValueError(
|
|
501
|
+
f"step {self.name!r}: 'llm' names an LLM profile and is "
|
|
502
|
+
"only allowed on 'agent' or 'brief_inputs' steps"
|
|
503
|
+
)
|
|
489
504
|
if self.brief_inputs is not None:
|
|
490
505
|
if self.produces:
|
|
491
506
|
raise ValueError(
|
|
@@ -587,9 +602,9 @@ class LlmConfig(BaseModel):
|
|
|
587
602
|
base_url: str | None = None
|
|
588
603
|
api_key_env: str
|
|
589
604
|
timeout: float | None = None # omitted → opinionated no-timeout default
|
|
590
|
-
# 047 (D1): opt-in passthrough; unset means
|
|
591
|
-
#
|
|
592
|
-
#
|
|
605
|
+
# 047 (D1) / 054 (D6): opt-in passthrough; unset means reasoning is off
|
|
606
|
+
# (the engine forwards none and strips reasoning passback). The SDK
|
|
607
|
+
# "high" default never engages because build_llm always passes the value.
|
|
593
608
|
reasoning_effort: ReasoningEffort | None = None
|
|
594
609
|
|
|
595
610
|
|
|
@@ -606,6 +621,9 @@ class PipelineConfig(BaseModel):
|
|
|
606
621
|
extensions_dir: str = "~/.agentic-dev-loop/extensions"
|
|
607
622
|
env_allowlist: list[str] = []
|
|
608
623
|
llm: LlmConfig
|
|
624
|
+
# 054 (D1): named LLM profiles, each a strict LlmConfig. Optional and
|
|
625
|
+
# purely additive - the `llm:` block above remains the default.
|
|
626
|
+
llms: dict[str, LlmConfig] = {}
|
|
609
627
|
isolation: IsolationConfig | None = None # None = always-on defaults
|
|
610
628
|
state_dir: str = ".pr" # default relocated to ~/.agentic-dev-loop/runs/<hash>/ at load
|
|
611
629
|
secrets: dict[str, SecretSpec] = {} # ref name -> locator; values never appear here
|
|
@@ -630,6 +648,9 @@ class PipelineConfig(BaseModel):
|
|
|
630
648
|
# edit instead of raising. Same declared-path vocabulary as
|
|
631
649
|
# ``sync_excludes:``.
|
|
632
650
|
merge_on_conflict: list[str] = []
|
|
651
|
+
# 055: the toolchain profile resolved at load time. Kept on the validated
|
|
652
|
+
# config so the run records which profile supplied its defaults.
|
|
653
|
+
profile: str | None = None
|
|
633
654
|
|
|
634
655
|
@field_validator("deps_policy")
|
|
635
656
|
@classmethod
|
|
@@ -690,6 +711,23 @@ class PipelineConfig(BaseModel):
|
|
|
690
711
|
"enforces, or set 'deps_policy: open'")
|
|
691
712
|
return self
|
|
692
713
|
|
|
714
|
+
@model_validator(mode="after")
|
|
715
|
+
def _check_llm_profiles(self) -> "PipelineConfig":
|
|
716
|
+
"""054 (D5): the registry is additive and referential. A profile
|
|
717
|
+
literally named 'default' is rejected (the `llm:` block already is the
|
|
718
|
+
default), and any step naming an undeclared profile fails at load,
|
|
719
|
+
never mid-run."""
|
|
720
|
+
if "default" in self.llms:
|
|
721
|
+
raise ValueError(
|
|
722
|
+
"'llms' cannot declare a profile named 'default' - the "
|
|
723
|
+
"top-level 'llm:' block is already the default")
|
|
724
|
+
for step in self.steps:
|
|
725
|
+
if step.llm is not None and step.llm not in self.llms:
|
|
726
|
+
raise ValueError(
|
|
727
|
+
f"step {step.name!r}: unknown LLM profile {step.llm!r} - "
|
|
728
|
+
f"declared profiles: {sorted(self.llms)}")
|
|
729
|
+
return self
|
|
730
|
+
|
|
693
731
|
@model_validator(mode="after")
|
|
694
732
|
def _mounts_avoid_state_dir(self) -> "PipelineConfig":
|
|
695
733
|
"""BR-1.3: the host-only run-state dir is never a mount path.
|
|
@@ -858,6 +896,21 @@ def load_config(path: Path) -> PipelineConfig:
|
|
|
858
896
|
raise ValueError(
|
|
859
897
|
f"pipeline config must be a mapping, got {type(data).__name__}: {path}"
|
|
860
898
|
)
|
|
899
|
+
profile_ref = data.pop("profile", None)
|
|
900
|
+
if profile_ref is not None:
|
|
901
|
+
from agentic_dev_loop.core.profiles import (
|
|
902
|
+
ProfileError,
|
|
903
|
+
load_profile,
|
|
904
|
+
merge_profile,
|
|
905
|
+
)
|
|
906
|
+
|
|
907
|
+
try:
|
|
908
|
+
installed = importlib.metadata.version("agentic-dev-loop")
|
|
909
|
+
profile = load_profile(str(profile_ref), installed)
|
|
910
|
+
data = merge_profile(data, profile)
|
|
911
|
+
except ProfileError as exc:
|
|
912
|
+
raise ValueError(f"pipeline {path}: {exc}") from exc
|
|
913
|
+
data["profile"] = profile_ref
|
|
861
914
|
if "state_dir" not in data:
|
|
862
915
|
data["state_dir"] = default_state_dir(path)
|
|
863
916
|
cfg = PipelineConfig.model_validate(data)
|