agentic-dev-loop 1.0.0a8__tar.gz → 1.0.0a10__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.0a8 → agentic_dev_loop-1.0.0a10}/PKG-INFO +120 -12
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/README.md +119 -11
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/core/config.py +67 -3
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/actions.py +24 -3
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/agents.py +7 -3
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/cli.py +2 -1
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/dryrun.py +19 -5
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/graph.py +32 -7
- agentic_dev_loop-1.0.0a10/src/agentic_dev_loop/engine/hostboundary.py +137 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/isolation.py +10 -2
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/outputs.py +4 -1
- agentic_dev_loop-1.0.0a10/src/agentic_dev_loop/engine/registry.py +291 -0
- agentic_dev_loop-1.0.0a10/src/agentic_dev_loop/engine/secrets.py +118 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/stages.py +94 -13
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop.egg-info/PKG-INFO +120 -12
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop.egg-info/SOURCES.txt +7 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_actions.py +39 -1
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_cli.py +2 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_config.py +6 -5
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_dry_run.py +39 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_e2e_fixtures.py +5 -1
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_graph.py +22 -2
- agentic_dev_loop-1.0.0a10/tests/test_hostboundary.py +136 -0
- agentic_dev_loop-1.0.0a10/tests/test_hostboundary_integration.py +91 -0
- agentic_dev_loop-1.0.0a10/tests/test_registry.py +243 -0
- agentic_dev_loop-1.0.0a10/tests/test_secrets.py +221 -0
- agentic_dev_loop-1.0.0a10/tests/test_secrets_integration.py +67 -0
- agentic_dev_loop-1.0.0a10/tests/test_secrets_pipeline.py +106 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_stages.py +50 -1
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_viz.py +2 -1
- agentic_dev_loop-1.0.0a8/src/agentic_dev_loop/engine/registry.py +0 -133
- agentic_dev_loop-1.0.0a8/tests/test_registry.py +0 -114
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_isolation.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/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.0a10
|
|
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
|
|
@@ -90,10 +90,27 @@ LLM timeout (agent runs are long).
|
|
|
90
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. |
|
|
91
91
|
|
|
92
92
|
```bash
|
|
93
|
-
pip install "agentic-dev-loop==1.0.
|
|
93
|
+
pip install "agentic-dev-loop==1.0.0a9"
|
|
94
94
|
agentic-dev-loop --version
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
The canonical consumer install is **pipx**, which puts the engine's venv
|
|
98
|
+
*outside* the workspace — the sandbox mount set is exactly `{workspace}`, so an
|
|
99
|
+
out-of-workspace install is unmounted and never agent-writable:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pipx install "agentic-dev-loop==1.0.0a9"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If you install with `uv` shortly after a release, pass `--refresh` — PyPI
|
|
106
|
+
serves the package index with `Cache-Control: max-age=600`, and uv otherwise
|
|
107
|
+
reuses a pre-upload snapshot of the index for up to 10 minutes, reporting
|
|
108
|
+
"no version of agentic-dev-loop==X.Y.Z" for a version that is already live:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
uv pip install --refresh "agentic-dev-loop==1.0.0a9"
|
|
112
|
+
```
|
|
113
|
+
|
|
97
114
|
On the first run the engine pulls the **digest-pinned** dev image
|
|
98
115
|
(`ghcr.io/lukedawilson/agentic-dev-loop/dev`) and verifies its digest — the
|
|
99
116
|
supply-chain check refuses to continue on a mismatch. If you're running
|
|
@@ -188,12 +205,53 @@ steps:
|
|
|
188
205
|
|
|
189
206
|
| Key | Notes |
|
|
190
207
|
| --- | --- |
|
|
191
|
-
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call); actions are
|
|
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)). |
|
|
192
209
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
193
210
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
194
211
|
| `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). |
|
|
212
|
+
| `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). |
|
|
195
213
|
| `isolation:` (optional) | Configures the sandbox — image override, resource caps, `allow_git_mutations`. It can never disable it. |
|
|
196
214
|
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` — artifacts and checkpoints live outside the sandbox mount. |
|
|
215
|
+
| `extensions_dir` | Where consumer `extensions:` modules resolve — defaults to `~/.agentic-dev-loop/extensions`, **outside** the workspace. Workspace-resident extension/tool modules are refused at load. |
|
|
216
|
+
|
|
217
|
+
### Secrets (`secrets:`) — refs only, never values
|
|
218
|
+
|
|
219
|
+
Secret values never belong in the pipeline yaml, the workspace, or `state_dir`.
|
|
220
|
+
Declare the refs at the top level and opt each step into the refs it needs:
|
|
221
|
+
|
|
222
|
+
```yaml
|
|
223
|
+
secrets: # refs only — never values
|
|
224
|
+
kinde-client-secret: # ref name (arbitrary)
|
|
225
|
+
from: env # provider name
|
|
226
|
+
key: KINDIE_CLIENT_SECRET # provider-specific locator
|
|
227
|
+
r2-keys:
|
|
228
|
+
from: env
|
|
229
|
+
key: YOSK_R2_KEYS
|
|
230
|
+
|
|
231
|
+
steps:
|
|
232
|
+
- name: qa
|
|
233
|
+
agent: qa
|
|
234
|
+
secrets: [kinde-client-secret, r2-keys]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
- The engine resolves every declared ref **host-side at run start** (the `env`
|
|
238
|
+
provider reads the named host environment variable). A missing variable, an
|
|
239
|
+
unknown provider, or a ref a step lists without declaring it fails fast —
|
|
240
|
+
naming the ref, the step, and the provider error. No silent fallback.
|
|
241
|
+
- A resolved secret is injected into a step's exec env **only when that step
|
|
242
|
+
lists the ref** — the same path step `env:` uses, no new channel. The secret
|
|
243
|
+
never reaches the provision container, `state_dir`, or any other step.
|
|
244
|
+
- Prefer a mechanical `command` step for secret-consuming work (a script runs
|
|
245
|
+
the app with creds) so the LLM and the secret never meet; an `agent` step
|
|
246
|
+
holds a secret only when its job genuinely requires it.
|
|
247
|
+
- **`.env*` ban.** At run start the workspace root is scanned for `.env*`
|
|
248
|
+
files. Any hit fails fast, naming the files, with the remedy text. The
|
|
249
|
+
convention fixtures `.env.example`, `.env.template`, and `.env.sample` are
|
|
250
|
+
whitelisted. This applies regardless of `network:` flags.
|
|
251
|
+
- **Output scrubbing.** Resolved secret values are scrubbed from every
|
|
252
|
+
engine-written output — captured step output, retry notes, artifacts, and
|
|
253
|
+
`state_dir` files. (Hygiene: the declaring step's agent has already seen
|
|
254
|
+
the value in its own env.)
|
|
197
255
|
|
|
198
256
|
### Per-stage networking
|
|
199
257
|
|
|
@@ -213,29 +271,79 @@ steps:
|
|
|
213
271
|
sandbox container (`--read-only`, `--cap-drop ALL`, single workspace mount)
|
|
214
272
|
on the Docker bridge network — the default network-none container is always
|
|
215
273
|
created too, so no infrastructure work happens mid-run.
|
|
216
|
-
- **Command and
|
|
217
|
-
never
|
|
274
|
+
- **Command, agent, and sandbox-action steps** may declare it. The host-side
|
|
275
|
+
built-in `commit` action never takes it (refused once the registry is known).
|
|
218
276
|
- **Author opt-in is the control.** A networked step gives its agent raw
|
|
219
277
|
egress for that stage; the default stays network-off everywhere. Reserve it
|
|
220
278
|
for stages that need it (QA, integration). The residual risk is documented
|
|
221
279
|
in [docs/setup.md](docs/setup.md).
|
|
222
280
|
|
|
281
|
+
## Host boundary (security model)
|
|
282
|
+
|
|
283
|
+
The orchestrator, tool layer, and the `commit` action run in the **host
|
|
284
|
+
process** — they hold your credentials and your full user account, so the
|
|
285
|
+
engine keeps anything agent-writable out of that process:
|
|
286
|
+
|
|
287
|
+
- **Consumer actions are sandbox-executed.** An action you register via an
|
|
288
|
+
extension is a command-shaped script (`script + params`), run through the
|
|
289
|
+
same sandbox path as command steps — the exit code is the verdict, and the
|
|
290
|
+
code never runs in the host. Only the engine's trusted built-ins (`commit`,
|
|
291
|
+
document loaders) run host-side.
|
|
292
|
+
- **Extensions load from outside the workspace.** `extensions_dir` defaults to
|
|
293
|
+
`~/.agentic-dev-loop/extensions`; at load time any extension or tool module
|
|
294
|
+
whose file resolves under the workspace is refused *before import*, naming
|
|
295
|
+
the file.
|
|
296
|
+
- **The engine installs outside the workspace.** Install with pipx so the venv
|
|
297
|
+
is unmounted from the sandbox; a startup self-check refuses to run if
|
|
298
|
+
`agentic_dev_loop` resolves under the workspace (an editable install bypasses
|
|
299
|
+
everything).
|
|
300
|
+
- **Tooling is read-only by the loop.** At run start the pipeline file,
|
|
301
|
+
`agents_dir`, `additional_files`, `aidlc-docs/`, and `tools/` must be
|
|
302
|
+
git-clean relative to HEAD — otherwise the run refuses, naming the dirty
|
|
303
|
+
files. The `commit` action excludes those paths from its sweep, so the loop
|
|
304
|
+
never commits its own tooling.
|
|
305
|
+
- **`commit` stays host-side, deliberately.** It holds push credentials; moving
|
|
306
|
+
it into the sandbox would trade host-exec-of-trusted-code for
|
|
307
|
+
credential-in-sandbox. It runs argv-style with a scrubbed child env and
|
|
308
|
+
`-c core.hooksPath=~/.agentic-dev-loop/state/hooks` so repo hooks never
|
|
309
|
+
execute.
|
|
310
|
+
|
|
311
|
+
Net: the host executes nothing from the workspace, reads only git-clean
|
|
312
|
+
control files, and the only agent-writable host-consumed thing left is the code
|
|
313
|
+
the loop is building.
|
|
314
|
+
|
|
315
|
+
### Consumer migration notes
|
|
316
|
+
|
|
317
|
+
- **Actions** — replace a host-side `fn` registration with
|
|
318
|
+
`registry.add_sandbox_action(name, script)`. `script` is a command string
|
|
319
|
+
rendered with the step's `params:` plus `{subject}`; the exit code is the
|
|
320
|
+
verdict. `add_action` (host-side) is now engine-built-in-only and is refused
|
|
321
|
+
from extensions.
|
|
322
|
+
- **Extensions** — move workspace-resident extension modules (e.g.
|
|
323
|
+
`extensions/*.py`) into `~/.agentic-dev-loop/extensions/` (or whatever
|
|
324
|
+
`extensions_dir:` names), and reinstall the engine via pipx so it is not
|
|
325
|
+
editable/in-workspace.
|
|
326
|
+
- **Tooling** — commit the pipeline, agent defs, shared `additional_files`,
|
|
327
|
+
`aidlc-docs/`, and `tools/` before running: the run refuses dirty tooling,
|
|
328
|
+
and `commit` never commits it.
|
|
329
|
+
|
|
223
330
|
## Development environment
|
|
224
331
|
|
|
225
332
|
```bash
|
|
226
333
|
git clone git@github.com:lukedawilson/agentic-dev-loop.git
|
|
227
334
|
cd agentic-dev-loop
|
|
228
|
-
|
|
229
|
-
#
|
|
230
|
-
#
|
|
231
|
-
python -m pytest tests/ -q # fast suite
|
|
335
|
+
pipx install "agentic-dev-loop==1.0.0a9" # engine venv OUTSIDE the workspace (self-check)
|
|
336
|
+
export OPENAI_API_KEY=... # set your LLM key in the host env (no .env in the repo)
|
|
337
|
+
python -m pytest tests/ -q # fast suite (in-repo .venv is for tests only)
|
|
232
338
|
python -m pytest tests/ -q -m "not docker" # skip engine-dependent tests
|
|
233
|
-
|
|
339
|
+
agentic-dev-loop agentic-dev-loop/pipeline.yaml --dry-run # run the shipped pipeline
|
|
234
340
|
./run-demo.sh # viz demo — scripted run replay, no pipeline needed
|
|
235
341
|
```
|
|
236
342
|
|
|
237
|
-
- `setup-venv.sh`
|
|
238
|
-
|
|
343
|
+
- `setup-venv.sh` builds the throwaway `.venv` for local `pytest` runs (the
|
|
344
|
+
pipeline's `test` step provisions its own `.venv-sandbox` inside the
|
|
345
|
+
sandbox). The loop itself must run from the pipx install — never an
|
|
346
|
+
in-workspace `.venv`, which the startup self-check refuses.
|
|
239
347
|
- Docker-marked integration tests need a local daemon; CI runs them on Linux.
|
|
240
348
|
- The shipped pipeline (`agentic-dev-loop/pipeline.yaml`) **dogfoods the tool on
|
|
241
349
|
its own repo** — dev → test → review → qa → commit, with the engine's own
|
|
@@ -51,10 +51,27 @@ LLM timeout (agent runs are long).
|
|
|
51
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. |
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
pip install "agentic-dev-loop==1.0.
|
|
54
|
+
pip install "agentic-dev-loop==1.0.0a9"
|
|
55
55
|
agentic-dev-loop --version
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
The canonical consumer install is **pipx**, which puts the engine's venv
|
|
59
|
+
*outside* the workspace — the sandbox mount set is exactly `{workspace}`, so an
|
|
60
|
+
out-of-workspace install is unmounted and never agent-writable:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pipx install "agentic-dev-loop==1.0.0a9"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If you install with `uv` shortly after a release, pass `--refresh` — PyPI
|
|
67
|
+
serves the package index with `Cache-Control: max-age=600`, and uv otherwise
|
|
68
|
+
reuses a pre-upload snapshot of the index for up to 10 minutes, reporting
|
|
69
|
+
"no version of agentic-dev-loop==X.Y.Z" for a version that is already live:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
uv pip install --refresh "agentic-dev-loop==1.0.0a9"
|
|
73
|
+
```
|
|
74
|
+
|
|
58
75
|
On the first run the engine pulls the **digest-pinned** dev image
|
|
59
76
|
(`ghcr.io/lukedawilson/agentic-dev-loop/dev`) and verifies its digest — the
|
|
60
77
|
supply-chain check refuses to continue on a mismatch. If you're running
|
|
@@ -149,12 +166,53 @@ steps:
|
|
|
149
166
|
|
|
150
167
|
| Key | Notes |
|
|
151
168
|
| --- | --- |
|
|
152
|
-
| `agent:` / `command:` / `action:` | Exactly one per step. Agents live in `agents_dir` as `<name>.agent.md`; commands run sandboxed (mechanical, never an LLM call); actions are
|
|
169
|
+
| `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)). |
|
|
153
170
|
| `produces:` / `artifact:` + `verdicts:` | Hygiene-tracked outputs vs verdict-parsed routing. The **last** `VERDICT:` line wins; stale artifacts are deleted at each attempt start. |
|
|
154
171
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
155
172
|
| `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). |
|
|
173
|
+
| `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). |
|
|
156
174
|
| `isolation:` (optional) | Configures the sandbox — image override, resource caps, `allow_git_mutations`. It can never disable it. |
|
|
157
175
|
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` — artifacts and checkpoints live outside the sandbox mount. |
|
|
176
|
+
| `extensions_dir` | Where consumer `extensions:` modules resolve — defaults to `~/.agentic-dev-loop/extensions`, **outside** the workspace. Workspace-resident extension/tool modules are refused at load. |
|
|
177
|
+
|
|
178
|
+
### Secrets (`secrets:`) — refs only, never values
|
|
179
|
+
|
|
180
|
+
Secret values never belong in the pipeline yaml, the workspace, or `state_dir`.
|
|
181
|
+
Declare the refs at the top level and opt each step into the refs it needs:
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
secrets: # refs only — never values
|
|
185
|
+
kinde-client-secret: # ref name (arbitrary)
|
|
186
|
+
from: env # provider name
|
|
187
|
+
key: KINDIE_CLIENT_SECRET # provider-specific locator
|
|
188
|
+
r2-keys:
|
|
189
|
+
from: env
|
|
190
|
+
key: YOSK_R2_KEYS
|
|
191
|
+
|
|
192
|
+
steps:
|
|
193
|
+
- name: qa
|
|
194
|
+
agent: qa
|
|
195
|
+
secrets: [kinde-client-secret, r2-keys]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- The engine resolves every declared ref **host-side at run start** (the `env`
|
|
199
|
+
provider reads the named host environment variable). A missing variable, an
|
|
200
|
+
unknown provider, or a ref a step lists without declaring it fails fast —
|
|
201
|
+
naming the ref, the step, and the provider error. No silent fallback.
|
|
202
|
+
- A resolved secret is injected into a step's exec env **only when that step
|
|
203
|
+
lists the ref** — the same path step `env:` uses, no new channel. The secret
|
|
204
|
+
never reaches the provision container, `state_dir`, or any other step.
|
|
205
|
+
- Prefer a mechanical `command` step for secret-consuming work (a script runs
|
|
206
|
+
the app with creds) so the LLM and the secret never meet; an `agent` step
|
|
207
|
+
holds a secret only when its job genuinely requires it.
|
|
208
|
+
- **`.env*` ban.** At run start the workspace root is scanned for `.env*`
|
|
209
|
+
files. Any hit fails fast, naming the files, with the remedy text. The
|
|
210
|
+
convention fixtures `.env.example`, `.env.template`, and `.env.sample` are
|
|
211
|
+
whitelisted. This applies regardless of `network:` flags.
|
|
212
|
+
- **Output scrubbing.** Resolved secret values are scrubbed from every
|
|
213
|
+
engine-written output — captured step output, retry notes, artifacts, and
|
|
214
|
+
`state_dir` files. (Hygiene: the declaring step's agent has already seen
|
|
215
|
+
the value in its own env.)
|
|
158
216
|
|
|
159
217
|
### Per-stage networking
|
|
160
218
|
|
|
@@ -174,29 +232,79 @@ steps:
|
|
|
174
232
|
sandbox container (`--read-only`, `--cap-drop ALL`, single workspace mount)
|
|
175
233
|
on the Docker bridge network — the default network-none container is always
|
|
176
234
|
created too, so no infrastructure work happens mid-run.
|
|
177
|
-
- **Command and
|
|
178
|
-
never
|
|
235
|
+
- **Command, agent, and sandbox-action steps** may declare it. The host-side
|
|
236
|
+
built-in `commit` action never takes it (refused once the registry is known).
|
|
179
237
|
- **Author opt-in is the control.** A networked step gives its agent raw
|
|
180
238
|
egress for that stage; the default stays network-off everywhere. Reserve it
|
|
181
239
|
for stages that need it (QA, integration). The residual risk is documented
|
|
182
240
|
in [docs/setup.md](docs/setup.md).
|
|
183
241
|
|
|
242
|
+
## Host boundary (security model)
|
|
243
|
+
|
|
244
|
+
The orchestrator, tool layer, and the `commit` action run in the **host
|
|
245
|
+
process** — they hold your credentials and your full user account, so the
|
|
246
|
+
engine keeps anything agent-writable out of that process:
|
|
247
|
+
|
|
248
|
+
- **Consumer actions are sandbox-executed.** An action you register via an
|
|
249
|
+
extension is a command-shaped script (`script + params`), run through the
|
|
250
|
+
same sandbox path as command steps — the exit code is the verdict, and the
|
|
251
|
+
code never runs in the host. Only the engine's trusted built-ins (`commit`,
|
|
252
|
+
document loaders) run host-side.
|
|
253
|
+
- **Extensions load from outside the workspace.** `extensions_dir` defaults to
|
|
254
|
+
`~/.agentic-dev-loop/extensions`; at load time any extension or tool module
|
|
255
|
+
whose file resolves under the workspace is refused *before import*, naming
|
|
256
|
+
the file.
|
|
257
|
+
- **The engine installs outside the workspace.** Install with pipx so the venv
|
|
258
|
+
is unmounted from the sandbox; a startup self-check refuses to run if
|
|
259
|
+
`agentic_dev_loop` resolves under the workspace (an editable install bypasses
|
|
260
|
+
everything).
|
|
261
|
+
- **Tooling is read-only by the loop.** At run start the pipeline file,
|
|
262
|
+
`agents_dir`, `additional_files`, `aidlc-docs/`, and `tools/` must be
|
|
263
|
+
git-clean relative to HEAD — otherwise the run refuses, naming the dirty
|
|
264
|
+
files. The `commit` action excludes those paths from its sweep, so the loop
|
|
265
|
+
never commits its own tooling.
|
|
266
|
+
- **`commit` stays host-side, deliberately.** It holds push credentials; moving
|
|
267
|
+
it into the sandbox would trade host-exec-of-trusted-code for
|
|
268
|
+
credential-in-sandbox. It runs argv-style with a scrubbed child env and
|
|
269
|
+
`-c core.hooksPath=~/.agentic-dev-loop/state/hooks` so repo hooks never
|
|
270
|
+
execute.
|
|
271
|
+
|
|
272
|
+
Net: the host executes nothing from the workspace, reads only git-clean
|
|
273
|
+
control files, and the only agent-writable host-consumed thing left is the code
|
|
274
|
+
the loop is building.
|
|
275
|
+
|
|
276
|
+
### Consumer migration notes
|
|
277
|
+
|
|
278
|
+
- **Actions** — replace a host-side `fn` registration with
|
|
279
|
+
`registry.add_sandbox_action(name, script)`. `script` is a command string
|
|
280
|
+
rendered with the step's `params:` plus `{subject}`; the exit code is the
|
|
281
|
+
verdict. `add_action` (host-side) is now engine-built-in-only and is refused
|
|
282
|
+
from extensions.
|
|
283
|
+
- **Extensions** — move workspace-resident extension modules (e.g.
|
|
284
|
+
`extensions/*.py`) into `~/.agentic-dev-loop/extensions/` (or whatever
|
|
285
|
+
`extensions_dir:` names), and reinstall the engine via pipx so it is not
|
|
286
|
+
editable/in-workspace.
|
|
287
|
+
- **Tooling** — commit the pipeline, agent defs, shared `additional_files`,
|
|
288
|
+
`aidlc-docs/`, and `tools/` before running: the run refuses dirty tooling,
|
|
289
|
+
and `commit` never commits it.
|
|
290
|
+
|
|
184
291
|
## Development environment
|
|
185
292
|
|
|
186
293
|
```bash
|
|
187
294
|
git clone git@github.com:lukedawilson/agentic-dev-loop.git
|
|
188
295
|
cd agentic-dev-loop
|
|
189
|
-
|
|
190
|
-
#
|
|
191
|
-
#
|
|
192
|
-
python -m pytest tests/ -q # fast suite
|
|
296
|
+
pipx install "agentic-dev-loop==1.0.0a9" # engine venv OUTSIDE the workspace (self-check)
|
|
297
|
+
export OPENAI_API_KEY=... # set your LLM key in the host env (no .env in the repo)
|
|
298
|
+
python -m pytest tests/ -q # fast suite (in-repo .venv is for tests only)
|
|
193
299
|
python -m pytest tests/ -q -m "not docker" # skip engine-dependent tests
|
|
194
|
-
|
|
300
|
+
agentic-dev-loop agentic-dev-loop/pipeline.yaml --dry-run # run the shipped pipeline
|
|
195
301
|
./run-demo.sh # viz demo — scripted run replay, no pipeline needed
|
|
196
302
|
```
|
|
197
303
|
|
|
198
|
-
- `setup-venv.sh`
|
|
199
|
-
|
|
304
|
+
- `setup-venv.sh` builds the throwaway `.venv` for local `pytest` runs (the
|
|
305
|
+
pipeline's `test` step provisions its own `.venv-sandbox` inside the
|
|
306
|
+
sandbox). The loop itself must run from the pipx install — never an
|
|
307
|
+
in-workspace `.venv`, which the startup self-check refuses.
|
|
200
308
|
- Docker-marked integration tests need a local daemon; CI runs them on Linux.
|
|
201
309
|
- The shipped pipeline (`agentic-dev-loop/pipeline.yaml`) **dogfoods the tool on
|
|
202
310
|
its own repo** — dev → test → review → qa → commit, with the engine's own
|
|
@@ -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.0a10"
|
|
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"
|
|
@@ -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
|
|
@@ -85,7 +114,8 @@ class StepConfig(BaseModel):
|
|
|
85
114
|
timeout: float | None = Field(default=None, gt=0) # seconds, command steps only
|
|
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
|
-
network: bool = False # per-stage egress (command +
|
|
117
|
+
network: bool = False # per-stage egress (command/agent + sandbox actions; host-side actions reject at registry time)
|
|
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
|
|
|
@@ -143,9 +173,14 @@ class StepConfig(BaseModel):
|
|
|
143
173
|
raise ValueError(
|
|
144
174
|
f"step {self.name!r}: 'setup' is only allowed on 'command' steps"
|
|
145
175
|
)
|
|
146
|
-
|
|
176
|
+
# `network` is NOT rejected here: the schema can't tell a host-side
|
|
177
|
+
# built-in action (e.g. `commit`, which must never take egress) from
|
|
178
|
+
# a sandbox-executed consumer action (which may, like a command step).
|
|
179
|
+
# That distinction is enforced once the registry is known — see
|
|
180
|
+
# `validate_action_network` (020).
|
|
181
|
+
if self.secrets and self.action is not None:
|
|
147
182
|
raise ValueError(
|
|
148
|
-
f"step {self.name!r}: '
|
|
183
|
+
f"step {self.name!r}: 'secrets' is only allowed on 'agent' or "
|
|
149
184
|
"'command' steps"
|
|
150
185
|
)
|
|
151
186
|
return self
|
|
@@ -166,14 +201,37 @@ class PipelineConfig(BaseModel):
|
|
|
166
201
|
name: str
|
|
167
202
|
extensions: list[str] = []
|
|
168
203
|
additional_files: list[str] = []
|
|
204
|
+
# Where consumer `extensions:` modules resolve (work item 020). Must live
|
|
205
|
+
# OUTSIDE the workspace — the host refuses workspace-resident extension and
|
|
206
|
+
# tool-handler modules, so agent-writable files are never imported by the
|
|
207
|
+
# host process.
|
|
208
|
+
extensions_dir: str = "~/.agentic-dev-loop/extensions"
|
|
169
209
|
env_allowlist: list[str] = []
|
|
170
210
|
llm: LlmConfig
|
|
171
211
|
isolation: IsolationConfig | None = None # None = always-on defaults
|
|
172
212
|
state_dir: str = ".pr" # default relocated to ~/.agentic-dev-loop/runs/<hash>/ at load
|
|
213
|
+
secrets: dict[str, SecretSpec] = {} # ref name -> locator; values never appear here
|
|
173
214
|
max_attempts: int = 3
|
|
174
215
|
agents_dir: str
|
|
175
216
|
steps: list[StepConfig] = Field(min_length=1)
|
|
176
217
|
|
|
218
|
+
@field_validator("secrets", mode="before")
|
|
219
|
+
@classmethod
|
|
220
|
+
def _reject_value_shaped_refs(cls, v: Any) -> Any:
|
|
221
|
+
if v is None:
|
|
222
|
+
return {}
|
|
223
|
+
if not isinstance(v, dict):
|
|
224
|
+
raise ValueError(
|
|
225
|
+
"'secrets' must be a mapping of ref name to "
|
|
226
|
+
"{from: ..., key: ...}")
|
|
227
|
+
for name, spec in v.items():
|
|
228
|
+
if not isinstance(spec, dict):
|
|
229
|
+
raise ValueError(
|
|
230
|
+
f"secret ref {name!r} must be a mapping "
|
|
231
|
+
"{from: ..., key: ...}, not a literal value — secret "
|
|
232
|
+
"values never belong in the pipeline yaml")
|
|
233
|
+
return v
|
|
234
|
+
|
|
177
235
|
@model_validator(mode="after")
|
|
178
236
|
def _check_graph(self) -> "PipelineConfig":
|
|
179
237
|
names = [s.name for s in self.steps]
|
|
@@ -191,6 +249,12 @@ class PipelineConfig(BaseModel):
|
|
|
191
249
|
raise ValueError(
|
|
192
250
|
f"step {step.name!r}: unknown route target {target!r}"
|
|
193
251
|
)
|
|
252
|
+
for step in self.steps:
|
|
253
|
+
for ref in step.secrets:
|
|
254
|
+
if ref not in self.secrets:
|
|
255
|
+
raise ValueError(
|
|
256
|
+
f"step {step.name!r}: secret ref {ref!r} is not "
|
|
257
|
+
"declared in the top-level 'secrets:' section")
|
|
194
258
|
return self
|
|
195
259
|
|
|
196
260
|
|
{agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/actions.py
RENAMED
|
@@ -186,6 +186,23 @@ def checkpoint_exclude_pathspec(state_dir: str | None, cwd: Path) -> str | None:
|
|
|
186
186
|
return f":(exclude){rel}/*.sqlite*"
|
|
187
187
|
|
|
188
188
|
|
|
189
|
+
def tooling_exclude_pathspec(tooling_paths: list[str] | None) -> list[str]:
|
|
190
|
+
"""git pathspecs excluding tooling paths from ``git add -A`` (020).
|
|
191
|
+
|
|
192
|
+
The loop must never commit its own tooling (pipeline, agent defs,
|
|
193
|
+
additional files, aidlc-docs, tools): the run-start integrity check
|
|
194
|
+
requires those paths to be clean relative to HEAD, so the auto-commit
|
|
195
|
+
must sweep everything *except* them. Empty when there is nothing to
|
|
196
|
+
exclude."""
|
|
197
|
+
specs = []
|
|
198
|
+
for rel in (tooling_paths or []):
|
|
199
|
+
rel = rel.replace(os.sep, "/")
|
|
200
|
+
if rel in ("", ".", ".."):
|
|
201
|
+
continue
|
|
202
|
+
specs.append(f":(exclude){rel}")
|
|
203
|
+
return specs
|
|
204
|
+
|
|
205
|
+
|
|
189
206
|
def commit(state: dict, params: dict) -> dict:
|
|
190
207
|
"""The pipeline's only commit point. Skipped loudly when the worktree was
|
|
191
208
|
dirty at run start, so the auto-commit can never swallow pre-existing
|
|
@@ -205,9 +222,13 @@ def commit(state: dict, params: dict) -> dict:
|
|
|
205
222
|
cwd = Path.cwd()
|
|
206
223
|
hooks = _trusted_hooks_dir()
|
|
207
224
|
add_argv = ["-c", f"core.hooksPath={hooks}", "add", "-A"]
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
225
|
+
excludes = []
|
|
226
|
+
checkpoint_exclude = checkpoint_exclude_pathspec(state.get("state_dir"), cwd)
|
|
227
|
+
if checkpoint_exclude:
|
|
228
|
+
excludes.append(checkpoint_exclude)
|
|
229
|
+
excludes.extend(tooling_exclude_pathspec(state.get("tooling_paths")))
|
|
230
|
+
if excludes:
|
|
231
|
+
add_argv += ["--", *excludes]
|
|
211
232
|
added = _git(add_argv, cwd=cwd)
|
|
212
233
|
if added.returncode != 0:
|
|
213
234
|
# The add is the step that touches the most files (a locked DB, a
|
{agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
@@ -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(
|
|
@@ -71,7 +71,8 @@ def _registry_for(cfg_path: Path) -> tuple[Registry, PipelineConfig]:
|
|
|
71
71
|
run path instead of being rebuilt."""
|
|
72
72
|
cfg = load_config(cfg_path)
|
|
73
73
|
registry = default_registry()
|
|
74
|
-
load_extensions(cfg.extensions, registry
|
|
74
|
+
load_extensions(cfg.extensions, registry,
|
|
75
|
+
extensions_dir=cfg.extensions_dir, workspace=Path.cwd())
|
|
75
76
|
return registry, cfg
|
|
76
77
|
|
|
77
78
|
|
{agentic_dev_loop-1.0.0a8 → agentic_dev_loop-1.0.0a10}/src/agentic_dev_loop/engine/dryrun.py
RENAMED
|
@@ -13,10 +13,13 @@ from __future__ import annotations
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
from agentic_dev_loop.core.config import PipelineConfig, StepConfig, load_config
|
|
16
|
+
from .hostboundary import check_orchestrator_install, check_tooling_integrity
|
|
16
17
|
from .loaders import resolve_docs
|
|
17
18
|
from .isolation import (isolation_state_dir, resolve_effective_profile,
|
|
18
19
|
resolve_image_ref)
|
|
19
|
-
from .registry import Registry, default_registry, load_extensions
|
|
20
|
+
from .registry import (Registry, default_registry, load_extensions,
|
|
21
|
+
validate_action_network)
|
|
22
|
+
from .secrets import scan_env_files
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
def _validate_agents(cfg: PipelineConfig, agents_dir: Path) -> None:
|
|
@@ -31,12 +34,18 @@ def _validate_agents(cfg: PipelineConfig, agents_dir: Path) -> None:
|
|
|
31
34
|
|
|
32
35
|
def _validate_actions(cfg: PipelineConfig, registry: Registry) -> None:
|
|
33
36
|
"""Unknown action names fail at graph build time; catch them here so a
|
|
34
|
-
dry run is a true load pass.
|
|
37
|
+
dry run is a true load pass. Host-side built-in actions cannot take
|
|
38
|
+
``network`` (020) — deferred here because the config schema can't
|
|
39
|
+
distinguish host-side from sandbox actions."""
|
|
40
|
+
known = set(registry.actions) | set(registry.sandbox_actions)
|
|
35
41
|
for step in cfg.steps:
|
|
36
|
-
if step.action is
|
|
42
|
+
if step.action is None:
|
|
43
|
+
continue
|
|
44
|
+
if step.action not in known:
|
|
37
45
|
raise ValueError(
|
|
38
46
|
f"step {step.name!r}: unknown action {step.action!r} "
|
|
39
|
-
f"(registered: {sorted(
|
|
47
|
+
f"(registered: {sorted(known)})")
|
|
48
|
+
validate_action_network(step.name, step.action, step.network, registry)
|
|
40
49
|
|
|
41
50
|
|
|
42
51
|
def _resolved_image(cfg: PipelineConfig) -> tuple[str, str, float]:
|
|
@@ -113,10 +122,15 @@ def dry_run(cfg_path, args, registry: Registry | None = None) -> int:
|
|
|
113
122
|
cfg_path = Path(cfg_path)
|
|
114
123
|
cfg = load_config(cfg_path)
|
|
115
124
|
base = cfg_path.resolve().parent
|
|
125
|
+
check_orchestrator_install(Path.cwd())
|
|
126
|
+
scan_env_files(Path.cwd())
|
|
127
|
+
check_tooling_integrity(cfg, cfg_path, base, Path.cwd())
|
|
116
128
|
|
|
117
129
|
if registry is None:
|
|
118
130
|
registry = default_registry()
|
|
119
|
-
load_extensions(cfg.extensions, registry
|
|
131
|
+
load_extensions(cfg.extensions, registry,
|
|
132
|
+
extensions_dir=cfg.extensions_dir,
|
|
133
|
+
workspace=Path.cwd())
|
|
120
134
|
|
|
121
135
|
_validate_agents(cfg, base / cfg.agents_dir)
|
|
122
136
|
_validate_actions(cfg, registry)
|