agentic-dev-loop 1.0.0a48__tar.gz → 1.0.0a49__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.

Files changed (104) hide show
  1. {agentic_dev_loop-1.0.0a48/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a49}/PKG-INFO +11 -10
  2. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/README.md +10 -9
  3. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/pyproject.toml +1 -1
  4. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/config.py +62 -31
  5. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/agents.py +6 -0
  6. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/dryrun.py +10 -3
  7. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/graph.py +3 -4
  8. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/stages.py +9 -8
  9. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49/src/agentic_dev_loop.egg-info}/PKG-INFO +11 -10
  10. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_033_brief_step.py +29 -62
  11. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_041_brief_coverage.py +10 -10
  12. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_agent_error_guide.py +1 -1
  13. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_config.py +83 -7
  14. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_e2e_fixtures.py +7 -8
  15. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_publish.py +35 -0
  16. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/LICENSE +0 -0
  17. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/setup.cfg +0 -0
  18. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/__init__.py +0 -0
  19. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/__init__.py +0 -0
  20. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/paths.py +0 -0
  21. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/results.py +0 -0
  22. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/core/verdict.py +0 -0
  23. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/__init__.py +0 -0
  24. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/actions.py +0 -0
  25. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
  26. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
  27. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/allowlist.py +0 -0
  28. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
  29. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
  30. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
  31. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/cli.py +0 -0
  32. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/containers.py +0 -0
  33. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/deps.py +0 -0
  34. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
  35. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
  36. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
  37. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
  38. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/forbidden.py +0 -0
  39. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
  40. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/isolation.py +0 -0
  41. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/llm.py +0 -0
  42. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/loaders.py +0 -0
  43. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/mounts.py +0 -0
  44. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/outputs.py +0 -0
  45. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/presets.py +0 -0
  46. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/proc.py +0 -0
  47. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/registry.py +0 -0
  48. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
  49. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
  50. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/secrets.py +0 -0
  51. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
  52. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/engine/sync_back.py +0 -0
  53. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/py.typed +0 -0
  54. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/viz/__init__.py +0 -0
  55. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/viz/__main__.py +0 -0
  56. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop/viz/server.py +0 -0
  57. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
  58. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
  59. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
  60. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
  61. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
  62. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_028_declared_caches.py +0 -0
  63. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_029_merge_on_conflict.py +0 -0
  64. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_035_finalize_nudge.py +0 -0
  65. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
  66. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_046_brief_prompt.py +0 -0
  67. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_actions.py +0 -0
  68. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_agents.py +0 -0
  69. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_aidlc.py +0 -0
  70. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_cli.py +0 -0
  71. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_containers.py +0 -0
  72. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_deps.py +0 -0
  73. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_dry_run.py +0 -0
  74. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_egress_proxy.py +0 -0
  75. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_graph.py +0 -0
  76. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_hostboundary.py +0 -0
  77. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_hostboundary_integration.py +0 -0
  78. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_isolation.py +0 -0
  79. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_isolation_integration.py +0 -0
  80. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_isolation_pbt.py +0 -0
  81. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_llm.py +0 -0
  82. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_loaders.py +0 -0
  83. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_module_contract.py +0 -0
  84. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_outputs.py +0 -0
  85. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_outputs_pbt.py +0 -0
  86. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_proc.py +0 -0
  87. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_qa_prompt.py +0 -0
  88. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_registry.py +0 -0
  89. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_repo_ascii.py +0 -0
  90. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_results.py +0 -0
  91. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_sdk_compat.py +0 -0
  92. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_secrets.py +0 -0
  93. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_secrets_integration.py +0 -0
  94. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_secrets_pipeline.py +0 -0
  95. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_source_resolution.py +0 -0
  96. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_stages.py +0 -0
  97. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_step_workspace.py +0 -0
  98. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_sync_back.py +0 -0
  99. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_trigger_script.py +0 -0
  100. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_unit5_config.py +0 -0
  101. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_unit5_pbt.py +0 -0
  102. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_unit5_pure.py +0 -0
  103. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_verdict.py +0 -0
  104. {agentic_dev_loop-1.0.0a48 → agentic_dev_loop-1.0.0a49}/tests/test_viz.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-dev-loop
3
- Version: 1.0.0a48
3
+ Version: 1.0.0a49
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
@@ -206,10 +206,11 @@ agents_dir: sdk_agents # your <name>.agent.md f
206
206
 
207
207
  steps:
208
208
  - name: brief
209
- agent: brief
210
- produces: attempt-brief.md
211
- max_iterations: 12
212
- brief_inputs: { include: [AGENTS.md, coding-standards-ddd.yaml], env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"] }
209
+ reasoning_effort: medium
210
+ brief_inputs:
211
+ include: [AGENTS.md, coding-standards-ddd.yaml]
212
+ sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
213
+ env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
213
214
  on_pass: dev
214
215
 
215
216
  - name: dev
@@ -249,9 +250,9 @@ steps:
249
250
 
250
251
  | Key | Notes |
251
252
  | --- | --- |
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)). |
253
+ | `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
254
  | `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:` | Optional agent-step input spec: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as that step's task message, and regenerates the step only when those inputs change. `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). A produces-only agent step such as `brief` is the documented pattern for per-run context digestion. |
255
+ | `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
256
  | `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
257
  | `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
258
  | `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 +295,9 @@ Recommended lane defaults (guidance only, not enforced):
294
295
  | review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
295
296
 
296
297
  A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
297
- run start (or when its inputs change), its `produces:` artifact survives attempt
298
- starts, and the dev/review agents read that digest instead of re-deriving repo
299
- rules and environment facts every attempt.
298
+ run start (or when its inputs change), its engine-owned `attempt-brief.md`
299
+ digest survives attempt starts, and the dev/review agents read that digest
300
+ instead of re-deriving repo rules and environment facts every attempt.
300
301
 
301
302
  The brief digest is mechanically coverage-checked: the configured
302
303
  `brief_inputs.sections` headings must each appear on their own line, every
@@ -162,10 +162,11 @@ agents_dir: sdk_agents # your <name>.agent.md f
162
162
 
163
163
  steps:
164
164
  - name: brief
165
- agent: brief
166
- produces: attempt-brief.md
167
- max_iterations: 12
168
- brief_inputs: { include: [AGENTS.md, coding-standards-ddd.yaml], env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"] }
165
+ reasoning_effort: medium
166
+ brief_inputs:
167
+ include: [AGENTS.md, coding-standards-ddd.yaml]
168
+ sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
169
+ env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
169
170
  on_pass: dev
170
171
 
171
172
  - name: dev
@@ -205,9 +206,9 @@ steps:
205
206
 
206
207
  | Key | Notes |
207
208
  | --- | --- |
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)). |
209
+ | `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
210
  | `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:` | Optional agent-step input spec: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as that step's task message, and regenerates the step only when those inputs change. `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). A produces-only agent step such as `brief` is the documented pattern for per-run context digestion. |
211
+ | `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
212
  | `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
213
  | `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
214
  | `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 +251,9 @@ Recommended lane defaults (guidance only, not enforced):
250
251
  | review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
251
252
 
252
253
  A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
253
- run start (or when its inputs change), its `produces:` artifact survives attempt
254
- starts, and the dev/review agents read that digest instead of re-deriving repo
255
- rules and environment facts every attempt.
254
+ run start (or when its inputs change), its engine-owned `attempt-brief.md`
255
+ digest survives attempt starts, and the dev/review agents read that digest
256
+ instead of re-deriving repo rules and environment facts every attempt.
256
257
 
257
258
  The brief digest is mechanically coverage-checked: the configured
258
259
  `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.0a48"
7
+ version = "1.0.0a49"
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 `command` - empty values do not count, anything
376
- else fails to load. See the `kind` property (the single discriminator
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 of the
417
- three keys is set is the ONLY signal; validation has already
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 (self.agent, self.action, self.command)) != 1:
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', or "
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 and self.agent is 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.brief_inputs is not None
526
- and (self.artifact is not None or self.verdicts is not None)):
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' or 'agent' steps"
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 "
@@ -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
@@ -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:`` step is
31
- exempt from the agents_dir lookup - it never reads a vendored file."""
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 or step.brief_inputs is not 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:
@@ -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
- if step.brief_inputs is not None:
97
- node = make_brief_node(runtime, step)
98
- else:
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:
@@ -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, register_sandbox_terminal
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.brief_inputs is not None:
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 is None:
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.brief_inputs is None:
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 / f"{step.agent}.agent.md"
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.agent} agent...", flush=True)
849
- result = runtime.run_agent(step.agent, message,
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.brief_inputs is None:
1129
+ if step.kind != "brief":
1129
1130
  continue
1130
1131
  try:
1131
1132
  current = brief_inputs_hash(runtime, step,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-dev-loop
3
- Version: 1.0.0a48
3
+ Version: 1.0.0a49
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
@@ -206,10 +206,11 @@ agents_dir: sdk_agents # your <name>.agent.md f
206
206
 
207
207
  steps:
208
208
  - name: brief
209
- agent: brief
210
- produces: attempt-brief.md
211
- max_iterations: 12
212
- brief_inputs: { include: [AGENTS.md, coding-standards-ddd.yaml], env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"] }
209
+ reasoning_effort: medium
210
+ brief_inputs:
211
+ include: [AGENTS.md, coding-standards-ddd.yaml]
212
+ sections: [Repo rules digest, Unit scope digest, Environment facts, Open risks]
213
+ env_facts: ["the workspace is an export with no .git; verify with find/ls, never git status"]
213
214
  on_pass: dev
214
215
 
215
216
  - name: dev
@@ -249,9 +250,9 @@ steps:
249
250
 
250
251
  | Key | Notes |
251
252
  | --- | --- |
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)). |
253
+ | `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
254
  | `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:` | Optional agent-step input spec: `{ include: [<paths/globs>], env_facts: [<strings>], sections: [<headings>] }`. The engine resolves the include list against the workspace, passes the resolved docs + include contents + env facts as that step's task message, and regenerates the step only when those inputs change. `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). A produces-only agent step such as `brief` is the documented pattern for per-run context digestion. |
255
+ | `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
256
  | `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
257
  | `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
258
  | `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 +295,9 @@ Recommended lane defaults (guidance only, not enforced):
294
295
  | review | xhigh | shortest lane, highest stakes: a missed detection sails past the last gate into commit |
295
296
 
296
297
  A `brief_inputs:` step is the per-run context-digestion pattern: it runs once at
297
- run start (or when its inputs change), its `produces:` artifact survives attempt
298
- starts, and the dev/review agents read that digest instead of re-deriving repo
299
- rules and environment facts every attempt.
298
+ run start (or when its inputs change), its engine-owned `attempt-brief.md`
299
+ digest survives attempt starts, and the dev/review agents read that digest
300
+ instead of re-deriving repo rules and environment facts every attempt.
300
301
 
301
302
  The brief digest is mechanically coverage-checked: the configured
302
303
  `brief_inputs.sections` headings must each appear on their own line, every