agentic-dev-loop 1.0.0a48__tar.gz → 1.0.0a50__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.0a48/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a50}/PKG-INFO +15 -11
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/README.md +14 -10
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/core/config.py +62 -31
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/agents.py +6 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/dryrun.py +10 -3
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/graph.py +3 -4
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/isolation.py +35 -16
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/stages.py +9 -8
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50/src/agentic_dev_loop.egg-info}/PKG-INFO +15 -11
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_033_brief_step.py +29 -62
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_041_brief_coverage.py +10 -10
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_agent_error_guide.py +1 -1
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_config.py +83 -7
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_containers.py +5 -1
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_e2e_fixtures.py +7 -8
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_isolation.py +69 -7
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_publish.py +35 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/containers.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_035_finalize_nudge.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_046_brief_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/tests/test_viz.py +0 -0
{agentic_dev_loop-1.0.0a48/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a50}/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.0a50
|
|
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
|
|
@@ -169,7 +169,10 @@ engine-owned object to its owning run and a `live`, `orphaned`,
|
|
|
169
169
|
registry-owned orphans whose owner pid is dead (never unregistered names).
|
|
170
170
|
A SIGTERM to a running pipeline still closes the sandbox (the graph's
|
|
171
171
|
`finally` runs); SIGKILL is unhandleable, so its containers are reclaimed by
|
|
172
|
-
the startup reaper on the next run or by `containers --reap`.
|
|
172
|
+
the startup reaper on the next run or by `containers --reap`. The reaper
|
|
173
|
+
and `--reap` prune a registry entry only after the daemon confirms the
|
|
174
|
+
object is gone; a surviving object (a failed removal) keeps its entry so
|
|
175
|
+
the next startup retries it. Pass
|
|
173
176
|
`--state-dir` to point at a custom `isolation.state_dir`.
|
|
174
177
|
|
|
175
178
|
**What a run looks like** (the shipped self-loop example):
|
|
@@ -206,10 +209,11 @@ agents_dir: sdk_agents # your <name>.agent.md f
|
|
|
206
209
|
|
|
207
210
|
steps:
|
|
208
211
|
- name: brief
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
reasoning_effort: medium
|
|
213
|
+
brief_inputs:
|
|
214
|
+
include: [AGENTS.md, coding-standards-ddd.yaml]
|
|
215
|
+
sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
|
|
216
|
+
env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
|
|
213
217
|
on_pass: dev
|
|
214
218
|
|
|
215
219
|
- name: dev
|
|
@@ -249,9 +253,9 @@ steps:
|
|
|
249
253
|
|
|
250
254
|
| Key | Notes |
|
|
251
255
|
| --- | --- |
|
|
252
|
-
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
|
|
256
|
+
| `agent:` / `command:` / `action:` / `brief_inputs:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). `brief_inputs:` is a native brief step - see its row below. |
|
|
253
257
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
254
|
-
| `brief_inputs:` |
|
|
258
|
+
| `brief_inputs:` | The discriminator for a native brief step: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. There is no `agent:`, `produces:`, or `max_iterations:` - the prompt is engine-embedded, the capture file is `attempt-brief.md`, and the iteration cap is 12, all engine-owned. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as the step's task message, and regenerates the step only when those inputs change. `sections` is the ordered list of digest headings the brief must emit (default `[Repo rules digest, Unit scope digest, Environment facts, Open risks]`; non-empty and unique). `name` stays free-form, so multiple brief steps are allowed. |
|
|
255
259
|
| `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. |
|
|
256
260
|
| `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). |
|
|
257
261
|
| `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. |
|
|
@@ -294,9 +298,9 @@ Recommended lane defaults (guidance only, not enforced):
|
|
|
294
298
|
| review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
|
|
295
299
|
|
|
296
300
|
A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
|
|
297
|
-
run start (or when its inputs change), its `
|
|
298
|
-
starts, and the dev/review agents read that digest
|
|
299
|
-
rules and environment facts every attempt.
|
|
301
|
+
run start (or when its inputs change), its engine-owned `attempt-brief.md`
|
|
302
|
+
digest survives attempt starts, and the dev/review agents read that digest
|
|
303
|
+
instead of re-deriving repo rules and environment facts every attempt.
|
|
300
304
|
|
|
301
305
|
The brief digest is mechanically coverage-checked: the configured
|
|
302
306
|
`brief_inputs.sections` headings must each appear on their own line, every
|
|
@@ -125,7 +125,10 @@ engine-owned object to its owning run and a `live`, `orphaned`,
|
|
|
125
125
|
registry-owned orphans whose owner pid is dead (never unregistered names).
|
|
126
126
|
A SIGTERM to a running pipeline still closes the sandbox (the graph's
|
|
127
127
|
`finally` runs); SIGKILL is unhandleable, so its containers are reclaimed by
|
|
128
|
-
the startup reaper on the next run or by `containers --reap`.
|
|
128
|
+
the startup reaper on the next run or by `containers --reap`. The reaper
|
|
129
|
+
and `--reap` prune a registry entry only after the daemon confirms the
|
|
130
|
+
object is gone; a surviving object (a failed removal) keeps its entry so
|
|
131
|
+
the next startup retries it. Pass
|
|
129
132
|
`--state-dir` to point at a custom `isolation.state_dir`.
|
|
130
133
|
|
|
131
134
|
**What a run looks like** (the shipped self-loop example):
|
|
@@ -162,10 +165,11 @@ agents_dir: sdk_agents # your <name>.agent.md f
|
|
|
162
165
|
|
|
163
166
|
steps:
|
|
164
167
|
- name: brief
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
reasoning_effort: medium
|
|
169
|
+
brief_inputs:
|
|
170
|
+
include: [AGENTS.md, coding-standards-ddd.yaml]
|
|
171
|
+
sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
|
|
172
|
+
env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
|
|
169
173
|
on_pass: dev
|
|
170
174
|
|
|
171
175
|
- name: dev
|
|
@@ -205,9 +209,9 @@ steps:
|
|
|
205
209
|
|
|
206
210
|
| Key | Notes |
|
|
207
211
|
| --- | --- |
|
|
208
|
-
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
|
|
212
|
+
| `agent:` / `command:` / `action:` / `brief_inputs:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). `brief_inputs:` is a native brief step - see its row below. |
|
|
209
213
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
210
|
-
| `brief_inputs:` |
|
|
214
|
+
| `brief_inputs:` | The discriminator for a native brief step: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. There is no `agent:`, `produces:`, or `max_iterations:` - the prompt is engine-embedded, the capture file is `attempt-brief.md`, and the iteration cap is 12, all engine-owned. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as the step's task message, and regenerates the step only when those inputs change. `sections` is the ordered list of digest headings the brief must emit (default `[Repo rules digest, Unit scope digest, Environment facts, Open risks]`; non-empty and unique). `name` stays free-form, so multiple brief steps are allowed. |
|
|
211
215
|
| `terminal_timeout:` | Optional agent-step per-action timeout in seconds (gt 0). `SandboxTerminalExecutor` uses it when the agent leaves the terminal action timeout unset, overriding the SDK tool's default. `None` = current behavior. |
|
|
212
216
|
| `finalize_nudge_turns:` | Optional agent-step final-response nudge (gt 0). Requires `max_iterations:` and must be less than it. At `max_iterations - finalize_nudge_turns` completed turns the engine injects one advisory turn ("`N turns remain: produce your final response now`"), exactly once; the agent may still exhaust its turns and existing stuck/unclear handling applies unchanged. Absent = current behavior. Provisional pending one consumer run (revert if noisy or the raised cap inflates cost). |
|
|
213
217
|
| `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. |
|
|
@@ -250,9 +254,9 @@ Recommended lane defaults (guidance only, not enforced):
|
|
|
250
254
|
| review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
|
|
251
255
|
|
|
252
256
|
A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
|
|
253
|
-
run start (or when its inputs change), its `
|
|
254
|
-
starts, and the dev/review agents read that digest
|
|
255
|
-
rules and environment facts every attempt.
|
|
257
|
+
run start (or when its inputs change), its engine-owned `attempt-brief.md`
|
|
258
|
+
digest survives attempt starts, and the dev/review agents read that digest
|
|
259
|
+
instead of re-deriving repo rules and environment facts every attempt.
|
|
256
260
|
|
|
257
261
|
The brief digest is mechanically coverage-checked: the configured
|
|
258
262
|
`brief_inputs.sections` headings must each appear on their own line, every
|
|
@@ -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.0a50"
|
|
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"
|
|
@@ -35,6 +35,13 @@ DEFAULT_BRIEF_SECTIONS = [
|
|
|
35
35
|
"Open risks",
|
|
36
36
|
]
|
|
37
37
|
|
|
38
|
+
# 049: a brief step is engine-owned end to end - the capture file and the
|
|
39
|
+
# iteration cap are fixed, not configurable. Downstream consumers (capture
|
|
40
|
+
# write, hygiene, context, retry classification) keep reading `produces` and
|
|
41
|
+
# `max_iterations`; parse-time normalization applies these after validation.
|
|
42
|
+
BRIEF_PRODUCES = ["attempt-brief.md"]
|
|
43
|
+
BRIEF_MAX_ITERATIONS = 12
|
|
44
|
+
|
|
38
45
|
# 047: the full SDK reasoning_effort literal, passed through verbatim. The
|
|
39
46
|
# engine is provider-generic, so it validates shape only - whether a given
|
|
40
47
|
# provider accepts every value (notably "none") is operator risk (D3).
|
|
@@ -372,9 +379,9 @@ class BriefInputsSpec(BaseModel):
|
|
|
372
379
|
|
|
373
380
|
class StepConfig(BaseModel):
|
|
374
381
|
"""One pipeline step. Its type IS the key it carries: exactly one of
|
|
375
|
-
`agent`, `action`, or `
|
|
376
|
-
else fails to load. See the `kind` property (the single
|
|
377
|
-
graph-building dispatches on)."""
|
|
382
|
+
`agent`, `action`, `command`, or `brief_inputs` - empty values do not
|
|
383
|
+
count, anything else fails to load. See the `kind` property (the single
|
|
384
|
+
discriminator graph-building dispatches on)."""
|
|
378
385
|
model_config = ConfigDict(extra="forbid")
|
|
379
386
|
|
|
380
387
|
name: str
|
|
@@ -413,10 +420,12 @@ class StepConfig(BaseModel):
|
|
|
413
420
|
|
|
414
421
|
@property
|
|
415
422
|
def kind(self) -> str:
|
|
416
|
-
"""The step's type: 'agent', 'command', or 'action'. Which
|
|
417
|
-
|
|
423
|
+
"""The step's type: 'brief', 'agent', 'command', or 'action'. Which
|
|
424
|
+
of the four keys is set is the ONLY signal; validation has already
|
|
418
425
|
guaranteed exactly one is set and non-empty by the time any instance
|
|
419
426
|
exists."""
|
|
427
|
+
if self.brief_inputs is not None:
|
|
428
|
+
return "brief"
|
|
420
429
|
if self.agent is not None:
|
|
421
430
|
return "agent"
|
|
422
431
|
if self.command is not None:
|
|
@@ -470,11 +479,48 @@ class StepConfig(BaseModel):
|
|
|
470
479
|
raise ValueError(
|
|
471
480
|
f"step {self.name!r}: {key!r} must be a non-empty string"
|
|
472
481
|
)
|
|
473
|
-
if sum(k is not None for k in
|
|
482
|
+
if sum(k is not None for k in
|
|
483
|
+
(self.agent, self.action, self.command, self.brief_inputs)) != 1:
|
|
474
484
|
raise ValueError(
|
|
475
|
-
f"step {self.name!r}: exactly one of 'agent', 'action',
|
|
476
|
-
"'command' is required"
|
|
485
|
+
f"step {self.name!r}: exactly one of 'agent', 'action', "
|
|
486
|
+
"'command', or 'brief_inputs' is required"
|
|
477
487
|
)
|
|
488
|
+
if self.brief_inputs is not None:
|
|
489
|
+
if self.produces:
|
|
490
|
+
raise ValueError(
|
|
491
|
+
f"step {self.name!r}: 'brief_inputs' steps write "
|
|
492
|
+
f"{BRIEF_PRODUCES[0]!r}; drop 'produces'")
|
|
493
|
+
if self.artifact is not None:
|
|
494
|
+
raise ValueError(
|
|
495
|
+
f"step {self.name!r}: 'brief_inputs' steps have no "
|
|
496
|
+
"verdicts; drop 'artifact'")
|
|
497
|
+
if self.verdicts is not None:
|
|
498
|
+
raise ValueError(
|
|
499
|
+
f"step {self.name!r}: 'brief_inputs' steps have no "
|
|
500
|
+
"verdicts; drop 'verdicts'")
|
|
501
|
+
if self.params:
|
|
502
|
+
raise ValueError(
|
|
503
|
+
f"step {self.name!r}: 'brief_inputs' steps take no "
|
|
504
|
+
"'params'")
|
|
505
|
+
if self.max_iterations is not None:
|
|
506
|
+
raise ValueError(
|
|
507
|
+
f"step {self.name!r}: 'brief_inputs' steps use the "
|
|
508
|
+
f"engine cap ({BRIEF_MAX_ITERATIONS}); drop "
|
|
509
|
+
"'max_iterations'")
|
|
510
|
+
if self.timeout is not None:
|
|
511
|
+
raise ValueError(
|
|
512
|
+
f"step {self.name!r}: 'timeout' is command-only; drop it "
|
|
513
|
+
"from a 'brief_inputs' step")
|
|
514
|
+
if self.custom_prompt is not None:
|
|
515
|
+
raise ValueError(
|
|
516
|
+
f"step {self.name!r}: the brief prompt is engine-owned; "
|
|
517
|
+
"drop 'custom_prompt'")
|
|
518
|
+
if self.finalize_nudge_turns is not None:
|
|
519
|
+
raise ValueError(
|
|
520
|
+
f"step {self.name!r}: 'brief_inputs' steps have no "
|
|
521
|
+
"verdicts; drop 'finalize_nudge_turns'")
|
|
522
|
+
self.produces = list(BRIEF_PRODUCES)
|
|
523
|
+
self.max_iterations = BRIEF_MAX_ITERATIONS
|
|
478
524
|
if self.command is not None:
|
|
479
525
|
for field, empty in (("artifact", None), ("verdicts", None),
|
|
480
526
|
("produces", []), ("params", {}),
|
|
@@ -495,9 +541,11 @@ class StepConfig(BaseModel):
|
|
|
495
541
|
raise ValueError(
|
|
496
542
|
f"step {self.name!r}: 'timeout' requires 'command'"
|
|
497
543
|
)
|
|
498
|
-
if self.terminal_timeout is not None
|
|
544
|
+
if (self.terminal_timeout is not None
|
|
545
|
+
and self.agent is None and self.brief_inputs is None):
|
|
499
546
|
raise ValueError(
|
|
500
|
-
f"step {self.name!r}: 'terminal_timeout' requires 'agent'"
|
|
547
|
+
f"step {self.name!r}: 'terminal_timeout' requires 'agent' or "
|
|
548
|
+
"'brief_inputs'"
|
|
501
549
|
)
|
|
502
550
|
if self.finalize_nudge_turns is not None and self.agent is None:
|
|
503
551
|
raise ValueError(
|
|
@@ -514,37 +562,20 @@ class StepConfig(BaseModel):
|
|
|
514
562
|
f"step {self.name!r}: 'finalize_nudge_turns' must be less "
|
|
515
563
|
"than 'max_iterations'"
|
|
516
564
|
)
|
|
517
|
-
if self.brief_inputs is not None and self.agent is None:
|
|
518
|
-
raise ValueError(
|
|
519
|
-
f"step {self.name!r}: 'brief_inputs' requires 'agent'"
|
|
520
|
-
)
|
|
521
565
|
if self.custom_prompt is not None and self.agent is None:
|
|
522
566
|
raise ValueError(
|
|
523
567
|
f"step {self.name!r}: 'custom_prompt' requires 'agent'"
|
|
524
568
|
)
|
|
525
|
-
if (self.
|
|
526
|
-
and
|
|
527
|
-
raise ValueError(
|
|
528
|
-
f"step {self.name!r}: 'brief_inputs' is only valid on a "
|
|
529
|
-
"produces-only agent step"
|
|
530
|
-
)
|
|
531
|
-
if self.brief_inputs is not None and not self.produces:
|
|
532
|
-
raise ValueError(
|
|
533
|
-
f"step {self.name!r}: 'brief_inputs' requires 'produces'"
|
|
534
|
-
)
|
|
535
|
-
if self.setup is not None and self.command is None and self.agent is None:
|
|
569
|
+
if (self.setup is not None and self.command is None
|
|
570
|
+
and self.agent is None and self.brief_inputs is None):
|
|
536
571
|
raise ValueError(
|
|
537
|
-
f"step {self.name!r}: 'setup' is only allowed on 'command'
|
|
572
|
+
f"step {self.name!r}: 'setup' is only allowed on 'command', "
|
|
573
|
+
"'agent', or 'brief_inputs' steps"
|
|
538
574
|
)
|
|
539
575
|
if self.setup_probe is not None and self.setup is None:
|
|
540
576
|
raise ValueError(
|
|
541
577
|
f"step {self.name!r}: 'setup_probe' requires 'setup'"
|
|
542
578
|
)
|
|
543
|
-
# `network` is NOT rejected here: the schema can't tell a host-side
|
|
544
|
-
# built-in action (e.g. `commit`, which must never take egress) from
|
|
545
|
-
# a sandbox-executed consumer action (which may, like a command step).
|
|
546
|
-
# That distinction is enforced once the registry is known - see
|
|
547
|
-
# `validate_action_network` (020).
|
|
548
579
|
if self.secrets and self.action is not None:
|
|
549
580
|
raise ValueError(
|
|
550
581
|
f"step {self.name!r}: 'secrets' is only allowed on 'agent' or "
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
@@ -25,6 +25,12 @@ if TYPE_CHECKING:
|
|
|
25
25
|
|
|
26
26
|
from .isolation import Sandbox
|
|
27
27
|
|
|
28
|
+
# 049, D3: the internal brief agent name is a fixed UUID4 string, never the
|
|
29
|
+
# literal "brief", so a consumer vendoring a `brief.agent.md` cannot mint a
|
|
30
|
+
# second AgentDefinition under the same name. The vendored-file warning still
|
|
31
|
+
# keys on the literal filename `brief.agent.md` (that is the file to delete).
|
|
32
|
+
BRIEF_AGENT_NAME = "4f1c9e2b-7a3d-4e5f-8b6c-0d1a2f3c4d5e"
|
|
33
|
+
|
|
28
34
|
|
|
29
35
|
def load_agent(agents_dir: Path | str, name: str, llm: LLM) -> "Agent":
|
|
30
36
|
"""Load `<agents_dir>/<name>.agent.md` and build the Agent via the SDK
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/dryrun.py
RENAMED
|
@@ -27,10 +27,11 @@ def _validate_agents(cfg: PipelineConfig, agents_dir: Path) -> None:
|
|
|
27
27
|
"""Every `agent:` step must resolve to an existing file under agents_dir
|
|
28
28
|
(the same fail-fast `load_agent` enforces at run time).
|
|
29
29
|
|
|
30
|
-
The brief prompt is engine-embedded (046), so a ``brief_inputs:``
|
|
31
|
-
|
|
30
|
+
The brief prompt is engine-embedded (046), so a native ``brief_inputs:``
|
|
31
|
+
step (kind 'brief') has `agent is None` and is exempt from the agents_dir
|
|
32
|
+
lookup - it never reads a vendored file."""
|
|
32
33
|
for step in cfg.steps:
|
|
33
|
-
if step.agent is None
|
|
34
|
+
if step.agent is None:
|
|
34
35
|
continue
|
|
35
36
|
path = agents_dir / f"{step.agent}.agent.md"
|
|
36
37
|
if not path.is_file():
|
|
@@ -79,6 +80,12 @@ def _step_summary(step: StepConfig) -> str:
|
|
|
79
80
|
parts.append(f"produces={','.join(step.produces)}")
|
|
80
81
|
if step.max_iterations is not None:
|
|
81
82
|
parts.append(f"max_iterations={step.max_iterations}")
|
|
83
|
+
elif step.kind == "brief":
|
|
84
|
+
parts = [f"sections={len(step.brief_inputs.sections)}"]
|
|
85
|
+
if step.produces:
|
|
86
|
+
parts.append(f"produces={','.join(step.produces)}")
|
|
87
|
+
if step.max_iterations is not None:
|
|
88
|
+
parts.append(f"max_iterations={step.max_iterations}")
|
|
82
89
|
elif step.kind == "command":
|
|
83
90
|
parts = [f"command={step.command}"]
|
|
84
91
|
if step.timeout is not None:
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/graph.py
RENAMED
|
@@ -93,10 +93,9 @@ def build_graph(runtime: Runtime, checkpointer=None):
|
|
|
93
93
|
graph = StateGraph(LoopState)
|
|
94
94
|
for step in runtime.cfg.steps:
|
|
95
95
|
if step.kind == "agent":
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
node = make_agent_node(runtime, step)
|
|
96
|
+
node = make_agent_node(runtime, step)
|
|
97
|
+
elif step.kind == "brief":
|
|
98
|
+
node = make_brief_node(runtime, step)
|
|
100
99
|
elif step.kind == "command":
|
|
101
100
|
node = make_command_node(runtime, step)
|
|
102
101
|
else:
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
@@ -1632,23 +1632,12 @@ class DockerBackend(Sandbox):
|
|
|
1632
1632
|
_write_registry(self._iso_state_dir, remaining)
|
|
1633
1633
|
|
|
1634
1634
|
def _container_gone(self, name: str) -> bool:
|
|
1635
|
-
"""
|
|
1636
|
-
|
|
1637
|
-
res = self._runner.run(
|
|
1638
|
-
["docker", "ps", "-a", "--filter", f"name={name}",
|
|
1639
|
-
"--format", "{{.Names}}"], timeout=30)
|
|
1640
|
-
if res.exit_code != 0:
|
|
1641
|
-
return False
|
|
1642
|
-
return name not in res.stdout.splitlines()
|
|
1635
|
+
"""Thin delegation to the module-level confirm helper (050 D2)."""
|
|
1636
|
+
return _container_gone(self._runner, name)
|
|
1643
1637
|
|
|
1644
1638
|
def _network_gone(self, name: str) -> bool:
|
|
1645
|
-
"""
|
|
1646
|
-
|
|
1647
|
-
res = self._runner.run(
|
|
1648
|
-
["docker", "network", "inspect", name], timeout=30)
|
|
1649
|
-
if res.exit_code == 0:
|
|
1650
|
-
return False
|
|
1651
|
-
return "No such network" in f"{res.stderr} {res.stdout}"
|
|
1639
|
+
"""Thin delegation to the module-level confirm helper (050 D2)."""
|
|
1640
|
+
return _network_gone(self._runner, name)
|
|
1652
1641
|
|
|
1653
1642
|
# -- internal -----------------------------------------------------------
|
|
1654
1643
|
|
|
@@ -1882,6 +1871,28 @@ def _pid_alive_matches(pid: int, recorded_start: str | None) -> bool:
|
|
|
1882
1871
|
return current == recorded_start
|
|
1883
1872
|
|
|
1884
1873
|
|
|
1874
|
+
def _container_gone(runner: CommandRunner, name: str) -> bool:
|
|
1875
|
+
"""True only when the daemon confirms the container no longer exists.
|
|
1876
|
+
A nonzero daemon exit keeps the entry."""
|
|
1877
|
+
res = runner.run(
|
|
1878
|
+
["docker", "ps", "-a", "--filter", f"name={name}",
|
|
1879
|
+
"--format", "{{.Names}}"], timeout=30)
|
|
1880
|
+
if res.exit_code != 0:
|
|
1881
|
+
return False
|
|
1882
|
+
return name not in res.stdout.splitlines()
|
|
1883
|
+
|
|
1884
|
+
|
|
1885
|
+
def _network_gone(runner: CommandRunner, name: str) -> bool:
|
|
1886
|
+
"""True only when the daemon reports the network is missing. Any
|
|
1887
|
+
other inspect failure (daemon unreachable) keeps the entry."""
|
|
1888
|
+
res = runner.run(
|
|
1889
|
+
["docker", "network", "inspect", name], timeout=30)
|
|
1890
|
+
if res.exit_code == 0:
|
|
1891
|
+
return False
|
|
1892
|
+
return "No such network" in f"{res.stderr} {res.stdout}"
|
|
1893
|
+
|
|
1894
|
+
|
|
1895
|
+
|
|
1885
1896
|
def reap_orphaned_containers(state_dir: str | Path,
|
|
1886
1897
|
runner: CommandRunner | None = None,
|
|
1887
1898
|
*, announce: bool = True) -> list[str]:
|
|
@@ -1889,7 +1900,9 @@ def reap_orphaned_containers(state_dir: str | Path,
|
|
|
1889
1900
|
and the per-run internal network whose owning CLI process is gone (crashed
|
|
1890
1901
|
before close()). Exact by construction - only engine registry names are
|
|
1891
1902
|
touched, and a live owner (pid alive with the same start time) is left
|
|
1892
|
-
alone.
|
|
1903
|
+
alone. A surviving object (rm failure) keeps its entry so the next
|
|
1904
|
+
startup reaper retries; only a daemon-confirmed absence prunes it (050
|
|
1905
|
+
D1). Best-effort: never raises."""
|
|
1893
1906
|
runner = runner or _default_runner
|
|
1894
1907
|
entries = _read_registry(Path(state_dir))
|
|
1895
1908
|
if not entries:
|
|
@@ -1909,6 +1922,9 @@ def reap_orphaned_containers(state_dir: str | Path,
|
|
|
1909
1922
|
remaining.append(entry)
|
|
1910
1923
|
continue
|
|
1911
1924
|
runner.run(["docker", "network", "rm", name], timeout=60)
|
|
1925
|
+
if not _network_gone(runner, name):
|
|
1926
|
+
remaining.append(entry)
|
|
1927
|
+
continue
|
|
1912
1928
|
if announce:
|
|
1913
1929
|
_announce(f"reaped orphaned sandbox network {name}")
|
|
1914
1930
|
reaped.append(name)
|
|
@@ -1924,6 +1940,9 @@ def reap_orphaned_containers(state_dir: str | Path,
|
|
|
1924
1940
|
remaining.append(entry)
|
|
1925
1941
|
continue
|
|
1926
1942
|
runner.run(["docker", "rm", "-f", name], timeout=60)
|
|
1943
|
+
if not _container_gone(runner, name):
|
|
1944
|
+
remaining.append(entry)
|
|
1945
|
+
continue
|
|
1927
1946
|
if announce:
|
|
1928
1947
|
_announce(f"reaped orphaned sandbox container {name}")
|
|
1929
1948
|
reaped.append(name)
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
@@ -32,7 +32,8 @@ from langgraph.graph import END
|
|
|
32
32
|
from openhands.sdk import Conversation
|
|
33
33
|
|
|
34
34
|
from .agent_error_guide import guide_section
|
|
35
|
-
from .agents import load_agent, load_brief_agent,
|
|
35
|
+
from .agents import (BRIEF_AGENT_NAME, load_agent, load_brief_agent,
|
|
36
|
+
register_sandbox_terminal)
|
|
36
37
|
from .brief_coverage import extract_anchors, missing_anchors
|
|
37
38
|
from .brief_prompt import BRIEF_PROMPT_VERSION
|
|
38
39
|
from agentic_dev_loop.core.config import TERMINALS, PipelineConfig, Route, StepConfig, route_target
|
|
@@ -182,7 +183,7 @@ class Runtime:
|
|
|
182
183
|
if step is not None and step.reasoning_effort is not None:
|
|
183
184
|
llm = build_llm(self.cfg.llm,
|
|
184
185
|
reasoning_effort=step.reasoning_effort)
|
|
185
|
-
if step is not None and step.
|
|
186
|
+
if step is not None and step.kind == "brief":
|
|
186
187
|
agent = load_brief_agent(
|
|
187
188
|
agent_name, step.brief_inputs.sections, llm)
|
|
188
189
|
else:
|
|
@@ -398,7 +399,7 @@ def build_context(runtime: Runtime, docs: dict[str, str], subject: str,
|
|
|
398
399
|
parts.append("## Dependency policy\n\n" + policy_injection(runtime.cfg))
|
|
399
400
|
lines = []
|
|
400
401
|
for step in runtime.cfg.steps:
|
|
401
|
-
if step.agent
|
|
402
|
+
if step.kind not in ("agent", "brief"):
|
|
402
403
|
continue
|
|
403
404
|
for produced in step.produces:
|
|
404
405
|
lines.append(f"{step.name} agent writes: `{prompt_dir(runtime)}/{produced}`")
|
|
@@ -428,7 +429,7 @@ def run_scoped_artifacts(cfg: PipelineConfig) -> list[str]:
|
|
|
428
429
|
outputs are exempt from per-attempt hygiene."""
|
|
429
430
|
files = []
|
|
430
431
|
for step in cfg.steps:
|
|
431
|
-
if step.
|
|
432
|
+
if step.kind != "brief":
|
|
432
433
|
continue
|
|
433
434
|
files.extend(step.produces)
|
|
434
435
|
if step.artifact is not None:
|
|
@@ -778,7 +779,7 @@ def _warn_vendored_brief(runtime: Runtime, step: StepConfig) -> None:
|
|
|
778
779
|
The embedded prompt is authoritative, so a leftover
|
|
779
780
|
``<agents_dir>/<agent>.agent.md`` for a ``brief_inputs:`` step is ignored;
|
|
780
781
|
the warning names it without failing the run."""
|
|
781
|
-
vendored = runtime.agents_dir /
|
|
782
|
+
vendored = runtime.agents_dir / "brief.agent.md"
|
|
782
783
|
if vendored.is_file():
|
|
783
784
|
print(
|
|
784
785
|
f"[{runtime.cfg.name}] ignoring vendored brief prompt {vendored} - "
|
|
@@ -845,8 +846,8 @@ def make_brief_node(runtime: Runtime, step: StepConfig):
|
|
|
845
846
|
state.get("doc_sources"),
|
|
846
847
|
coverage_note=coverage_note)
|
|
847
848
|
|
|
848
|
-
print(f"[{subject}] Running {step.
|
|
849
|
-
result = runtime.run_agent(
|
|
849
|
+
print(f"[{subject}] Running {step.name} agent...", flush=True)
|
|
850
|
+
result = runtime.run_agent(BRIEF_AGENT_NAME, message,
|
|
850
851
|
max_iterations=max_iterations, step=step)
|
|
851
852
|
if not result.ok:
|
|
852
853
|
print(f"[{subject}] {step.name} agent failed on attempt {attempt}.",
|
|
@@ -1125,7 +1126,7 @@ def make_bump_node(runtime: Runtime):
|
|
|
1125
1126
|
# re-enter that brief step so it regenerates once and refreshes its
|
|
1126
1127
|
# hash. Unchanged inputs keep the failing step's retry_target.
|
|
1127
1128
|
for step in runtime.cfg.steps:
|
|
1128
|
-
if step.
|
|
1129
|
+
if step.kind != "brief":
|
|
1129
1130
|
continue
|
|
1130
1131
|
try:
|
|
1131
1132
|
current = brief_inputs_hash(runtime, step,
|
{agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a50/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.0a50
|
|
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
|
|
@@ -169,7 +169,10 @@ engine-owned object to its owning run and a `live`, `orphaned`,
|
|
|
169
169
|
registry-owned orphans whose owner pid is dead (never unregistered names).
|
|
170
170
|
A SIGTERM to a running pipeline still closes the sandbox (the graph's
|
|
171
171
|
`finally` runs); SIGKILL is unhandleable, so its containers are reclaimed by
|
|
172
|
-
the startup reaper on the next run or by `containers --reap`.
|
|
172
|
+
the startup reaper on the next run or by `containers --reap`. The reaper
|
|
173
|
+
and `--reap` prune a registry entry only after the daemon confirms the
|
|
174
|
+
object is gone; a surviving object (a failed removal) keeps its entry so
|
|
175
|
+
the next startup retries it. Pass
|
|
173
176
|
`--state-dir` to point at a custom `isolation.state_dir`.
|
|
174
177
|
|
|
175
178
|
**What a run looks like** (the shipped self-loop example):
|
|
@@ -206,10 +209,11 @@ agents_dir: sdk_agents # your <name>.agent.md f
|
|
|
206
209
|
|
|
207
210
|
steps:
|
|
208
211
|
- name: brief
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
reasoning_effort: medium
|
|
213
|
+
brief_inputs:
|
|
214
|
+
include: [AGENTS.md, coding-standards-ddd.yaml]
|
|
215
|
+
sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
|
|
216
|
+
env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
|
|
213
217
|
on_pass: dev
|
|
214
218
|
|
|
215
219
|
- name: dev
|
|
@@ -249,9 +253,9 @@ steps:
|
|
|
249
253
|
|
|
250
254
|
| Key | Notes |
|
|
251
255
|
| --- | --- |
|
|
252
|
-
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). |
|
|
256
|
+
| `agent:` / `command:` / `action:` / `brief_inputs:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call). `commit` is the sole host-side built-in action; consumer actions are sandbox-executed, command-shaped scripts (see [Host boundary](#host-boundary-security-model)). `brief_inputs:` is a native brief step - see its row below. |
|
|
253
257
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
254
|
-
| `brief_inputs:` |
|
|
258
|
+
| `brief_inputs:` | The discriminator for a native brief step: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. There is no `agent:`, `produces:`, or `max_iterations:` - the prompt is engine-embedded, the capture file is `attempt-brief.md`, and the iteration cap is 12, all engine-owned. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as the step's task message, and regenerates the step only when those inputs change. `sections` is the ordered list of digest headings the brief must emit (default `[Repo rules digest, Unit scope digest, Environment facts, Open risks]`; non-empty and unique). `name` stays free-form, so multiple brief steps are allowed. |
|
|
255
259
|
| `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. |
|
|
256
260
|
| `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). |
|
|
257
261
|
| `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. |
|
|
@@ -294,9 +298,9 @@ Recommended lane defaults (guidance only, not enforced):
|
|
|
294
298
|
| review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
|
|
295
299
|
|
|
296
300
|
A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
|
|
297
|
-
run start (or when its inputs change), its `
|
|
298
|
-
starts, and the dev/review agents read that digest
|
|
299
|
-
rules and environment facts every attempt.
|
|
301
|
+
run start (or when its inputs change), its engine-owned `attempt-brief.md`
|
|
302
|
+
digest survives attempt starts, and the dev/review agents read that digest
|
|
303
|
+
instead of re-deriving repo rules and environment facts every attempt.
|
|
300
304
|
|
|
301
305
|
The brief digest is mechanically coverage-checked: the configured
|
|
302
306
|
`brief_inputs.sections` headings must each appear on their own line, every
|