agentic-dev-loop 1.0.0a40__tar.gz → 1.0.0a42__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.0a40/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a42}/PKG-INFO +11 -1
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/README.md +10 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/pyproject.toml +1 -1
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/cli.py +106 -14
- agentic_dev_loop-1.0.0a42/src/agentic_dev_loop/engine/containers.py +81 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/graph.py +8 -4
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/isolation.py +86 -33
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/stages.py +10 -2
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42/src/agentic_dev_loop.egg-info}/PKG-INFO +11 -1
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop.egg-info/SOURCES.txt +2 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_041_brief_coverage.py +28 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_cli.py +83 -0
- agentic_dev_loop-1.0.0a42/tests/test_containers.py +134 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_isolation.py +157 -1
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_isolation_integration.py +2 -1
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/LICENSE +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/setup.cfg +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/core/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/core/config.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/core/paths.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/core/results.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/core/verdict.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/actions.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/agents.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/allowlist.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/deps.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/dryrun.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/forbidden.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/llm.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/loaders.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/mounts.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/outputs.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/presets.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/proc.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/registry.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/secrets.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/py.typed +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/viz/__init__.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/viz/__main__.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/viz/server.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_028_declared_caches.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_029_merge_on_conflict.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_032_stop_when_green.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_033_brief_step.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_035_finalize_nudge.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_actions.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_agent_error_guide.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_agents.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_aidlc.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_config.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_deps.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_dry_run.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_e2e_fixtures.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_egress_proxy.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_graph.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_hostboundary.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_hostboundary_integration.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_isolation_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_llm.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_loaders.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_module_contract.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_outputs.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_outputs_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_proc.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_publish.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_qa_prompt.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_registry.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_repo_ascii.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_results.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_sdk_compat.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_secrets.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_secrets_integration.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_secrets_pipeline.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_source_resolution.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_stages.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_step_workspace.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_sync_back.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_trigger_script.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_unit5_config.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_unit5_pbt.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_unit5_pure.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_verdict.py +0 -0
- {agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/tests/test_viz.py +0 -0
{agentic_dev_loop-1.0.0a40/src/agentic_dev_loop.egg-info → agentic_dev_loop-1.0.0a42}/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.0a42
|
|
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
|
|
@@ -160,6 +160,16 @@ agentic-dev-loop pipeline.yaml --resume <thread-id> --max-attempts 5
|
|
|
160
160
|
|
|
161
161
|
Exit code is `0` on pipeline success, `1` otherwise.
|
|
162
162
|
|
|
163
|
+
`agentic-dev-loop containers` inspects the engine's sandbox containers and
|
|
164
|
+
networks from the host: the default list is read-only and resolves each
|
|
165
|
+
engine-owned object to its owning run and a `live`, `orphaned`,
|
|
166
|
+
`stale-entry`, or `unregistered` verdict; `containers --reap` removes only
|
|
167
|
+
registry-owned orphans whose owner pid is dead (never unregistered names).
|
|
168
|
+
A SIGTERM to a running pipeline still closes the sandbox (the graph's
|
|
169
|
+
`finally` runs); SIGKILL is unhandleable, so its containers are reclaimed by
|
|
170
|
+
the startup reaper on the next run or by `containers --reap`. Pass
|
|
171
|
+
`--state-dir` to point at a custom `isolation.state_dir`.
|
|
172
|
+
|
|
163
173
|
**What a run looks like** (the shipped self-loop example):
|
|
164
174
|
|
|
165
175
|

|
|
@@ -118,6 +118,16 @@ agentic-dev-loop pipeline.yaml --resume <thread-id> --max-attempts 5
|
|
|
118
118
|
|
|
119
119
|
Exit code is `0` on pipeline success, `1` otherwise.
|
|
120
120
|
|
|
121
|
+
`agentic-dev-loop containers` inspects the engine's sandbox containers and
|
|
122
|
+
networks from the host: the default list is read-only and resolves each
|
|
123
|
+
engine-owned object to its owning run and a `live`, `orphaned`,
|
|
124
|
+
`stale-entry`, or `unregistered` verdict; `containers --reap` removes only
|
|
125
|
+
registry-owned orphans whose owner pid is dead (never unregistered names).
|
|
126
|
+
A SIGTERM to a running pipeline still closes the sandbox (the graph's
|
|
127
|
+
`finally` runs); SIGKILL is unhandleable, so its containers are reclaimed by
|
|
128
|
+
the startup reaper on the next run or by `containers --reap`. Pass
|
|
129
|
+
`--state-dir` to point at a custom `isolation.state_dir`.
|
|
130
|
+
|
|
121
131
|
**What a run looks like** (the shipped self-loop example):
|
|
122
132
|
|
|
123
133
|

|
|
@@ -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.0a42"
|
|
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"
|
|
@@ -6,16 +6,47 @@ extension is listed in that pipeline's YAML."""
|
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import importlib.metadata
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import signal
|
|
9
12
|
import sys
|
|
10
13
|
from pathlib import Path
|
|
11
14
|
|
|
12
15
|
from agentic_dev_loop.core.config import PipelineConfig, load_config
|
|
13
|
-
from .
|
|
14
|
-
from .isolation import clean_stamps, isolation_state_dir
|
|
16
|
+
from .containers import containers_report
|
|
17
|
+
from .isolation import (clean_stamps, isolation_state_dir,
|
|
18
|
+
reap_orphaned_containers)
|
|
15
19
|
from .loaders import mark_plan_completed
|
|
16
20
|
from .registry import Registry, default_registry, load_extensions
|
|
17
21
|
|
|
18
22
|
|
|
23
|
+
_sigterm_in_progress = False
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _handle_sigterm(signum, frame) -> None:
|
|
27
|
+
"""First SIGTERM raises SystemExit(143) so the graph ``finally`` runs
|
|
28
|
+
close(); a second SIGTERM while already terminating hard-exits (D4)."""
|
|
29
|
+
global _sigterm_in_progress
|
|
30
|
+
if _sigterm_in_progress:
|
|
31
|
+
os._exit(143)
|
|
32
|
+
_sigterm_in_progress = True
|
|
33
|
+
raise SystemExit(143)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _install_sigterm_handler():
|
|
37
|
+
"""Install the graceful-shutdown handler; returns the prior handler."""
|
|
38
|
+
global _sigterm_in_progress
|
|
39
|
+
_sigterm_in_progress = False
|
|
40
|
+
return signal.signal(signal.SIGTERM, _handle_sigterm)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _restore_sigterm_handler(previous) -> None:
|
|
44
|
+
"""Restore the prior SIGTERM handler and reset the in-progress flag."""
|
|
45
|
+
global _sigterm_in_progress
|
|
46
|
+
_sigterm_in_progress = False
|
|
47
|
+
signal.signal(signal.SIGTERM, previous)
|
|
48
|
+
|
|
49
|
+
|
|
19
50
|
def _version() -> str:
|
|
20
51
|
"""The installed distribution version - the single source of truth."""
|
|
21
52
|
try:
|
|
@@ -77,6 +108,53 @@ def _registry_for(cfg_path: Path) -> tuple[Registry, PipelineConfig]:
|
|
|
77
108
|
return registry, cfg
|
|
78
109
|
|
|
79
110
|
|
|
111
|
+
def _containers_parser() -> argparse.ArgumentParser:
|
|
112
|
+
parser = argparse.ArgumentParser(
|
|
113
|
+
prog="agentic-dev-loop containers",
|
|
114
|
+
description="Inspect engine sandbox containers and networks.")
|
|
115
|
+
parser.add_argument("--reap", action="store_true",
|
|
116
|
+
help="reap orphaned registry-owned containers and "
|
|
117
|
+
"networks and print their names")
|
|
118
|
+
parser.add_argument("--json", action="store_true",
|
|
119
|
+
help="emit the report or reap result as JSON")
|
|
120
|
+
parser.add_argument("--state-dir", default="~/.agentic-dev-loop/state",
|
|
121
|
+
help="isolation state dir "
|
|
122
|
+
"(default: ~/.agentic-dev-loop/state)")
|
|
123
|
+
return parser
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _run_containers(argv: list[str]) -> int:
|
|
127
|
+
args = _containers_parser().parse_args(argv)
|
|
128
|
+
state_dir = Path(args.state_dir).expanduser()
|
|
129
|
+
if args.reap:
|
|
130
|
+
names = reap_orphaned_containers(state_dir, announce=False)
|
|
131
|
+
if args.json:
|
|
132
|
+
print(json.dumps({"reaped": names}, sort_keys=True))
|
|
133
|
+
else:
|
|
134
|
+
for name in names:
|
|
135
|
+
print(name)
|
|
136
|
+
return 0
|
|
137
|
+
report = containers_report(state_dir)
|
|
138
|
+
if args.json:
|
|
139
|
+
print(json.dumps(report, sort_keys=True))
|
|
140
|
+
else:
|
|
141
|
+
_print_containers_report(report)
|
|
142
|
+
return 0
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _print_containers_report(report) -> None:
|
|
146
|
+
rows = report["containers"] + report["unregistered"]
|
|
147
|
+
if not rows:
|
|
148
|
+
print("no engine containers or networks found")
|
|
149
|
+
return
|
|
150
|
+
for item in rows:
|
|
151
|
+
run = item.get("run_id") or "-"
|
|
152
|
+
pid = item.get("pid")
|
|
153
|
+
pid_s = str(pid) if pid is not None else "-"
|
|
154
|
+
print(f"{item['verdict']:12} {item['kind']:9} {item['name']} "
|
|
155
|
+
f"run={run} pid={pid_s}")
|
|
156
|
+
|
|
157
|
+
|
|
80
158
|
def build_parser(cfg_path) -> argparse.ArgumentParser:
|
|
81
159
|
"""The full parser for one pipeline: core flags + loader-contributed."""
|
|
82
160
|
registry, _ = _registry_for(cfg_path)
|
|
@@ -87,6 +165,12 @@ def build_parser(cfg_path) -> argparse.ArgumentParser:
|
|
|
87
165
|
|
|
88
166
|
|
|
89
167
|
def main(argv: list[str] | None = None) -> int:
|
|
168
|
+
if argv is None:
|
|
169
|
+
argv = sys.argv[1:]
|
|
170
|
+
else:
|
|
171
|
+
argv = list(argv)
|
|
172
|
+
if argv and argv[0] == "containers":
|
|
173
|
+
return _run_containers(argv[1:])
|
|
90
174
|
probe = argparse.ArgumentParser(add_help=False)
|
|
91
175
|
probe.add_argument("pipeline", type=Path, nargs="?")
|
|
92
176
|
_add_version_flag(probe)
|
|
@@ -108,16 +192,24 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
108
192
|
f"{state / 'stamps'}", flush=True)
|
|
109
193
|
return 0
|
|
110
194
|
if args.dry_run:
|
|
195
|
+
from .dryrun import dry_run
|
|
111
196
|
return dry_run(args.pipeline, args, registry)
|
|
112
|
-
# The run path alone needs the heavy graph/SDK imports -
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
197
|
+
# The run path alone needs the heavy graph/SDK imports, and --dry-run
|
|
198
|
+
# needs the dryrun import chain in-branch; defer both so `containers`,
|
|
199
|
+
# --clean-stamps, --help, and --version stay side-effect free. 042 D4: a
|
|
200
|
+
# SIGTERM here raises SystemExit(143) so graph.run_pipeline's finally
|
|
201
|
+
# still closes the sandbox; a second SIGTERM hard-exits.
|
|
202
|
+
previous = _install_sigterm_handler()
|
|
203
|
+
try:
|
|
204
|
+
from .graph import run_pipeline
|
|
205
|
+
success, _attempts = run_pipeline(args.pipeline, args, registry=registry)
|
|
206
|
+
if success and args.plan is not None:
|
|
207
|
+
# Only a completed run earns the stamp - failed runs leave the plan
|
|
208
|
+
# as-is. AI-DLC runs never pass --plan, so aidlc docs stay untouched.
|
|
209
|
+
mark_plan_completed(args.plan)
|
|
210
|
+
if not success:
|
|
211
|
+
print("agentic-dev-loop: pipeline failed - exiting with code 1",
|
|
212
|
+
file=sys.stderr)
|
|
213
|
+
return 0 if success else 1
|
|
214
|
+
finally:
|
|
215
|
+
_restore_sigterm_handler(previous)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Host-side container inspection and reaping (042 D5/D6).
|
|
2
|
+
|
|
3
|
+
Reads the engine's isolation-state container registry plus the labeled
|
|
4
|
+
engine-owned daemon objects, and resolves each entry to a verdict without
|
|
5
|
+
ever removing anything whose ownership is unproven. The only mutating path is
|
|
6
|
+
``reap_orphaned_containers`` (registry-only, pid-gated), which the CLI's
|
|
7
|
+
``containers --reap`` flag invokes explicitly.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from .isolation import (CommandRunner, _default_runner, _pid_alive_matches,
|
|
16
|
+
_read_registry)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _labeled_names(runner: CommandRunner, kind: str) -> set[str]:
|
|
20
|
+
"""The engine-owned object names currently visible to the daemon."""
|
|
21
|
+
if kind == "network":
|
|
22
|
+
argv = ["docker", "network", "ls", "--filter",
|
|
23
|
+
"label=agentic-dev-loop=true", "--format", "{{.Name}}"]
|
|
24
|
+
else:
|
|
25
|
+
argv = ["docker", "ps", "-a", "--filter",
|
|
26
|
+
"label=agentic-dev-loop=true", "--format", "{{.Names}}"]
|
|
27
|
+
res = runner.run(argv, timeout=60)
|
|
28
|
+
if res.exit_code != 0:
|
|
29
|
+
return set()
|
|
30
|
+
return {line.strip() for line in res.stdout.splitlines() if line.strip()}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def containers_report(state_dir: str | Path,
|
|
34
|
+
runner: CommandRunner | None = None) -> dict[str, Any]:
|
|
35
|
+
"""Resolve registry entries and labeled-but-unregistered engine objects
|
|
36
|
+
to verdicts (042 D5). Read-only: never removes anything.
|
|
37
|
+
|
|
38
|
+
Registry entries become ``live`` (object present + owner pid alive with
|
|
39
|
+
start-time match), ``orphaned`` (object present + owner pid dead), or
|
|
40
|
+
``stale-entry`` (entry present, object gone). Labeled objects with no
|
|
41
|
+
registry entry become ``unregistered`` - ownership is unproven, so they
|
|
42
|
+
are surfaced, never auto-reaped (D6)."""
|
|
43
|
+
runner = runner or _default_runner
|
|
44
|
+
entries = _read_registry(Path(state_dir))
|
|
45
|
+
container_names = _labeled_names(runner, "container")
|
|
46
|
+
network_names = _labeled_names(runner, "network")
|
|
47
|
+
registry_names = {e.get("name") for e in entries
|
|
48
|
+
if isinstance(e.get("name"), str)}
|
|
49
|
+
containers: list[dict[str, Any]] = []
|
|
50
|
+
for entry in entries:
|
|
51
|
+
name = entry.get("name")
|
|
52
|
+
if not isinstance(name, str):
|
|
53
|
+
continue
|
|
54
|
+
kind = entry.get("kind", "container")
|
|
55
|
+
present = name in (network_names if kind == "network" else container_names)
|
|
56
|
+
if not present:
|
|
57
|
+
verdict = "stale-entry"
|
|
58
|
+
else:
|
|
59
|
+
pid = entry.get("pid")
|
|
60
|
+
if isinstance(pid, int) and _pid_alive_matches(
|
|
61
|
+
pid, entry.get("process_start")):
|
|
62
|
+
verdict = "live"
|
|
63
|
+
else:
|
|
64
|
+
verdict = "orphaned"
|
|
65
|
+
containers.append({
|
|
66
|
+
"name": name,
|
|
67
|
+
"run_id": entry.get("run_id"),
|
|
68
|
+
"kind": kind,
|
|
69
|
+
"pid": entry.get("pid") if isinstance(entry.get("pid"), int) else None,
|
|
70
|
+
"process_start": entry.get("process_start"),
|
|
71
|
+
"created_at": entry.get("created_at"),
|
|
72
|
+
"verdict": verdict,
|
|
73
|
+
})
|
|
74
|
+
unregistered: list[dict[str, Any]] = []
|
|
75
|
+
for name in sorted(container_names - registry_names):
|
|
76
|
+
unregistered.append({"name": name, "kind": "container",
|
|
77
|
+
"verdict": "unregistered"})
|
|
78
|
+
for name in sorted(network_names - registry_names):
|
|
79
|
+
unregistered.append({"name": name, "kind": "network",
|
|
80
|
+
"verdict": "unregistered"})
|
|
81
|
+
return {"containers": containers, "unregistered": unregistered}
|
{agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/graph.py
RENAMED
|
@@ -163,15 +163,19 @@ def run_pipeline(cfg_path, args, *, viz_bus=None,
|
|
|
163
163
|
# Eager startup (Q2): probe -> image -> create+start exec container ->
|
|
164
164
|
# pre-provision all declared setups, BEFORE the graph builds. Zero infra
|
|
165
165
|
# operations mid-run; the only sandbox call during steps is run(request).
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
# 042 D3: startup is close-protected - a post-create provision failure
|
|
167
|
+
# must still reach close() and reap the container it created.
|
|
168
|
+
sandbox = None
|
|
169
169
|
try:
|
|
170
|
+
sandbox = startup_sandbox(cfg, workspace=Path.cwd(),
|
|
171
|
+
refresh=getattr(args, "refresh", False),
|
|
172
|
+
state_dir=state_dir)
|
|
170
173
|
return _run_pipeline(cfg, base, registry, state_dir, max_attempts,
|
|
171
174
|
args, viz_bus, sandbox, resolved_secrets,
|
|
172
175
|
tooling_paths)
|
|
173
176
|
finally:
|
|
174
|
-
sandbox
|
|
177
|
+
if sandbox is not None:
|
|
178
|
+
sandbox.close()
|
|
175
179
|
|
|
176
180
|
|
|
177
181
|
def _run_pipeline(cfg, base, registry, state_dir, max_attempts, args, viz_bus,
|
{agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/isolation.py
RENAMED
|
@@ -1053,12 +1053,12 @@ class DockerBackend(Sandbox):
|
|
|
1053
1053
|
workspace=self._workspace, network=False,
|
|
1054
1054
|
sandbox_venv_dir=self._sandbox_venv_dir,
|
|
1055
1055
|
seccomp_profile=self._seccomp_profile)
|
|
1056
|
+
self._register_container(self._container)
|
|
1056
1057
|
res = self._runner.run(payload, timeout=60)
|
|
1057
1058
|
if res.exit_code != 0:
|
|
1058
1059
|
raise SandboxError(
|
|
1059
1060
|
f"AE-SBX-010: container run failed: "
|
|
1060
1061
|
f"{res.stderr.strip() or res.stdout.strip()}")
|
|
1061
|
-
self._register_container(self._container)
|
|
1062
1062
|
self._started = True
|
|
1063
1063
|
if not self._network_enabled:
|
|
1064
1064
|
return
|
|
@@ -1068,6 +1068,7 @@ class DockerBackend(Sandbox):
|
|
|
1068
1068
|
network_name=self._run_net,
|
|
1069
1069
|
sandbox_venv_dir=self._sandbox_venv_dir,
|
|
1070
1070
|
seccomp_profile=self._seccomp_profile)
|
|
1071
|
+
self._register_container(self._network_container)
|
|
1071
1072
|
net_res = self._runner.run(net_payload, timeout=60)
|
|
1072
1073
|
if net_res.exit_code != 0:
|
|
1073
1074
|
# reclaim the already-started network-none container - never leak it.
|
|
@@ -1076,13 +1077,13 @@ class DockerBackend(Sandbox):
|
|
|
1076
1077
|
raise SandboxError(
|
|
1077
1078
|
f"AE-SBX-013: networked container run failed: "
|
|
1078
1079
|
f"{net_res.stderr.strip() or net_res.stdout.strip()}")
|
|
1079
|
-
self._register_container(self._network_container)
|
|
1080
1080
|
self._network_started = True
|
|
1081
1081
|
|
|
1082
1082
|
def _ensure_run_network(self) -> None:
|
|
1083
1083
|
"""Create the per-run internal network once (038, D1/D6)."""
|
|
1084
1084
|
if self._network_created:
|
|
1085
1085
|
return
|
|
1086
|
+
self._register_network(self._run_net)
|
|
1086
1087
|
res = self._runner.run(
|
|
1087
1088
|
["docker", "network", "create", "--internal",
|
|
1088
1089
|
"--label", "agentic-dev-loop=true",
|
|
@@ -1093,10 +1094,12 @@ class DockerBackend(Sandbox):
|
|
|
1093
1094
|
f"AE-SBX-021: internal network create failed: "
|
|
1094
1095
|
f"{res.stderr.strip() or res.stdout.strip()}")
|
|
1095
1096
|
self._network_created = True
|
|
1096
|
-
self._register_network(self._run_net)
|
|
1097
1097
|
|
|
1098
1098
|
def _register_network(self, name: str) -> None:
|
|
1099
|
-
"""Record the per-run network for the orphan reaper (038,
|
|
1099
|
+
"""Record the per-run network for the orphan reaper (038 D6, 042 D1).
|
|
1100
|
+
Appended BEFORE the create command so any object that exists is
|
|
1101
|
+
registered; the reaper tolerates a stale entry (a dead owner whose
|
|
1102
|
+
create never happened gets a no-op rm and a pruned entry)."""
|
|
1100
1103
|
_append_registry_record(self._iso_state_dir, {
|
|
1101
1104
|
"run_id": self._run_id,
|
|
1102
1105
|
"name": name,
|
|
@@ -1117,12 +1120,12 @@ class DockerBackend(Sandbox):
|
|
|
1117
1120
|
seccomp_profile=self._seccomp_profile,
|
|
1118
1121
|
mount_set=base, export_root=self._export_root,
|
|
1119
1122
|
cache_mounts=self._cache_mounts)
|
|
1123
|
+
self._register_container(self._container)
|
|
1120
1124
|
res = self._runner.run(payload, timeout=60)
|
|
1121
1125
|
if res.exit_code != 0:
|
|
1122
1126
|
raise SandboxError(
|
|
1123
1127
|
f"AE-SBX-010: container run failed: "
|
|
1124
1128
|
f"{res.stderr.strip() or res.stdout.strip()}")
|
|
1125
|
-
self._register_container(self._container)
|
|
1126
1129
|
self._started = True
|
|
1127
1130
|
if not self._baseline:
|
|
1128
1131
|
self._baseline = _snapshot(Path(self._export_root),
|
|
@@ -1297,6 +1300,7 @@ class DockerBackend(Sandbox):
|
|
|
1297
1300
|
mount_set=mount_set, export_root=self._export_root,
|
|
1298
1301
|
cache_mounts=self._cache_mounts, egress_proxy=proxy_url_value,
|
|
1299
1302
|
name=name)
|
|
1303
|
+
self._register_container(name)
|
|
1300
1304
|
res = self._runner.run(payload, timeout=60)
|
|
1301
1305
|
if res.exit_code != 0:
|
|
1302
1306
|
if proxy_name is not None:
|
|
@@ -1304,7 +1308,6 @@ class DockerBackend(Sandbox):
|
|
|
1304
1308
|
raise SandboxError(
|
|
1305
1309
|
f"AE-SBX-010: container run failed: "
|
|
1306
1310
|
f"{res.stderr.strip() or res.stdout.strip()}")
|
|
1307
|
-
self._register_container(name)
|
|
1308
1311
|
return _ProfileContainer(name=name, mount_set=mount_set,
|
|
1309
1312
|
egress_policy=policy, proxy_name=proxy_name,
|
|
1310
1313
|
started=True)
|
|
@@ -1334,6 +1337,7 @@ class DockerBackend(Sandbox):
|
|
|
1334
1337
|
egress_proxy_host_path=str(Path(egress_proxy_module.__file__)),
|
|
1335
1338
|
egress_policy_host_path=str(Path(egress_policy_module.__file__)),
|
|
1336
1339
|
bind_host=bind_host, port=EGRESS_PROXY_PORT)
|
|
1340
|
+
self._register_container(name)
|
|
1337
1341
|
res = self._runner.run(payload, timeout=60)
|
|
1338
1342
|
if res.exit_code != 0:
|
|
1339
1343
|
raise SandboxError(
|
|
@@ -1353,7 +1357,6 @@ class DockerBackend(Sandbox):
|
|
|
1353
1357
|
f"AE-SBX-021: egress proxy failed to bind "
|
|
1354
1358
|
f"{bind_host}:{EGRESS_PROXY_PORT} after "
|
|
1355
1359
|
f"{PROXY_READY_ATTEMPTS} readiness probes")
|
|
1356
|
-
self._register_container(name)
|
|
1357
1360
|
self._proxy_containers.append(name)
|
|
1358
1361
|
|
|
1359
1362
|
def _proxy_internal_ip(self) -> str:
|
|
@@ -1521,12 +1524,14 @@ class DockerBackend(Sandbox):
|
|
|
1521
1524
|
self._runner.run(["docker", "network", "rm", self._run_net], timeout=60)
|
|
1522
1525
|
self._network_created = False
|
|
1523
1526
|
merge_egress_events(self._iso_state_dir, self._egress_events_path)
|
|
1524
|
-
self.
|
|
1527
|
+
self._verify_and_prune_registry()
|
|
1525
1528
|
|
|
1526
1529
|
def _register_container(self, name: str) -> None:
|
|
1527
|
-
"""Record one
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
+
"""Record one exec/proxy container in the host registry (030 D4, 042
|
|
1531
|
+
D1). Appended BEFORE the create command so any object that exists is
|
|
1532
|
+
registered. Best-effort: a broken registry only disables reaping, it
|
|
1533
|
+
never fails the run; the reaper tolerates a stale entry (a dead owner
|
|
1534
|
+
whose create never happened gets a no-op rm and a pruned entry)."""
|
|
1530
1535
|
_append_registry_record(self._iso_state_dir, {
|
|
1531
1536
|
"run_id": self._run_id,
|
|
1532
1537
|
"name": name,
|
|
@@ -1535,15 +1540,50 @@ class DockerBackend(Sandbox):
|
|
|
1535
1540
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
1536
1541
|
})
|
|
1537
1542
|
|
|
1538
|
-
def
|
|
1539
|
-
"""
|
|
1540
|
-
|
|
1543
|
+
def _verify_and_prune_registry(self) -> None:
|
|
1544
|
+
"""Prune this run's registry entries only after the daemon confirms
|
|
1545
|
+
the object is gone (042 D2). A surviving object (daemon down, rm
|
|
1546
|
+
failure) keeps its entry so the next startup reaper retries. Never
|
|
1547
|
+
raises."""
|
|
1541
1548
|
entries = _read_registry(self._iso_state_dir)
|
|
1542
1549
|
if not entries:
|
|
1543
1550
|
return
|
|
1544
|
-
remaining = [
|
|
1551
|
+
remaining: list[dict] = []
|
|
1552
|
+
for entry in entries:
|
|
1553
|
+
if entry.get("run_id") != self._run_id:
|
|
1554
|
+
remaining.append(entry)
|
|
1555
|
+
continue
|
|
1556
|
+
name = entry.get("name")
|
|
1557
|
+
if not isinstance(name, str):
|
|
1558
|
+
remaining.append(entry)
|
|
1559
|
+
continue
|
|
1560
|
+
if entry.get("kind") == "network":
|
|
1561
|
+
gone = self._network_gone(name)
|
|
1562
|
+
else:
|
|
1563
|
+
gone = self._container_gone(name)
|
|
1564
|
+
if not gone:
|
|
1565
|
+
remaining.append(entry)
|
|
1545
1566
|
_write_registry(self._iso_state_dir, remaining)
|
|
1546
1567
|
|
|
1568
|
+
def _container_gone(self, name: str) -> bool:
|
|
1569
|
+
"""True only when the daemon confirms the container no longer exists.
|
|
1570
|
+
A nonzero daemon exit keeps the entry."""
|
|
1571
|
+
res = self._runner.run(
|
|
1572
|
+
["docker", "ps", "-a", "--filter", f"name={name}",
|
|
1573
|
+
"--format", "{{.Names}}"], timeout=30)
|
|
1574
|
+
if res.exit_code != 0:
|
|
1575
|
+
return False
|
|
1576
|
+
return name not in res.stdout.splitlines()
|
|
1577
|
+
|
|
1578
|
+
def _network_gone(self, name: str) -> bool:
|
|
1579
|
+
"""True only when the daemon reports the network is missing. Any
|
|
1580
|
+
other inspect failure (daemon unreachable) keeps the entry."""
|
|
1581
|
+
res = self._runner.run(
|
|
1582
|
+
["docker", "network", "inspect", name], timeout=30)
|
|
1583
|
+
if res.exit_code == 0:
|
|
1584
|
+
return False
|
|
1585
|
+
return "No such network" in f"{res.stderr} {res.stdout}"
|
|
1586
|
+
|
|
1547
1587
|
# -- internal -----------------------------------------------------------
|
|
1548
1588
|
|
|
1549
1589
|
def _verify_digest(self) -> None:
|
|
@@ -1777,7 +1817,8 @@ def _pid_alive_matches(pid: int, recorded_start: str | None) -> bool:
|
|
|
1777
1817
|
|
|
1778
1818
|
|
|
1779
1819
|
def reap_orphaned_containers(state_dir: str | Path,
|
|
1780
|
-
runner: CommandRunner | None = None
|
|
1820
|
+
runner: CommandRunner | None = None,
|
|
1821
|
+
*, announce: bool = True) -> list[str]:
|
|
1781
1822
|
"""Startup orphan reaper (030/038, D4/D6): remove exec/proxy containers
|
|
1782
1823
|
and the per-run internal network whose owning CLI process is gone (crashed
|
|
1783
1824
|
before close()). Exact by construction - only engine registry names are
|
|
@@ -1786,8 +1827,9 @@ def reap_orphaned_containers(state_dir: str | Path,
|
|
|
1786
1827
|
runner = runner or _default_runner
|
|
1787
1828
|
entries = _read_registry(Path(state_dir))
|
|
1788
1829
|
if not entries:
|
|
1789
|
-
return
|
|
1830
|
+
return []
|
|
1790
1831
|
remaining: list[dict] = []
|
|
1832
|
+
reaped: list[str] = []
|
|
1791
1833
|
for entry in entries:
|
|
1792
1834
|
name = entry.get("name")
|
|
1793
1835
|
kind = entry.get("kind", "container")
|
|
@@ -1801,7 +1843,9 @@ def reap_orphaned_containers(state_dir: str | Path,
|
|
|
1801
1843
|
remaining.append(entry)
|
|
1802
1844
|
continue
|
|
1803
1845
|
runner.run(["docker", "network", "rm", name], timeout=60)
|
|
1804
|
-
|
|
1846
|
+
if announce:
|
|
1847
|
+
_announce(f"reaped orphaned sandbox network {name}")
|
|
1848
|
+
reaped.append(name)
|
|
1805
1849
|
continue
|
|
1806
1850
|
if not isinstance(name, str) or not _CONTAINER_NAME_RE.match(name):
|
|
1807
1851
|
# Defense in depth: a corrupted registry entry is never an engine
|
|
@@ -1814,8 +1858,11 @@ def reap_orphaned_containers(state_dir: str | Path,
|
|
|
1814
1858
|
remaining.append(entry)
|
|
1815
1859
|
continue
|
|
1816
1860
|
runner.run(["docker", "rm", "-f", name], timeout=60)
|
|
1817
|
-
|
|
1861
|
+
if announce:
|
|
1862
|
+
_announce(f"reaped orphaned sandbox container {name}")
|
|
1863
|
+
reaped.append(name)
|
|
1818
1864
|
_write_registry(Path(state_dir), remaining)
|
|
1865
|
+
return reaped
|
|
1819
1866
|
|
|
1820
1867
|
|
|
1821
1868
|
# ---------------------------------------------------------------------------
|
|
@@ -1961,17 +2008,23 @@ def startup_sandbox(cfg: PipelineConfig, *, workspace: Path,
|
|
|
1961
2008
|
sync_excludes=list(cfg.sync_excludes),
|
|
1962
2009
|
merge_on_conflict=list(cfg.merge_on_conflict),
|
|
1963
2010
|
dial_rewrites=dial_rewrites)
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
refresh
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
2011
|
+
try:
|
|
2012
|
+
_announce(f"preparing sandbox image {image} (first pull can take a while)")
|
|
2013
|
+
backend.ensure_image()
|
|
2014
|
+
_announce("sandbox image ready")
|
|
2015
|
+
_announce("starting execution container...")
|
|
2016
|
+
backend.create_execution_container()
|
|
2017
|
+
_announce("execution container started")
|
|
2018
|
+
if refresh is None:
|
|
2019
|
+
refresh = bool(cfg.isolation and cfg.isolation.refresh)
|
|
2020
|
+
for setup_cmd, stamp_key in _declared_setups(cfg, workspace):
|
|
2021
|
+
_announce(f"provisioning setup (stamp {stamp_key[:8]})")
|
|
2022
|
+
backend.provision(setup_cmd, stamp_key,
|
|
2023
|
+
str(iso_state_dir / "stamps"), force=refresh)
|
|
2024
|
+
_announce("sandbox ready")
|
|
2025
|
+
return backend
|
|
2026
|
+
except BaseException:
|
|
2027
|
+
# 042 D3: a post-create startup failure (e.g. a setup hook) must still
|
|
2028
|
+
# reap the exec container it created, never leak it to the reaper.
|
|
2029
|
+
backend.close()
|
|
2030
|
+
raise
|
{agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop/engine/stages.py
RENAMED
|
@@ -661,8 +661,16 @@ def build_brief_context(runtime: Runtime, step: StepConfig,
|
|
|
661
661
|
parts.append(
|
|
662
662
|
"## Coverage retry\n\n"
|
|
663
663
|
"The captured brief is missing the following required items. "
|
|
664
|
-
"Regenerate the digest so every listed item
|
|
665
|
-
"line,
|
|
664
|
+
"Regenerate the digest so every listed item occupies its own "
|
|
665
|
+
"line, exactly as listed: the item alone on the line, no "
|
|
666
|
+
"trailing colon and no content. Put the compressed content on "
|
|
667
|
+
"the following line:\n\n"
|
|
668
|
+
"- general_principles\n"
|
|
669
|
+
" Simplest thing that works; defer decisions to the last "
|
|
670
|
+
"responsible moment.\n\n"
|
|
671
|
+
"A line like `general_principles: Simplest thing that works; "
|
|
672
|
+
"...` does not satisfy coverage; the item must be the only "
|
|
673
|
+
"text on its line:\n\n"
|
|
666
674
|
+ coverage_note)
|
|
667
675
|
return "\n\n".join(parts)
|
|
668
676
|
|
{agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42/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.0a42
|
|
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
|
|
@@ -160,6 +160,16 @@ agentic-dev-loop pipeline.yaml --resume <thread-id> --max-attempts 5
|
|
|
160
160
|
|
|
161
161
|
Exit code is `0` on pipeline success, `1` otherwise.
|
|
162
162
|
|
|
163
|
+
`agentic-dev-loop containers` inspects the engine's sandbox containers and
|
|
164
|
+
networks from the host: the default list is read-only and resolves each
|
|
165
|
+
engine-owned object to its owning run and a `live`, `orphaned`,
|
|
166
|
+
`stale-entry`, or `unregistered` verdict; `containers --reap` removes only
|
|
167
|
+
registry-owned orphans whose owner pid is dead (never unregistered names).
|
|
168
|
+
A SIGTERM to a running pipeline still closes the sandbox (the graph's
|
|
169
|
+
`finally` runs); SIGKILL is unhandleable, so its containers are reclaimed by
|
|
170
|
+
the startup reaper on the next run or by `containers --reap`. Pass
|
|
171
|
+
`--state-dir` to point at a custom `isolation.state_dir`.
|
|
172
|
+
|
|
163
173
|
**What a run looks like** (the shipped self-loop example):
|
|
164
174
|
|
|
165
175
|

|
{agentic_dev_loop-1.0.0a40 → agentic_dev_loop-1.0.0a42}/src/agentic_dev_loop.egg-info/SOURCES.txt
RENAMED
|
@@ -22,6 +22,7 @@ src/agentic_dev_loop/engine/agents.py
|
|
|
22
22
|
src/agentic_dev_loop/engine/allowlist.py
|
|
23
23
|
src/agentic_dev_loop/engine/brief_coverage.py
|
|
24
24
|
src/agentic_dev_loop/engine/cli.py
|
|
25
|
+
src/agentic_dev_loop/engine/containers.py
|
|
25
26
|
src/agentic_dev_loop/engine/deps.py
|
|
26
27
|
src/agentic_dev_loop/engine/dryrun.py
|
|
27
28
|
src/agentic_dev_loop/engine/egress_policy.py
|
|
@@ -61,6 +62,7 @@ tests/test_agents.py
|
|
|
61
62
|
tests/test_aidlc.py
|
|
62
63
|
tests/test_cli.py
|
|
63
64
|
tests/test_config.py
|
|
65
|
+
tests/test_containers.py
|
|
64
66
|
tests/test_deps.py
|
|
65
67
|
tests/test_dry_run.py
|
|
66
68
|
tests/test_e2e_fixtures.py
|