agentic-dev-loop 1.0.0a15__tar.gz → 1.0.0a16__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.0a15 → agentic_dev_loop-1.0.0a16}/PKG-INFO +2 -2
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/README.md +1 -1
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/core/config.py +9 -5
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/dryrun.py +2 -1
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/isolation.py +13 -2
- agentic_dev_loop-1.0.0a16/src/agentic_dev_loop/engine/seccomp/__init__.py +72 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/PKG-INFO +2 -2
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_isolation.py +105 -0
- agentic_dev_loop-1.0.0a15/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -27
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/cli.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/graph.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/stages.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/SOURCES.txt +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_cli.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_isolation_integration.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/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.
|
|
3
|
+
Version: 1.0.0a16
|
|
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
|
|
@@ -213,7 +213,7 @@ steps:
|
|
|
213
213
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
214
214
|
| `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). |
|
|
215
215
|
| `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). |
|
|
216
|
-
| `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`. It can never disable it. |
|
|
216
|
+
| `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`, `seccomp_profile`. It can never disable it. See [browser sandbox](docs/browser-sandbox.md). |
|
|
217
217
|
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` - artifacts and checkpoints live outside the sandbox mount. |
|
|
218
218
|
| `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. |
|
|
219
219
|
| `deps_policy` | `locked` (default) or `open`. `locked` = the human-owned lockfile is the source of truth and the agent may not add dependencies; `open` = the bootstrap protocol (the agent may add exact-pinned manifest entries). See [Dependency policy](#dependency-policy-locked-by-default). |
|
|
@@ -171,7 +171,7 @@ steps:
|
|
|
171
171
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
172
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
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). |
|
|
174
|
-
| `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`. It can never disable it. |
|
|
174
|
+
| `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`, `seccomp_profile`. It can never disable it. See [browser sandbox](docs/browser-sandbox.md). |
|
|
175
175
|
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` - artifacts and checkpoints live outside the sandbox mount. |
|
|
176
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
177
|
| `deps_policy` | `locked` (default) or `open`. `locked` = the human-owned lockfile is the source of truth and the agent may not add dependencies; `open` = the bootstrap protocol (the agent may add exact-pinned manifest entries). See [Dependency policy](#dependency-policy-locked-by-default). |
|
|
@@ -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.0a16"
|
|
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"
|
|
@@ -87,11 +87,15 @@ class IsolationConfig(BaseModel):
|
|
|
87
87
|
# the workspace - validated at startup, not load (the workspace is unknown
|
|
88
88
|
# here).
|
|
89
89
|
sandbox_venv_dir: str | None = None
|
|
90
|
-
# 023: optional host path to a Docker seccomp profile JSON
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
90
|
+
# 023/025: optional host path to a Docker seccomp profile JSON, or a
|
|
91
|
+
# ``bundled:<name>`` sentinel resolved at load to a packaged profile
|
|
92
|
+
# (e.g. ``bundled:chromium-userns``). When set, the exec container runs
|
|
93
|
+
# with ``--security-opt seccomp=<path>`` in place of Docker's default
|
|
94
|
+
# profile, plus ``--cap-add SYS_CHROOT`` (the profile gates chroot(2) on
|
|
95
|
+
# exactly that capability). Used to allow Chromium's namespace sandbox
|
|
96
|
+
# (userns syscalls) under --cap-drop ALL. An unknown sentinel name fails
|
|
97
|
+
# at load naming the known bundled profiles; a raw path is validated by
|
|
98
|
+
# Docker at container-create time, not at load.
|
|
95
99
|
seccomp_profile: str | None = None
|
|
96
100
|
|
|
97
101
|
@field_validator("memory")
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/dryrun.py
RENAMED
|
@@ -16,7 +16,7 @@ from agentic_dev_loop.core.config import PipelineConfig, StepConfig, load_config
|
|
|
16
16
|
from .hostboundary import check_orchestrator_install, check_tooling_integrity
|
|
17
17
|
from .loaders import resolve_docs
|
|
18
18
|
from .isolation import (isolation_state_dir, resolve_effective_profile,
|
|
19
|
-
resolve_image_ref)
|
|
19
|
+
resolve_image_ref, resolved_seccomp_profile)
|
|
20
20
|
from .registry import (Registry, default_registry, load_extensions,
|
|
21
21
|
validate_action_network)
|
|
22
22
|
from .secrets import scan_env_files
|
|
@@ -134,6 +134,7 @@ def dry_run(cfg_path, args, registry: Registry | None = None) -> int:
|
|
|
134
134
|
|
|
135
135
|
_validate_agents(cfg, base / cfg.agents_dir)
|
|
136
136
|
_validate_actions(cfg, registry)
|
|
137
|
+
resolved_seccomp_profile(cfg) # fails loud on an unknown bundled:<name>
|
|
137
138
|
|
|
138
139
|
loader, docs, subject = resolve_docs(cfg, base, registry, args)
|
|
139
140
|
image, memory, cpus = _resolved_image(cfg)
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
@@ -37,6 +37,7 @@ from typing import Protocol
|
|
|
37
37
|
|
|
38
38
|
from agentic_dev_loop.core.config import IsolationConfig, PipelineConfig
|
|
39
39
|
from .proc import needs_shell, split_command
|
|
40
|
+
from .seccomp import resolve_seccomp_profile
|
|
40
41
|
|
|
41
42
|
# ---------------------------------------------------------------------------
|
|
42
43
|
# Constants (KD-2 / ID-4)
|
|
@@ -480,7 +481,9 @@ def docker_create_payload(*, run_id: str, image: str, profile: EffectiveProfile,
|
|
|
480
481
|
CAP_SYS_CHROOT; the profile gates chroot(2) on exactly that capability,
|
|
481
482
|
so the payload adds it back (the bounding set is otherwise empty under
|
|
482
483
|
--cap-drop ALL). Without the cap the zygote dies even with the profile
|
|
483
|
-
(023 host-side gate 2).
|
|
484
|
+
(023 host-side gate 2). The ``--cap-add SYS_CHROOT`` applies to any
|
|
485
|
+
seccomp profile by design: the bundled profile requires it, and custom
|
|
486
|
+
profiles are expected to follow the same cap-gated pattern."""
|
|
484
487
|
payload = [
|
|
485
488
|
"docker", "run", "--detach", "--rm", "--name",
|
|
486
489
|
exec_container_name(run_id, network),
|
|
@@ -956,6 +959,14 @@ def isolation_state_dir(cfg: PipelineConfig) -> Path:
|
|
|
956
959
|
return Path(iso.state_dir).expanduser()
|
|
957
960
|
|
|
958
961
|
|
|
962
|
+
def resolved_seccomp_profile(cfg: PipelineConfig) -> str | None:
|
|
963
|
+
"""The pipeline's seccomp profile with any ``bundled:<name>`` sentinel
|
|
964
|
+
expanded to a real host path (025). A raw path passes through unchanged
|
|
965
|
+
and is still validated by Docker at container-create time."""
|
|
966
|
+
return resolve_seccomp_profile(
|
|
967
|
+
cfg.isolation.seccomp_profile if cfg.isolation else None)
|
|
968
|
+
|
|
969
|
+
|
|
959
970
|
def resolve_image_ref(image: str, environ: Mapping[str, str] | None = None) -> str:
|
|
960
971
|
"""AGENTIC_DEV_LOOP_DEV_IMAGE override (tag OR digest) → else the profile
|
|
961
972
|
image (digest-pinned) → if empty, fail fast AE-SBX-005."""
|
|
@@ -976,6 +987,7 @@ def startup_sandbox(cfg: PipelineConfig, *, workspace: Path,
|
|
|
976
987
|
refresh: bool | None = None) -> Sandbox:
|
|
977
988
|
"""Eager startup sequence (Q2): probe → image → backend → ensure_image →
|
|
978
989
|
create+start → pre-provision every declared setup (stamp-cached)."""
|
|
990
|
+
seccomp_profile = resolved_seccomp_profile(cfg)
|
|
979
991
|
probe_docker(runner)
|
|
980
992
|
profile = resolve_effective_profile(cfg.isolation)
|
|
981
993
|
image = resolve_image_ref(profile.image)
|
|
@@ -983,7 +995,6 @@ def startup_sandbox(cfg: PipelineConfig, *, workspace: Path,
|
|
|
983
995
|
needs_network = any(step.network for step in cfg.steps)
|
|
984
996
|
sandbox_venv_dir = resolve_sandbox_venv_dir(
|
|
985
997
|
cfg.isolation.sandbox_venv_dir if cfg.isolation else None, workspace)
|
|
986
|
-
seccomp_profile = cfg.isolation.seccomp_profile if cfg.isolation else None
|
|
987
998
|
backend = DockerBackend(
|
|
988
999
|
image=image, workspace=str(workspace), iso_state_dir=str(iso_state_dir),
|
|
989
1000
|
profile=profile, runner=runner, network=needs_network,
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Bundled Docker seccomp profiles (023).
|
|
2
|
+
|
|
3
|
+
Docker's default seccomp profile blocks the user-namespace syscalls Chromium's
|
|
4
|
+
namespace sandbox needs, which makes ``chromium.launch(chromiumSandbox=True)``
|
|
5
|
+
die at the zygote under ``--cap-drop ALL``. These profiles are Docker's default
|
|
6
|
+
profile with an allowlist for those userns syscalls; pass one as
|
|
7
|
+
``isolation.seccomp_profile`` to swap it in. The ``bundled:<name>`` sentinel
|
|
8
|
+
(025) resolves a profile by name so consumers declare intent instead of
|
|
9
|
+
rediscovering the packaged path.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from importlib.resources import as_file, files
|
|
15
|
+
|
|
16
|
+
CHROMIUM_USERNS_NAME = "chromium-userns"
|
|
17
|
+
CHROMIUM_USERNS_PROFILE = "chromium-userns.json"
|
|
18
|
+
|
|
19
|
+
# ``bundled:<name>`` sentinel suffix -> packaged JSON filename. This is the
|
|
20
|
+
# single registry new bundled profiles slot into (025).
|
|
21
|
+
BUNDLED_PROFILES = {
|
|
22
|
+
CHROMIUM_USERNS_NAME: CHROMIUM_USERNS_PROFILE,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _profile_path(filename: str) -> str:
|
|
27
|
+
"""Host filesystem path to a packaged seccomp profile JSON."""
|
|
28
|
+
traversable = files("agentic_dev_loop.engine.seccomp").joinpath(filename)
|
|
29
|
+
with as_file(traversable) as path:
|
|
30
|
+
return str(path)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def bundled_profile_path(name: str) -> str:
|
|
34
|
+
"""Host filesystem path to the named bundled seccomp profile.
|
|
35
|
+
|
|
36
|
+
Raises ``ValueError`` listing every known ``bundled:<name>`` when ``name``
|
|
37
|
+
is unknown, so a bad sentinel fails loud at load instead of at
|
|
38
|
+
container-create time.
|
|
39
|
+
"""
|
|
40
|
+
filename = BUNDLED_PROFILES.get(name)
|
|
41
|
+
if filename is None:
|
|
42
|
+
known = ", ".join(f"bundled:{n}" for n in sorted(BUNDLED_PROFILES))
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"unknown bundled seccomp profile {name!r} (known: {known})")
|
|
45
|
+
return _profile_path(filename)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def chromium_userns_profile_path() -> str:
|
|
49
|
+
"""Host filesystem path to the bundled Chromium userns seccomp profile.
|
|
50
|
+
|
|
51
|
+
Docker's ``--security-opt seccomp=<path>`` takes a host path, so the JSON
|
|
52
|
+
must resolve to a real file. Wheels unpack to disk, so ``as_file`` returns
|
|
53
|
+
the installed path without copying.
|
|
54
|
+
"""
|
|
55
|
+
return bundled_profile_path(CHROMIUM_USERNS_NAME)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def resolve_seccomp_profile(value: str | None) -> str | None:
|
|
59
|
+
"""Expand a ``bundled:<name>`` sentinel to its real profile path (025).
|
|
60
|
+
|
|
61
|
+
A raw host path passes through unchanged (Docker still validates it at
|
|
62
|
+
container-create time, unchanged from 023). An unknown or empty bundled
|
|
63
|
+
name fails loud with every known ``bundled:<name>``.
|
|
64
|
+
"""
|
|
65
|
+
if value is None or not value.startswith("bundled:"):
|
|
66
|
+
return value
|
|
67
|
+
name = value[len("bundled:"):]
|
|
68
|
+
if not name:
|
|
69
|
+
known = ", ".join(f"bundled:{n}" for n in sorted(BUNDLED_PROFILES))
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"seccomp_profile 'bundled:' is missing a name (known: {known})")
|
|
72
|
+
return bundled_profile_path(name)
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentic-dev-loop
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0a16
|
|
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
|
|
@@ -213,7 +213,7 @@ steps:
|
|
|
213
213
|
| `on_pass:` / `on_fail:` | A step name, terminal `success`/`failure`, or `{goto: X, retry: true}`. No loops happen unless you write them. |
|
|
214
214
|
| `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). |
|
|
215
215
|
| `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). |
|
|
216
|
-
| `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`. It can never disable it. |
|
|
216
|
+
| `isolation:` (optional) | Configures the sandbox - image override, resource caps, `allow_git_mutations`, `seccomp_profile`. It can never disable it. See [browser sandbox](docs/browser-sandbox.md). |
|
|
217
217
|
| `state_dir` | Defaults to `~/.agentic-dev-loop/runs/<pipeline-hash>/` - artifacts and checkpoints live outside the sandbox mount. |
|
|
218
218
|
| `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. |
|
|
219
219
|
| `deps_policy` | `locked` (default) or `open`. `locked` = the human-owned lockfile is the source of truth and the agent may not add dependencies; `open` = the bootstrap protocol (the agent may add exact-pinned manifest entries). See [Dependency policy](#dependency-policy-locked-by-default). |
|
|
@@ -1097,6 +1097,54 @@ class TestStartupSandbox:
|
|
|
1097
1097
|
finally:
|
|
1098
1098
|
sandbox.close()
|
|
1099
1099
|
|
|
1100
|
+
def test_bundled_seccomp_sentinel_resolved_and_plumbed(self, tmp_path, monkeypatch):
|
|
1101
|
+
"""025: a ``bundled:chromium-userns`` sentinel is expanded to the real
|
|
1102
|
+
packaged path before the exec payload is built, so the payload sees a
|
|
1103
|
+
plain path and emits the profile plus the cap-gated SYS_CHROOT add."""
|
|
1104
|
+
from agentic_dev_loop.engine.isolation import startup_sandbox
|
|
1105
|
+
from agentic_dev_loop.engine.seccomp import chromium_userns_profile_path
|
|
1106
|
+
|
|
1107
|
+
ws = tmp_path / "ws"
|
|
1108
|
+
ws.mkdir()
|
|
1109
|
+
cfg = self._cfg(tmp_path, with_setup=False)
|
|
1110
|
+
cfg.isolation.seccomp_profile = "bundled:chromium-userns"
|
|
1111
|
+
monkeypatch.setattr("agentic_dev_loop.engine.isolation.shutil.which",
|
|
1112
|
+
lambda *a: "/usr/bin/docker")
|
|
1113
|
+
monkeypatch.setenv("AGENTIC_DEV_LOOP_DEV_IMAGE", "img:tag")
|
|
1114
|
+
runner = FakeRunner(
|
|
1115
|
+
ok("1.54"), # probe version
|
|
1116
|
+
ok("linux"), # probe ostype
|
|
1117
|
+
ok(""), # image inspect
|
|
1118
|
+
ok(""), # docker run -d --rm (exec container)
|
|
1119
|
+
)
|
|
1120
|
+
sandbox = startup_sandbox(cfg, workspace=ws, runner=runner)
|
|
1121
|
+
try:
|
|
1122
|
+
run_argv = [c for c in runner.calls if c[:2] == ["docker", "run"]][0]
|
|
1123
|
+
resolved = chromium_userns_profile_path()
|
|
1124
|
+
assert "--security-opt" in run_argv
|
|
1125
|
+
assert run_argv[run_argv.index("--security-opt") + 1] == f"seccomp={resolved}"
|
|
1126
|
+
assert "--cap-add" in run_argv
|
|
1127
|
+
assert run_argv[run_argv.index("--cap-add") + 1] == "SYS_CHROOT"
|
|
1128
|
+
finally:
|
|
1129
|
+
sandbox.close()
|
|
1130
|
+
|
|
1131
|
+
def test_unknown_bundled_seccomp_name_fails_before_docker_probe(self, tmp_path, monkeypatch):
|
|
1132
|
+
"""025: an unknown bundled:<name> fails loud at startup, before any
|
|
1133
|
+
Docker probe runs."""
|
|
1134
|
+
from agentic_dev_loop.engine.isolation import startup_sandbox
|
|
1135
|
+
|
|
1136
|
+
ws = tmp_path / "ws"
|
|
1137
|
+
ws.mkdir()
|
|
1138
|
+
cfg = self._cfg(tmp_path, with_setup=False)
|
|
1139
|
+
cfg.isolation.seccomp_profile = "bundled:unknown-name"
|
|
1140
|
+
monkeypatch.setattr("agentic_dev_loop.engine.isolation.shutil.which",
|
|
1141
|
+
lambda *a: "/usr/bin/docker")
|
|
1142
|
+
monkeypatch.setenv("AGENTIC_DEV_LOOP_DEV_IMAGE", "img:tag")
|
|
1143
|
+
runner = FakeRunner()
|
|
1144
|
+
with pytest.raises(ValueError, match="bundled:chromium-userns"):
|
|
1145
|
+
startup_sandbox(cfg, workspace=ws, runner=runner)
|
|
1146
|
+
assert runner.calls == []
|
|
1147
|
+
|
|
1100
1148
|
def test_in_workspace_sandbox_venv_dir_fails_loud(self, tmp_path, monkeypatch):
|
|
1101
1149
|
from agentic_dev_loop.engine.isolation import startup_sandbox
|
|
1102
1150
|
|
|
@@ -1155,3 +1203,60 @@ class TestBundledSeccompProfile:
|
|
|
1155
1203
|
path = Path(chromium_userns_profile_path())
|
|
1156
1204
|
assert path.is_file()
|
|
1157
1205
|
assert json.loads(path.read_text(encoding="utf-8"))["defaultAction"] == "SCMP_ACT_ERRNO"
|
|
1206
|
+
|
|
1207
|
+
|
|
1208
|
+
class TestResolveSeccompProfile:
|
|
1209
|
+
"""025: the ``bundled:<name>`` sentinel resolves to a packaged profile
|
|
1210
|
+
path; raw host paths pass through unchanged; unknown names fail loud with
|
|
1211
|
+
every known bundled name."""
|
|
1212
|
+
|
|
1213
|
+
def test_bundled_sentinel_resolves_to_real_path(self):
|
|
1214
|
+
from agentic_dev_loop.engine.seccomp import (chromium_userns_profile_path,
|
|
1215
|
+
resolve_seccomp_profile)
|
|
1216
|
+
|
|
1217
|
+
resolved = resolve_seccomp_profile("bundled:chromium-userns")
|
|
1218
|
+
assert resolved == chromium_userns_profile_path()
|
|
1219
|
+
assert Path(resolved).is_file()
|
|
1220
|
+
|
|
1221
|
+
def test_unknown_bundled_name_lists_known(self):
|
|
1222
|
+
from agentic_dev_loop.engine.seccomp import resolve_seccomp_profile
|
|
1223
|
+
|
|
1224
|
+
with pytest.raises(ValueError, match="bundled:chromium-userns"):
|
|
1225
|
+
resolve_seccomp_profile("bundled:unknown-name")
|
|
1226
|
+
|
|
1227
|
+
def test_empty_bundled_name_lists_known(self):
|
|
1228
|
+
from agentic_dev_loop.engine.seccomp import resolve_seccomp_profile
|
|
1229
|
+
|
|
1230
|
+
with pytest.raises(ValueError, match="bundled:chromium-userns"):
|
|
1231
|
+
resolve_seccomp_profile("bundled:")
|
|
1232
|
+
|
|
1233
|
+
def test_none_passes_through(self):
|
|
1234
|
+
from agentic_dev_loop.engine.seccomp import resolve_seccomp_profile
|
|
1235
|
+
|
|
1236
|
+
assert resolve_seccomp_profile(None) is None
|
|
1237
|
+
|
|
1238
|
+
def test_raw_path_passes_through(self):
|
|
1239
|
+
from agentic_dev_loop.engine.seccomp import resolve_seccomp_profile
|
|
1240
|
+
|
|
1241
|
+
assert resolve_seccomp_profile(
|
|
1242
|
+
"/host/chromium-userns.json") == "/host/chromium-userns.json"
|
|
1243
|
+
|
|
1244
|
+
def test_bundled_profile_path_unknown_name(self):
|
|
1245
|
+
from agentic_dev_loop.engine.seccomp import bundled_profile_path
|
|
1246
|
+
|
|
1247
|
+
with pytest.raises(ValueError, match="bundled:chromium-userns"):
|
|
1248
|
+
bundled_profile_path("nope")
|
|
1249
|
+
|
|
1250
|
+
def test_sentinel_resolves_into_payload(self):
|
|
1251
|
+
"""025: the resolved sentinel path reaches ``docker_create_payload``
|
|
1252
|
+
unchanged, producing the profile security-opt and the cap-add."""
|
|
1253
|
+
from agentic_dev_loop.engine.seccomp import resolve_seccomp_profile
|
|
1254
|
+
|
|
1255
|
+
resolved = resolve_seccomp_profile("bundled:chromium-userns")
|
|
1256
|
+
payload = docker_create_payload(
|
|
1257
|
+
run_id="r1", image="img",
|
|
1258
|
+
profile=EffectiveProfile(True, True, True, True, True,
|
|
1259
|
+
"img", "4g", 2.0),
|
|
1260
|
+
workspace="/ws", seccomp_profile=resolved)
|
|
1261
|
+
assert payload[payload.index("--security-opt") + 1] == f"seccomp={resolved}"
|
|
1262
|
+
assert payload[payload.index("--cap-add") + 1] == "SYS_CHROOT"
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""Bundled Docker seccomp profiles (023).
|
|
2
|
-
|
|
3
|
-
Docker's default seccomp profile blocks the user-namespace syscalls Chromium's
|
|
4
|
-
namespace sandbox needs, which makes ``chromium.launch(chromiumSandbox=True)``
|
|
5
|
-
die at the zygote under ``--cap-drop ALL``. These profiles are Docker's default
|
|
6
|
-
profile with an allowlist for those userns syscalls; pass one as
|
|
7
|
-
``isolation.seccomp_profile`` to swap it in.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
from importlib.resources import as_file, files
|
|
13
|
-
|
|
14
|
-
CHROMIUM_USERNS_PROFILE = "chromium-userns.json"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def chromium_userns_profile_path() -> str:
|
|
18
|
-
"""Host filesystem path to the bundled Chromium userns seccomp profile.
|
|
19
|
-
|
|
20
|
-
Docker's ``--security-opt seccomp=<path>`` takes a host path, so the JSON
|
|
21
|
-
must resolve to a real file. Wheels unpack to disk, so ``as_file`` returns
|
|
22
|
-
the installed path without copying.
|
|
23
|
-
"""
|
|
24
|
-
traversable = files("agentic_dev_loop.engine.seccomp").joinpath(
|
|
25
|
-
CHROMIUM_USERNS_PROFILE)
|
|
26
|
-
with as_file(traversable) as path:
|
|
27
|
-
return str(path)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/core/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/core/results.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/core/verdict.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/actions.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/agents.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/graph.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/hostboundary.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/loaders.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/outputs.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/secrets.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/viz/__init__.py
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop/viz/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/requires.txt
RENAMED
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/src/agentic_dev_loop.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agentic_dev_loop-1.0.0a15 → agentic_dev_loop-1.0.0a16}/tests/test_hostboundary_integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|