agentic-dev-loop 1.0.0a7__tar.gz → 1.0.0a9__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.0a7 → agentic_dev_loop-1.0.0a9}/PKG-INFO +48 -59
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/README.md +42 -50
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/pyproject.toml +9 -22
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/core/__init__.py +1 -8
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/core/config.py +59 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/core/verdict.py +1 -1
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/__init__.py +1 -1
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/agents.py +7 -3
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/dryrun.py +2 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/graph.py +10 -3
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/isolation.py +10 -2
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/outputs.py +7 -4
- agentic_dev_loop-1.0.0a9/src/agentic_dev_loop/engine/secrets.py +118 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/stages.py +19 -7
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/viz/__init__.py +2 -3
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/viz/__main__.py +2 -5
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/viz/server.py +3 -36
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop.egg-info/PKG-INFO +48 -59
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop.egg-info/SOURCES.txt +5 -11
- agentic_dev_loop-1.0.0a9/src/agentic_dev_loop.egg-info/entry_points.txt +2 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop.egg-info/requires.txt +3 -8
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_e2e_fixtures.py +5 -1
- agentic_dev_loop-1.0.0a9/tests/test_module_contract.py +94 -0
- agentic_dev_loop-1.0.0a9/tests/test_secrets.py +221 -0
- agentic_dev_loop-1.0.0a9/tests/test_secrets_integration.py +67 -0
- agentic_dev_loop-1.0.0a9/tests/test_secrets_pipeline.py +106 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_viz.py +2 -1
- agentic_dev_loop-1.0.0a7/src/agentic_dev_loop/core/project.py +0 -64
- agentic_dev_loop-1.0.0a7/src/agentic_dev_loop/core/projection.py +0 -206
- agentic_dev_loop-1.0.0a7/src/agentic_dev_loop/core/workflow.js.template +0 -236
- agentic_dev_loop-1.0.0a7/src/agentic_dev_loop/engine/commit_shim.py +0 -56
- agentic_dev_loop-1.0.0a7/src/agentic_dev_loop/viz/ingest.py +0 -25
- agentic_dev_loop-1.0.0a7/src/agentic_dev_loop/workflow/__init__.py +0 -80
- agentic_dev_loop-1.0.0a7/src/agentic_dev_loop/workflow/cli.py +0 -40
- agentic_dev_loop-1.0.0a7/src/agentic_dev_loop.egg-info/entry_points.txt +0 -4
- agentic_dev_loop-1.0.0a7/tests/test_unit4_golden.py +0 -60
- agentic_dev_loop-1.0.0a7/tests/test_unit4_pbt.py +0 -283
- agentic_dev_loop-1.0.0a7/tests/test_unit4_viz_ingest.py +0 -92
- agentic_dev_loop-1.0.0a7/tests/test_unit4_workflow.py +0 -136
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/tests/test_verdict.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0a9
|
|
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
|
|
@@ -27,15 +27,12 @@ Description-Content-Type: text/markdown
|
|
|
27
27
|
License-File: LICENSE
|
|
28
28
|
Requires-Dist: pydantic>=2.0
|
|
29
29
|
Requires-Dist: PyYAML>=6.0
|
|
30
|
-
|
|
31
|
-
Requires-Dist: openhands-
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist: langgraph>=
|
|
35
|
-
Requires-Dist: langgraph-checkpoint-sqlite>=3.0; extra == "engine"
|
|
36
|
-
Provides-Extra: workflow
|
|
30
|
+
Requires-Dist: openhands-sdk~=1.17.0
|
|
31
|
+
Requires-Dist: openhands-tools~=1.17.0
|
|
32
|
+
Requires-Dist: agent-client-protocol<1.0.0
|
|
33
|
+
Requires-Dist: langgraph>=1.2
|
|
34
|
+
Requires-Dist: langgraph-checkpoint-sqlite>=3.0
|
|
37
35
|
Provides-Extra: dev
|
|
38
|
-
Requires-Dist: agentic-dev-loop[engine]; extra == "dev"
|
|
39
36
|
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
40
37
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
41
38
|
Dynamic: license-file
|
|
@@ -65,11 +62,6 @@ flowchart LR
|
|
|
65
62
|
- **Sandboxed by default.** The agent's shell and every command step run inside
|
|
66
63
|
an always-on Docker sandbox. There is no opt-out.
|
|
67
64
|
|
|
68
|
-
Two modes share one `pipeline.yaml`: **engine mode** (the sandboxed, resumable
|
|
69
|
-
loop above) and **workflow mode** — a Claude Dynamic Workflow exported from the
|
|
70
|
-
same pipeline that runs in Claude Code's own environment, with the live graph
|
|
71
|
-
visualiser. See [Workflow mode](#workflow-mode-claude-code).
|
|
72
|
-
|
|
73
65
|
---
|
|
74
66
|
|
|
75
67
|
## Core features
|
|
@@ -98,13 +90,8 @@ LLM timeout (agent runs are long).
|
|
|
98
90
|
| LLM API key | The env var named by your pipeline's `api_key_env` (e.g. `OPENAI_API_KEY`) must be set — a missing key fails fast, naming it. |
|
|
99
91
|
|
|
100
92
|
```bash
|
|
101
|
-
|
|
102
|
-
pip install "agentic-dev-loop[engine]==1.0.0a6"
|
|
93
|
+
pip install "agentic-dev-loop==1.0.0a8"
|
|
103
94
|
agentic-dev-loop --version
|
|
104
|
-
|
|
105
|
-
# workflow mode (Claude Code — core + viz only, no SDK/Docker deps)
|
|
106
|
-
pip install "agentic-dev-loop==1.0.0a6"
|
|
107
|
-
agentic-dev-loop-workflow install-skill
|
|
108
95
|
```
|
|
109
96
|
|
|
110
97
|
On the first run the engine pulls the **digest-pinned** dev image
|
|
@@ -115,43 +102,6 @@ image. Engine setup, first-run details, the full `pipeline.yaml` contract, and
|
|
|
115
102
|
troubleshooting for every fail-fast code live in
|
|
116
103
|
[docs/setup.md](docs/setup.md).
|
|
117
104
|
|
|
118
|
-
## Workflow mode (Claude Code)
|
|
119
|
-
|
|
120
|
-
For developers who know and love the agentic dev loop but are forced to use
|
|
121
|
-
Claude Code at work: workflow mode exports a **Claude Dynamic Workflow** from
|
|
122
|
-
the `pipeline.yaml` they already trust, plus the visualiser they trust. It adds
|
|
123
|
-
the deterministic loop through Claude's mechanism — nothing more. **No Docker
|
|
124
|
-
sandboxing, and no new security claims**: workflow steps run in Claude Code's
|
|
125
|
-
own environment under its permission model (a documented trust-model change
|
|
126
|
-
from engine mode).
|
|
127
|
-
|
|
128
|
-
Install the skill once:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
pip install "agentic-dev-loop==1.0.0a6" # base install: core + viz, no SDK/Docker
|
|
132
|
-
agentic-dev-loop-workflow install-skill # → ~/.claude/skills/agentic-dev-loop/
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
Then invoke `/agentic-dev-loop` in Claude Code. The skill walks through:
|
|
136
|
-
|
|
137
|
-
1. **Build** — `python -m agentic_dev_loop.core.project <pipeline.yaml>` loads +
|
|
138
|
-
validates the pipeline, verifies every agent step's `.agent.md` prompt exists
|
|
139
|
-
(a missing prompt fails the build loudly, naming the file), and writes a
|
|
140
|
-
self-contained `.agentic-dev-loop/workflow.js`. Steps are frozen at creation
|
|
141
|
-
— mid-run `pipeline.yaml` edits never affect a running workflow.
|
|
142
|
-
2. **Run** — the generated JS routes `on_pass` / `on_fail` / `goto` with its own
|
|
143
|
-
attempt counter, injects the failing subagent's output as the next retry note,
|
|
144
|
-
launches the live-graph server (localhost, token-authenticated ingest), and
|
|
145
|
-
invokes the docker-free commit shim, which refuses loudly unless the worktree
|
|
146
|
-
was clean at run start.
|
|
147
|
-
|
|
148
|
-
Engine-only fields (`isolation:`, per-step `network:`, `setup:`) are ignored in
|
|
149
|
-
workflow mode with no effect on the build or run.
|
|
150
|
-
|
|
151
|
-
The base install is deliberately dependency-light (`pydantic` + `PyYAML` only);
|
|
152
|
-
the heavy OpenHands-SDK engine lives in the `[engine]` extra. The `core` and
|
|
153
|
-
`viz` packages are guarded against pulling engine deps.
|
|
154
|
-
|
|
155
105
|
## Usage
|
|
156
106
|
|
|
157
107
|
```bash
|
|
@@ -242,9 +192,49 @@ steps:
|
|
|
242
192
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
243
193
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
244
194
|
| `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` provisions the sandbox (command steps, network ON, stamp-cached); `network: true` grants egress to a command or agent step (default off) — see [Per-stage networking](#per-stage-networking). |
|
|
195
|
+
| `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). |
|
|
245
196
|
| `isolation:` (optional) | Configures the sandbox — image override, resource caps, `allow_git_mutations`. It can never disable it. |
|
|
246
197
|
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` — artifacts and checkpoints live outside the sandbox mount. |
|
|
247
198
|
|
|
199
|
+
### Secrets (`secrets:`) — refs only, never values
|
|
200
|
+
|
|
201
|
+
Secret values never belong in the pipeline yaml, the workspace, or `state_dir`.
|
|
202
|
+
Declare the refs at the top level and opt each step into the refs it needs:
|
|
203
|
+
|
|
204
|
+
```yaml
|
|
205
|
+
secrets: # refs only — never values
|
|
206
|
+
kinde-client-secret: # ref name (arbitrary)
|
|
207
|
+
from: env # provider name
|
|
208
|
+
key: KINDIE_CLIENT_SECRET # provider-specific locator
|
|
209
|
+
r2-keys:
|
|
210
|
+
from: env
|
|
211
|
+
key: YOSK_R2_KEYS
|
|
212
|
+
|
|
213
|
+
steps:
|
|
214
|
+
- name: qa
|
|
215
|
+
agent: qa
|
|
216
|
+
secrets: [kinde-client-secret, r2-keys]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- The engine resolves every declared ref **host-side at run start** (the `env`
|
|
220
|
+
provider reads the named host environment variable). A missing variable, an
|
|
221
|
+
unknown provider, or a ref a step lists without declaring it fails fast —
|
|
222
|
+
naming the ref, the step, and the provider error. No silent fallback.
|
|
223
|
+
- A resolved secret is injected into a step's exec env **only when that step
|
|
224
|
+
lists the ref** — the same path step `env:` uses, no new channel. The secret
|
|
225
|
+
never reaches the provision container, `state_dir`, or any other step.
|
|
226
|
+
- Prefer a mechanical `command` step for secret-consuming work (a script runs
|
|
227
|
+
the app with creds) so the LLM and the secret never meet; an `agent` step
|
|
228
|
+
holds a secret only when its job genuinely requires it.
|
|
229
|
+
- **`.env*` ban.** At run start the workspace root is scanned for `.env*`
|
|
230
|
+
files. Any hit fails fast, naming the files, with the remedy text. The
|
|
231
|
+
convention fixtures `.env.example`, `.env.template`, and `.env.sample` are
|
|
232
|
+
whitelisted. This applies regardless of `network:` flags.
|
|
233
|
+
- **Output scrubbing.** Resolved secret values are scrubbed from every
|
|
234
|
+
engine-written output — captured step output, retry notes, artifacts, and
|
|
235
|
+
`state_dir` files. (Hygiene: the declaring step's agent has already seen
|
|
236
|
+
the value in its own env.)
|
|
237
|
+
|
|
248
238
|
### Per-stage networking
|
|
249
239
|
|
|
250
240
|
The sandbox is network-off by default. A step that legitimately needs egress —
|
|
@@ -276,8 +266,7 @@ steps:
|
|
|
276
266
|
git clone git@github.com:lukedawilson/agentic-dev-loop.git
|
|
277
267
|
cd agentic-dev-loop
|
|
278
268
|
./setup-venv.sh # idempotent .venv setup (never touches bare pip)
|
|
279
|
-
#
|
|
280
|
-
# echo 'OPENAI_API_KEY=...' > .env
|
|
269
|
+
export OPENAI_API_KEY=... # set your LLM key in the host env (no .env in the repo)
|
|
281
270
|
python -m pytest tests/ -q # fast suite
|
|
282
271
|
python -m pytest tests/ -q -m "not docker" # skip engine-dependent tests
|
|
283
272
|
./trigger-agentic-dev-loop.sh --dry-run # run the shipped pipeline against this repo
|
|
@@ -23,11 +23,6 @@ flowchart LR
|
|
|
23
23
|
- **Sandboxed by default.** The agent's shell and every command step run inside
|
|
24
24
|
an always-on Docker sandbox. There is no opt-out.
|
|
25
25
|
|
|
26
|
-
Two modes share one `pipeline.yaml`: **engine mode** (the sandboxed, resumable
|
|
27
|
-
loop above) and **workflow mode** — a Claude Dynamic Workflow exported from the
|
|
28
|
-
same pipeline that runs in Claude Code's own environment, with the live graph
|
|
29
|
-
visualiser. See [Workflow mode](#workflow-mode-claude-code).
|
|
30
|
-
|
|
31
26
|
---
|
|
32
27
|
|
|
33
28
|
## Core features
|
|
@@ -56,13 +51,8 @@ LLM timeout (agent runs are long).
|
|
|
56
51
|
| LLM API key | The env var named by your pipeline's `api_key_env` (e.g. `OPENAI_API_KEY`) must be set — a missing key fails fast, naming it. |
|
|
57
52
|
|
|
58
53
|
```bash
|
|
59
|
-
|
|
60
|
-
pip install "agentic-dev-loop[engine]==1.0.0a6"
|
|
54
|
+
pip install "agentic-dev-loop==1.0.0a8"
|
|
61
55
|
agentic-dev-loop --version
|
|
62
|
-
|
|
63
|
-
# workflow mode (Claude Code — core + viz only, no SDK/Docker deps)
|
|
64
|
-
pip install "agentic-dev-loop==1.0.0a6"
|
|
65
|
-
agentic-dev-loop-workflow install-skill
|
|
66
56
|
```
|
|
67
57
|
|
|
68
58
|
On the first run the engine pulls the **digest-pinned** dev image
|
|
@@ -73,43 +63,6 @@ image. Engine setup, first-run details, the full `pipeline.yaml` contract, and
|
|
|
73
63
|
troubleshooting for every fail-fast code live in
|
|
74
64
|
[docs/setup.md](docs/setup.md).
|
|
75
65
|
|
|
76
|
-
## Workflow mode (Claude Code)
|
|
77
|
-
|
|
78
|
-
For developers who know and love the agentic dev loop but are forced to use
|
|
79
|
-
Claude Code at work: workflow mode exports a **Claude Dynamic Workflow** from
|
|
80
|
-
the `pipeline.yaml` they already trust, plus the visualiser they trust. It adds
|
|
81
|
-
the deterministic loop through Claude's mechanism — nothing more. **No Docker
|
|
82
|
-
sandboxing, and no new security claims**: workflow steps run in Claude Code's
|
|
83
|
-
own environment under its permission model (a documented trust-model change
|
|
84
|
-
from engine mode).
|
|
85
|
-
|
|
86
|
-
Install the skill once:
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
pip install "agentic-dev-loop==1.0.0a6" # base install: core + viz, no SDK/Docker
|
|
90
|
-
agentic-dev-loop-workflow install-skill # → ~/.claude/skills/agentic-dev-loop/
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Then invoke `/agentic-dev-loop` in Claude Code. The skill walks through:
|
|
94
|
-
|
|
95
|
-
1. **Build** — `python -m agentic_dev_loop.core.project <pipeline.yaml>` loads +
|
|
96
|
-
validates the pipeline, verifies every agent step's `.agent.md` prompt exists
|
|
97
|
-
(a missing prompt fails the build loudly, naming the file), and writes a
|
|
98
|
-
self-contained `.agentic-dev-loop/workflow.js`. Steps are frozen at creation
|
|
99
|
-
— mid-run `pipeline.yaml` edits never affect a running workflow.
|
|
100
|
-
2. **Run** — the generated JS routes `on_pass` / `on_fail` / `goto` with its own
|
|
101
|
-
attempt counter, injects the failing subagent's output as the next retry note,
|
|
102
|
-
launches the live-graph server (localhost, token-authenticated ingest), and
|
|
103
|
-
invokes the docker-free commit shim, which refuses loudly unless the worktree
|
|
104
|
-
was clean at run start.
|
|
105
|
-
|
|
106
|
-
Engine-only fields (`isolation:`, per-step `network:`, `setup:`) are ignored in
|
|
107
|
-
workflow mode with no effect on the build or run.
|
|
108
|
-
|
|
109
|
-
The base install is deliberately dependency-light (`pydantic` + `PyYAML` only);
|
|
110
|
-
the heavy OpenHands-SDK engine lives in the `[engine]` extra. The `core` and
|
|
111
|
-
`viz` packages are guarded against pulling engine deps.
|
|
112
|
-
|
|
113
66
|
## Usage
|
|
114
67
|
|
|
115
68
|
```bash
|
|
@@ -200,9 +153,49 @@ steps:
|
|
|
200
153
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
201
154
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
202
155
|
| `env:` / `setup:` / `network:` | `env:` declares step vars (fixed safe base + declared vars only); `setup:` provisions the sandbox (command steps, network ON, stamp-cached); `network: true` grants egress to a command or agent step (default off) — see [Per-stage networking](#per-stage-networking). |
|
|
156
|
+
| `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). |
|
|
203
157
|
| `isolation:` (optional) | Configures the sandbox — image override, resource caps, `allow_git_mutations`. It can never disable it. |
|
|
204
158
|
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` — artifacts and checkpoints live outside the sandbox mount. |
|
|
205
159
|
|
|
160
|
+
### Secrets (`secrets:`) — refs only, never values
|
|
161
|
+
|
|
162
|
+
Secret values never belong in the pipeline yaml, the workspace, or `state_dir`.
|
|
163
|
+
Declare the refs at the top level and opt each step into the refs it needs:
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
secrets: # refs only — never values
|
|
167
|
+
kinde-client-secret: # ref name (arbitrary)
|
|
168
|
+
from: env # provider name
|
|
169
|
+
key: KINDIE_CLIENT_SECRET # provider-specific locator
|
|
170
|
+
r2-keys:
|
|
171
|
+
from: env
|
|
172
|
+
key: YOSK_R2_KEYS
|
|
173
|
+
|
|
174
|
+
steps:
|
|
175
|
+
- name: qa
|
|
176
|
+
agent: qa
|
|
177
|
+
secrets: [kinde-client-secret, r2-keys]
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- The engine resolves every declared ref **host-side at run start** (the `env`
|
|
181
|
+
provider reads the named host environment variable). A missing variable, an
|
|
182
|
+
unknown provider, or a ref a step lists without declaring it fails fast —
|
|
183
|
+
naming the ref, the step, and the provider error. No silent fallback.
|
|
184
|
+
- A resolved secret is injected into a step's exec env **only when that step
|
|
185
|
+
lists the ref** — the same path step `env:` uses, no new channel. The secret
|
|
186
|
+
never reaches the provision container, `state_dir`, or any other step.
|
|
187
|
+
- Prefer a mechanical `command` step for secret-consuming work (a script runs
|
|
188
|
+
the app with creds) so the LLM and the secret never meet; an `agent` step
|
|
189
|
+
holds a secret only when its job genuinely requires it.
|
|
190
|
+
- **`.env*` ban.** At run start the workspace root is scanned for `.env*`
|
|
191
|
+
files. Any hit fails fast, naming the files, with the remedy text. The
|
|
192
|
+
convention fixtures `.env.example`, `.env.template`, and `.env.sample` are
|
|
193
|
+
whitelisted. This applies regardless of `network:` flags.
|
|
194
|
+
- **Output scrubbing.** Resolved secret values are scrubbed from every
|
|
195
|
+
engine-written output — captured step output, retry notes, artifacts, and
|
|
196
|
+
`state_dir` files. (Hygiene: the declaring step's agent has already seen
|
|
197
|
+
the value in its own env.)
|
|
198
|
+
|
|
206
199
|
### Per-stage networking
|
|
207
200
|
|
|
208
201
|
The sandbox is network-off by default. A step that legitimately needs egress —
|
|
@@ -234,8 +227,7 @@ steps:
|
|
|
234
227
|
git clone git@github.com:lukedawilson/agentic-dev-loop.git
|
|
235
228
|
cd agentic-dev-loop
|
|
236
229
|
./setup-venv.sh # idempotent .venv setup (never touches bare pip)
|
|
237
|
-
#
|
|
238
|
-
# echo 'OPENAI_API_KEY=...' > .env
|
|
230
|
+
export OPENAI_API_KEY=... # set your LLM key in the host env (no .env in the repo)
|
|
239
231
|
python -m pytest tests/ -q # fast suite
|
|
240
232
|
python -m pytest tests/ -q -m "not docker" # skip engine-dependent tests
|
|
241
233
|
./trigger-agentic-dev-loop.sh --dry-run # run the shipped pipeline against this repo
|
|
@@ -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.0a9"
|
|
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"
|
|
@@ -32,23 +32,8 @@ classifiers = [
|
|
|
32
32
|
"Typing :: Typed",
|
|
33
33
|
]
|
|
34
34
|
dependencies = [
|
|
35
|
-
# Base install = workflow mode (core + viz + workflow CLI). Deliberately
|
|
36
|
-
# dependency-light (TS-1 / NFR-4.1 / BR-2): pydantic + PyYAML only. The
|
|
37
|
-
# heavy OpenHands-SDK engine deps live in the [engine] extra below.
|
|
38
35
|
"pydantic>=2.0",
|
|
39
36
|
"PyYAML>=6.0",
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
[project.urls]
|
|
43
|
-
Homepage = "https://github.com/lukedawilson/agentic-dev-loop"
|
|
44
|
-
|
|
45
|
-
[project.scripts]
|
|
46
|
-
agentic-dev-loop = "agentic_dev_loop.engine.cli:main"
|
|
47
|
-
agentic-dev-loop-workflow = "agentic_dev_loop.workflow.cli:main"
|
|
48
|
-
agentic-dev-loop-commit = "agentic_dev_loop.engine.commit_shim:main"
|
|
49
|
-
|
|
50
|
-
[project.optional-dependencies]
|
|
51
|
-
engine = [
|
|
52
37
|
"openhands-sdk ~=1.17.0",
|
|
53
38
|
"openhands-tools ~=1.17.0",
|
|
54
39
|
# openhands-sdk declares agent-client-protocol>=0.8.1 with NO upper bound;
|
|
@@ -59,12 +44,15 @@ engine = [
|
|
|
59
44
|
"langgraph>=1.2",
|
|
60
45
|
"langgraph-checkpoint-sqlite>=3.0",
|
|
61
46
|
]
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
|
|
48
|
+
[project.urls]
|
|
49
|
+
Homepage = "https://github.com/lukedawilson/agentic-dev-loop"
|
|
50
|
+
|
|
51
|
+
[project.scripts]
|
|
52
|
+
agentic-dev-loop = "agentic_dev_loop.engine.cli:main"
|
|
53
|
+
|
|
54
|
+
[project.optional-dependencies]
|
|
66
55
|
dev = [
|
|
67
|
-
"agentic-dev-loop[engine]",
|
|
68
56
|
"hypothesis>=6.0",
|
|
69
57
|
"pytest>=8.0",
|
|
70
58
|
]
|
|
@@ -74,7 +62,6 @@ where = ["src"]
|
|
|
74
62
|
|
|
75
63
|
[tool.setuptools.package-data]
|
|
76
64
|
agentic_dev_loop = ["py.typed"]
|
|
77
|
-
"agentic_dev_loop.core" = ["workflow.js.template"]
|
|
78
65
|
|
|
79
66
|
[tool.pytest.ini_options]
|
|
80
67
|
markers = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Pipeline model + parse +
|
|
1
|
+
"""Pipeline model + parse + results (core).
|
|
2
2
|
|
|
3
3
|
Dependency-light by contract (BR-2): pydantic + PyYAML + stdlib only. A guard
|
|
4
4
|
test fails if this package imports engine/viz or heavy deps.
|
|
@@ -9,17 +9,10 @@ from .config import (PipelineConfig, StepConfig, LlmConfig, IsolationConfig,
|
|
|
9
9
|
default_state_dir, TERMINALS)
|
|
10
10
|
from .results import Result
|
|
11
11
|
from .verdict import Verdict, VerdictError, parse_verdict, RETRY
|
|
12
|
-
from .projection import (ProjectedStep, WorkflowProjection, project_workflow,
|
|
13
|
-
validate_prompt_refs, render_workflow_js,
|
|
14
|
-
clean_at_start_flag, pipeline_hash, core_version,
|
|
15
|
-
WORKFLOW_TEMPLATE)
|
|
16
12
|
|
|
17
13
|
__all__ = [
|
|
18
14
|
"PipelineConfig", "StepConfig", "LlmConfig", "IsolationConfig",
|
|
19
15
|
"VerdictSpec", "Route", "route_target", "load_config",
|
|
20
16
|
"default_state_dir", "TERMINALS",
|
|
21
17
|
"Result", "Verdict", "VerdictError", "parse_verdict", "RETRY",
|
|
22
|
-
"ProjectedStep", "WorkflowProjection", "project_workflow",
|
|
23
|
-
"validate_prompt_refs", "render_workflow_js", "clean_at_start_flag",
|
|
24
|
-
"pipeline_hash", "core_version", "WORKFLOW_TEMPLATE",
|
|
25
18
|
]
|
|
@@ -11,6 +11,7 @@ import yaml
|
|
|
11
11
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
12
12
|
|
|
13
13
|
TERMINALS = frozenset({"success", "failure"})
|
|
14
|
+
KNOWN_SECRET_PROVIDERS = frozenset({"env"})
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class VerdictSpec(BaseModel):
|
|
@@ -36,6 +37,34 @@ def route_target(route: RouteTarget | None) -> str | None:
|
|
|
36
37
|
return route.goto if isinstance(route, Route) else route
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
class SecretSpec(BaseModel):
|
|
41
|
+
"""One ``secrets:`` ref — the locator only, never a value. ``from`` names
|
|
42
|
+
the provider (``env`` first); ``key`` is that provider's locator (for
|
|
43
|
+
``env``, the host environment variable to read and the step env name to
|
|
44
|
+
inject). Unknown keys fail at load (extra="forbid")."""
|
|
45
|
+
|
|
46
|
+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
47
|
+
|
|
48
|
+
from_: str = Field(alias="from")
|
|
49
|
+
key: str
|
|
50
|
+
|
|
51
|
+
@field_validator("from_")
|
|
52
|
+
@classmethod
|
|
53
|
+
def _known_provider(cls, v: str) -> str:
|
|
54
|
+
if v not in KNOWN_SECRET_PROVIDERS:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"unknown secret provider {v!r} (known: "
|
|
57
|
+
f"{sorted(KNOWN_SECRET_PROVIDERS)})")
|
|
58
|
+
return v
|
|
59
|
+
|
|
60
|
+
@field_validator("key")
|
|
61
|
+
@classmethod
|
|
62
|
+
def _nonempty_key(cls, v: str) -> str:
|
|
63
|
+
if not v.strip():
|
|
64
|
+
raise ValueError("secret key must be a non-empty string")
|
|
65
|
+
return v
|
|
66
|
+
|
|
67
|
+
|
|
39
68
|
class IsolationConfig(BaseModel):
|
|
40
69
|
"""Optional `isolation:` section — configures the always-on sandbox (BR-2).
|
|
41
70
|
Absence means opinionated defaults, never unsandboxed behaviour. Unknown
|
|
@@ -86,6 +115,7 @@ class StepConfig(BaseModel):
|
|
|
86
115
|
env: dict[str, str | None] | None = None # NFR-1: null = inherit host value
|
|
87
116
|
setup: str | None = None # BR-13: command steps only
|
|
88
117
|
network: bool = False # per-stage egress (command + agent steps; action steps reject)
|
|
118
|
+
secrets: list[str] = [] # declared ref names; action steps reject
|
|
89
119
|
on_pass: RouteTarget | None = None
|
|
90
120
|
on_fail: RouteTarget | None = None
|
|
91
121
|
|
|
@@ -148,6 +178,11 @@ class StepConfig(BaseModel):
|
|
|
148
178
|
f"step {self.name!r}: 'network' is only allowed on 'agent' or "
|
|
149
179
|
"'command' steps"
|
|
150
180
|
)
|
|
181
|
+
if self.secrets and self.action is not None:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
f"step {self.name!r}: 'secrets' is only allowed on 'agent' or "
|
|
184
|
+
"'command' steps"
|
|
185
|
+
)
|
|
151
186
|
return self
|
|
152
187
|
|
|
153
188
|
|
|
@@ -170,10 +205,28 @@ class PipelineConfig(BaseModel):
|
|
|
170
205
|
llm: LlmConfig
|
|
171
206
|
isolation: IsolationConfig | None = None # None = always-on defaults
|
|
172
207
|
state_dir: str = ".pr" # default relocated to ~/.agentic-dev-loop/runs/<hash>/ at load
|
|
208
|
+
secrets: dict[str, SecretSpec] = {} # ref name -> locator; values never appear here
|
|
173
209
|
max_attempts: int = 3
|
|
174
210
|
agents_dir: str
|
|
175
211
|
steps: list[StepConfig] = Field(min_length=1)
|
|
176
212
|
|
|
213
|
+
@field_validator("secrets", mode="before")
|
|
214
|
+
@classmethod
|
|
215
|
+
def _reject_value_shaped_refs(cls, v: Any) -> Any:
|
|
216
|
+
if v is None:
|
|
217
|
+
return {}
|
|
218
|
+
if not isinstance(v, dict):
|
|
219
|
+
raise ValueError(
|
|
220
|
+
"'secrets' must be a mapping of ref name to "
|
|
221
|
+
"{from: ..., key: ...}")
|
|
222
|
+
for name, spec in v.items():
|
|
223
|
+
if not isinstance(spec, dict):
|
|
224
|
+
raise ValueError(
|
|
225
|
+
f"secret ref {name!r} must be a mapping "
|
|
226
|
+
"{from: ..., key: ...}, not a literal value — secret "
|
|
227
|
+
"values never belong in the pipeline yaml")
|
|
228
|
+
return v
|
|
229
|
+
|
|
177
230
|
@model_validator(mode="after")
|
|
178
231
|
def _check_graph(self) -> "PipelineConfig":
|
|
179
232
|
names = [s.name for s in self.steps]
|
|
@@ -191,6 +244,12 @@ class PipelineConfig(BaseModel):
|
|
|
191
244
|
raise ValueError(
|
|
192
245
|
f"step {step.name!r}: unknown route target {target!r}"
|
|
193
246
|
)
|
|
247
|
+
for step in self.steps:
|
|
248
|
+
for ref in step.secrets:
|
|
249
|
+
if ref not in self.secrets:
|
|
250
|
+
raise ValueError(
|
|
251
|
+
f"step {step.name!r}: secret ref {ref!r} is not "
|
|
252
|
+
"declared in the top-level 'secrets:' section")
|
|
194
253
|
return self
|
|
195
254
|
|
|
196
255
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`Verdict.from_text` parses the captured response for routing; expected
|
|
4
4
|
conditions are typed `Result` errors, never exceptions. `parse_verdict`
|
|
5
|
-
collapses that `Result` to a route string for
|
|
5
|
+
collapses that `Result` to a route string for routing consumers: a clear
|
|
6
6
|
decision maps to its canonical value, anything unclear maps to ``retry`` —
|
|
7
7
|
totality for arbitrary input (no crash, no silent routing).
|
|
8
8
|
"""
|
{agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/__init__.py
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Engine package — graph/stages/sandbox/CLI and the heavy OpenHands-SDK
|
|
2
|
-
dependencies
|
|
2
|
+
dependencies."""
|
|
@@ -110,13 +110,17 @@ class SandboxTerminalExecutor(ToolExecutor):
|
|
|
110
110
|
def register_sandbox_terminal(sandbox: "Sandbox", *, env: dict[str, str | None] | None = None,
|
|
111
111
|
workspace: str = "", pipeline: str | None = None,
|
|
112
112
|
step: str | None = None,
|
|
113
|
-
network: bool = False
|
|
113
|
+
network: bool = False,
|
|
114
|
+
secrets: dict[str, str] | None = None) -> None:
|
|
114
115
|
"""Per-run re-registration of the `terminal` name (KD-4): a callable
|
|
115
116
|
factory that builds the SDK terminal with a `SandboxTerminalExecutor`.
|
|
116
|
-
Pipelines are sequential, so re-registering per agent step is safe.
|
|
117
|
+
Pipelines are sequential, so re-registering per agent step is safe.
|
|
118
|
+
|
|
119
|
+
``secrets`` (019) is the per-step resolved secret env, injected into the
|
|
120
|
+
terminal's exec env only for steps that declared the refs."""
|
|
117
121
|
from openhands.sdk.tool import register_tool
|
|
118
122
|
|
|
119
|
-
resolved_env = resolve_sandbox_env(env)
|
|
123
|
+
resolved_env = resolve_sandbox_env(env, secrets=secrets)
|
|
120
124
|
|
|
121
125
|
def factory(conv_state, **params):
|
|
122
126
|
return TerminalTool.create(
|
|
@@ -17,6 +17,7 @@ from .loaders import resolve_docs
|
|
|
17
17
|
from .isolation import (isolation_state_dir, resolve_effective_profile,
|
|
18
18
|
resolve_image_ref)
|
|
19
19
|
from .registry import Registry, default_registry, load_extensions
|
|
20
|
+
from .secrets import scan_env_files
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _validate_agents(cfg: PipelineConfig, agents_dir: Path) -> None:
|
|
@@ -113,6 +114,7 @@ def dry_run(cfg_path, args, registry: Registry | None = None) -> int:
|
|
|
113
114
|
cfg_path = Path(cfg_path)
|
|
114
115
|
cfg = load_config(cfg_path)
|
|
115
116
|
base = cfg_path.resolve().parent
|
|
117
|
+
scan_env_files(Path.cwd())
|
|
116
118
|
|
|
117
119
|
if registry is None:
|
|
118
120
|
registry = default_registry()
|
|
@@ -22,6 +22,7 @@ from agentic_dev_loop.core.config import load_config
|
|
|
22
22
|
from .isolation import startup_sandbox
|
|
23
23
|
from .llm import build_llm
|
|
24
24
|
from .loaders import resolve_docs
|
|
25
|
+
from .secrets import resolve_secrets, scan_env_files
|
|
25
26
|
from .registry import Registry, default_registry, load_extensions
|
|
26
27
|
from .stages import (Runtime, build_context, clear_attempt_artifacts,
|
|
27
28
|
make_action_node, make_agent_node, make_bump_node,
|
|
@@ -107,6 +108,11 @@ def run_pipeline(cfg_path, args, *, viz_bus=None,
|
|
|
107
108
|
base = cfg_path.resolve().parent
|
|
108
109
|
max_attempts = getattr(args, "max_attempts", None) or cfg.max_attempts
|
|
109
110
|
|
|
111
|
+
# 019: refuse secret-bearing workspace files and resolve declared secret
|
|
112
|
+
# refs host-side BEFORE any sandbox/LLM work — fail fast, loudly.
|
|
113
|
+
scan_env_files(Path.cwd())
|
|
114
|
+
resolved_secrets = resolve_secrets(cfg)
|
|
115
|
+
|
|
110
116
|
if registry is None:
|
|
111
117
|
registry = default_registry()
|
|
112
118
|
load_extensions(cfg.extensions, registry)
|
|
@@ -122,16 +128,17 @@ def run_pipeline(cfg_path, args, *, viz_bus=None,
|
|
|
122
128
|
refresh=getattr(args, "refresh", False))
|
|
123
129
|
try:
|
|
124
130
|
return _run_pipeline(cfg, base, registry, state_dir, max_attempts,
|
|
125
|
-
args, viz_bus, sandbox)
|
|
131
|
+
args, viz_bus, sandbox, resolved_secrets)
|
|
126
132
|
finally:
|
|
127
133
|
sandbox.close()
|
|
128
134
|
|
|
129
135
|
|
|
130
136
|
def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
|
131
|
-
sandbox) -> tuple[bool, int]:
|
|
137
|
+
sandbox, resolved_secrets) -> tuple[bool, int]:
|
|
132
138
|
runtime = Runtime(cfg=cfg, registry=registry, llm=build_llm(cfg.llm),
|
|
133
139
|
agents_dir=base / cfg.agents_dir, state_dir=state_dir,
|
|
134
|
-
workspace=Path.cwd(), sandbox=sandbox
|
|
140
|
+
workspace=Path.cwd(), sandbox=sandbox,
|
|
141
|
+
secrets=resolved_secrets)
|
|
135
142
|
state_dir.mkdir(parents=True, exist_ok=True)
|
|
136
143
|
|
|
137
144
|
resume = getattr(args, "resume", None)
|
{agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
@@ -183,11 +183,17 @@ def resolve_effective_profile(isolation: IsolationConfig | None) -> EffectivePro
|
|
|
183
183
|
|
|
184
184
|
|
|
185
185
|
def resolve_sandbox_env(step_env: dict[str, str | None] | None, *,
|
|
186
|
-
host_env: Mapping[str, str] | None = None
|
|
186
|
+
host_env: Mapping[str, str] | None = None,
|
|
187
|
+
secrets: Mapping[str, str] | None = None) -> dict[str, str]:
|
|
187
188
|
"""Fixed safe base (PATH/HOME/LANG/LC_ALL/TERM) + the step's declared
|
|
188
189
|
``env:`` mapping only. A declared value of ``None`` inherits the host
|
|
189
190
|
value; the global ``env_allowlist`` is never consulted and the LLM key is
|
|
190
|
-
never auto-added (NFR-3.1 / BR-9).
|
|
191
|
+
never auto-added (NFR-3.1 / BR-9).
|
|
192
|
+
|
|
193
|
+
``secrets`` (019) carries the per-step resolved secret env — injected last,
|
|
194
|
+
so a declared ref is authoritative over any same-named ``env:`` entry. It
|
|
195
|
+
arrives only from ``step_secret_env``, which maps only the refs the step
|
|
196
|
+
declared, so no secret ever reaches an undeclaring step."""
|
|
191
197
|
source = os.environ if host_env is None else host_env
|
|
192
198
|
env = dict(SANDBOX_ENV_BASE)
|
|
193
199
|
for name, value in (step_env or {}).items():
|
|
@@ -196,6 +202,8 @@ def resolve_sandbox_env(step_env: dict[str, str | None] | None, *,
|
|
|
196
202
|
env[name] = source[name]
|
|
197
203
|
else:
|
|
198
204
|
env[name] = value
|
|
205
|
+
for name, value in (secrets or {}).items():
|
|
206
|
+
env[name] = value
|
|
199
207
|
return env
|
|
200
208
|
|
|
201
209
|
|
{agentic_dev_loop-1.0.0a7 → agentic_dev_loop-1.0.0a9}/src/agentic_dev_loop/engine/outputs.py
RENAMED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
channel (unit-1).
|
|
3
3
|
|
|
4
4
|
The pipeline — not the agent — writes the step's artifact from the agent's
|
|
5
|
-
final SDK response. Verdict parsing lives in `core.verdict
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
final SDK response. Verdict parsing lives in `core.verdict`; this module only
|
|
6
|
+
owns the SDK-side capture. Expected conditions are typed `Result` errors,
|
|
7
|
+
never exceptions.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
@@ -16,6 +16,7 @@ from pathlib import Path
|
|
|
16
16
|
from openhands.sdk.conversation.response_utils import get_agent_final_response
|
|
17
17
|
|
|
18
18
|
from agentic_dev_loop.core.results import Result
|
|
19
|
+
from .secrets import redact_values
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class CaptureError(enum.Enum):
|
|
@@ -32,11 +33,13 @@ class CapturedOutput:
|
|
|
32
33
|
|
|
33
34
|
@staticmethod
|
|
34
35
|
def from_agent_result(
|
|
35
|
-
conv, artifact: str, state_dir: Path
|
|
36
|
+
conv, artifact: str, state_dir: Path,
|
|
37
|
+
redactions: tuple[str, ...] = ()
|
|
36
38
|
) -> "Result[CapturedOutput, CaptureError]":
|
|
37
39
|
text = _final_agent_text(conv)
|
|
38
40
|
if not text.strip():
|
|
39
41
|
return Result.err(CaptureError.EMPTY_RESPONSE)
|
|
42
|
+
text = redact_values(text, redactions)
|
|
40
43
|
return Result.ok(CapturedOutput(raw_text=text, artifact_path=state_dir / artifact))
|
|
41
44
|
|
|
42
45
|
def write(self) -> Result[None, CaptureError]:
|