agentic-dev-loop 0.11.0__tar.gz → 0.12.0__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.
Files changed (140) hide show
  1. {agentic_dev_loop-0.11.0/src/agentic_dev_loop.egg-info → agentic_dev_loop-0.12.0}/PKG-INFO +85 -9
  2. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/README.md +84 -8
  3. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/pyproject.toml +1 -1
  4. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/core/config.py +47 -5
  5. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/cli.py +68 -1
  6. agentic_dev_loop-0.12.0/src/agentic_dev_loop/engine/copilot_auth.py +224 -0
  7. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/llm.py +23 -1
  8. agentic_dev_loop-0.12.0/src/agentic_dev_loop/engine/provider_wire.py +38 -0
  9. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/secrets.py +94 -7
  10. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/stages.py +3 -1
  11. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0/src/agentic_dev_loop.egg-info}/PKG-INFO +85 -9
  12. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop.egg-info/SOURCES.txt +5 -0
  13. agentic_dev_loop-0.12.0/tests/test_055_cli.py +190 -0
  14. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_cli.py +6 -4
  15. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_config.py +65 -1
  16. agentic_dev_loop-0.12.0/tests/test_copilot_auth.py +197 -0
  17. agentic_dev_loop-0.12.0/tests/test_copilot_cli.py +90 -0
  18. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_graph.py +2 -2
  19. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_llm.py +56 -0
  20. agentic_dev_loop-0.12.0/tests/test_provider_wire.py +118 -0
  21. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_secrets.py +114 -2
  22. agentic_dev_loop-0.11.0/tests/test_055_cli.py +0 -23
  23. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/LICENSE +0 -0
  24. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/setup.cfg +0 -0
  25. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/__init__.py +0 -0
  26. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/core/__init__.py +0 -0
  27. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/core/paths.py +0 -0
  28. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/core/profiles/__init__.py +0 -0
  29. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/core/profiles/data/dotnet-node.yaml +0 -0
  30. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/core/profiles/data/python.yaml +0 -0
  31. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/core/results.py +0 -0
  32. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/core/verdict.py +0 -0
  33. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/__init__.py +0 -0
  34. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/actions.py +0 -0
  35. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
  36. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
  37. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/agents.py +0 -0
  38. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/allowlist.py +0 -0
  39. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
  40. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
  41. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
  42. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/containers.py +0 -0
  43. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/deps.py +0 -0
  44. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/dryrun.py +0 -0
  45. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
  46. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
  47. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
  48. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
  49. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/forbidden.py +0 -0
  50. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/graph.py +0 -0
  51. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
  52. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/isolation.py +0 -0
  53. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/loaders.py +0 -0
  54. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/mounts.py +0 -0
  55. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/__init__.py +0 -0
  56. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/checks.py +0 -0
  57. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/doctor.py +0 -0
  58. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/init.py +0 -0
  59. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/recipes/dotnet-node.yaml +0 -0
  60. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/recipes/python.yaml +0 -0
  61. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/recipes.py +0 -0
  62. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/templates/dev.agent.md +0 -0
  63. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/templates/env.example +0 -0
  64. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/templates/qa.agent.md +0 -0
  65. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/templates/review.agent.md +0 -0
  66. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/templates/trigger-agentic-dev-loop.sh +0 -0
  67. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/templates.py +0 -0
  68. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/onboarding/upgrade.py +0 -0
  69. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/outputs.py +0 -0
  70. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/presets.py +0 -0
  71. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/proc.py +0 -0
  72. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/registry.py +0 -0
  73. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
  74. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
  75. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
  76. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/engine/sync_back.py +0 -0
  77. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/py.typed +0 -0
  78. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/viz/__init__.py +0 -0
  79. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/viz/__main__.py +0 -0
  80. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/viz/layout.py +0 -0
  81. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/viz/page.py +0 -0
  82. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop/viz/server.py +0 -0
  83. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
  84. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
  85. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
  86. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
  87. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_029_merge_on_conflict.py +0 -0
  88. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_033_brief_step.py +0 -0
  89. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_035_finalize_nudge.py +0 -0
  90. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
  91. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_041_brief_coverage.py +0 -0
  92. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_046_brief_prompt.py +0 -0
  93. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_055_doctor.py +0 -0
  94. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_055_init.py +0 -0
  95. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_055_profile_loading.py +0 -0
  96. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_055_profiles.py +0 -0
  97. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_055_recipes.py +0 -0
  98. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_055_schema_lock.py +0 -0
  99. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_055_templates.py +0 -0
  100. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_055_upgrade.py +0 -0
  101. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_actions.py +0 -0
  102. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_agent_error_guide.py +0 -0
  103. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_agents.py +0 -0
  104. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_aidlc.py +0 -0
  105. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_build_viz_page.py +0 -0
  106. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_containers.py +0 -0
  107. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_deps.py +0 -0
  108. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_dry_run.py +0 -0
  109. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_e2e_fixtures.py +0 -0
  110. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_egress_proxy.py +0 -0
  111. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_hostboundary.py +0 -0
  112. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_hostboundary_integration.py +0 -0
  113. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_isolation.py +0 -0
  114. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_isolation_integration.py +0 -0
  115. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_isolation_pbt.py +0 -0
  116. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_loaders.py +0 -0
  117. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_module_contract.py +0 -0
  118. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_outputs.py +0 -0
  119. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_outputs_pbt.py +0 -0
  120. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_proc.py +0 -0
  121. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_publish.py +0 -0
  122. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_qa_prompt.py +0 -0
  123. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_registry.py +0 -0
  124. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_repo_ascii.py +0 -0
  125. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_results.py +0 -0
  126. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_sdk_compat.py +0 -0
  127. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_secrets_integration.py +0 -0
  128. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_secrets_pipeline.py +0 -0
  129. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_source_resolution.py +0 -0
  130. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_stages.py +0 -0
  131. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_step_workspace.py +0 -0
  132. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_sync_back.py +0 -0
  133. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_trigger_script.py +0 -0
  134. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_unit5_config.py +0 -0
  135. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_unit5_pbt.py +0 -0
  136. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_unit5_pure.py +0 -0
  137. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_verdict.py +0 -0
  138. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_viz.py +0 -0
  139. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_viz_demo.py +0 -0
  140. {agentic_dev_loop-0.11.0 → agentic_dev_loop-0.12.0}/tests/test_viz_layout.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentic-dev-loop
3
- Version: 0.11.0
3
+ Version: 0.12.0
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
@@ -51,13 +51,7 @@ trusted actions - and `agentic-dev-loop` runs it end to end: agent and command s
51
51
  kernel-level sandboxing via Docker, verdicts route the graph, every boundary is
52
52
  checkpointed, and nothing gets committed until every gate passes.
53
53
 
54
- ```mermaid
55
- flowchart LR
56
- dev["agent: dev"] --> test{{"test gate"}} --> review["agent: review"] --> qa["agent: qa"] --> commit["action: commit"]
57
- test -- fail --> dev
58
- review -- fail --> dev
59
- qa -- fail --> dev
60
- ```
54
+ ![The live graph view - pipeline graph with fail loopbacks, summary tiles, per-stage consoles, and event log](https://raw.githubusercontent.com/lukedawilson/agentic-dev-loop/main/docs/viz-pipeline.png)
61
55
 
62
56
  - **A tool, not a wiring project.** One CLI entry point, opinionated defaults,
63
57
  fixed safety policies. You write the pipeline and the agent definitions -
@@ -175,7 +169,13 @@ the next startup retries it. Pass
175
169
 
176
170
  **What a run looks like** (the shipped self-loop example):
177
171
 
178
- ![The live graph view - pipeline stages with verdict badges, retry cycles, and per-stage consoles](docs/viz-screenshot.png)
172
+ ```mermaid
173
+ flowchart LR
174
+ dev["agent: dev"] --> test{{"test gate"}} --> review["agent: review"] --> qa["agent: qa"] --> commit["action: commit"]
175
+ test -- fail --> dev
176
+ review -- fail --> dev
177
+ qa -- fail --> dev
178
+ ```
179
179
 
180
180
  1. **dev** - the dev agent works with full context (loaded docs + shared files).
181
181
  Its edits are untracked; only declared `produces:` files are hygiene-tracked.
@@ -391,6 +391,12 @@ steps:
391
391
  engine-written output - captured step output, retry notes, artifacts, and
392
392
  `state_dir` files. (Hygiene: the declaring step's agent has already seen
393
393
  the value in its own env.)
394
+ - **File provider and LLM keys.** `from: file` reads a host file (`key` is the
395
+ path, optional `json_key` selects a JSON field). The `llm:` block accepts
396
+ exactly one of `api_key_env` or `api_key_secret`; the resolved key joins the
397
+ redaction set. See [Copilot as an endpoint](docs/setup.md#copilot-subscription-endpoint-058)
398
+ for the GitHub Copilot subscription profile (`copilot-login` device flow,
399
+ `copilot-models` probe, and the required endpoint headers).
394
400
 
395
401
  ### Per-stage networking
396
402
 
@@ -462,6 +468,76 @@ lines, the sandbox's new lines are appended in order, both sides converge, and
462
468
  the run continues with `[isolation] merged concurrent edits: ...` on the
463
469
  console.
464
470
 
471
+ ### Copilot subscription endpoint
472
+
473
+ GitHub Copilot subscriptions work as an OpenAI-compatible LLM endpoint: log in
474
+ once, then point an `llm:` block at it. The smoke harness runs the same plan
475
+ against both the Copilot endpoint and a standard OpenAI-compatible endpoint,
476
+ so the provider choice is independent of the pipeline design.
477
+
478
+ ```bash
479
+ agentic-dev-loop copilot-login # device flow; writes the token (mode 600)
480
+ agentic-dev-loop copilot-models # the models this token's tier can use
481
+ ```
482
+
483
+ ```yaml
484
+ llm:
485
+ model: openai/gpt-5.6-luna # pro default; probe-validated (see below)
486
+ base_url: https://api.githubcopilot.com
487
+ api_key_secret:
488
+ from: file
489
+ key: ~/.agentic-dev-loop/auth/copilot.json
490
+ json_key: access_token
491
+ headers:
492
+ Copilot-Integration-Id: vscode-chat
493
+ Editor-Version: vscode/1.94.0
494
+ Openai-Intent: conversation-panel
495
+ llms:
496
+ flash:
497
+ model: openai/gpt-4.1 # lite tier; no reasoning_effort (see below)
498
+ base_url: https://api.githubcopilot.com
499
+ api_key_secret:
500
+ from: file
501
+ key: ~/.agentic-dev-loop/auth/copilot.json
502
+ json_key: access_token
503
+ headers:
504
+ Copilot-Integration-Id: vscode-chat
505
+ Editor-Version: vscode/1.94.0
506
+ Openai-Intent: conversation-panel
507
+ ```
508
+
509
+ - The `openai/` prefix on model names is required: litellm otherwise routes a
510
+ bare `claude-*` name through Anthropic's wire format, which the gateway
511
+ rejects. The prefix forces the OpenAI-compatible route.
512
+ - The model pins are probe-validated against the live endpoint (2026-11-06).
513
+ `gpt-5.6-luna` routes through the Responses API (the SDK switches
514
+ automatically), accepts `xhigh`, and returns real verdicts on the review
515
+ step. `gpt-4.1` tool-calls cleanly on the chat path but hard-400s on any
516
+ `reasoning_effort`, so lite-tier steps set none.
517
+ - The brief step runs on the pro model at `medium`: no-effort `gpt-4.1`
518
+ cannot satisfy the mechanical brief-coverage gate.
519
+ - The engine forces string-encoded message content for Copilot configs: the
520
+ gateway silently drops ARRAY-encoded tool results, which would leave an
521
+ agent blind to its own terminal output.
522
+
523
+ Known limitations (probe-validated, not speculation):
524
+
525
+ - Anthropic models are unusable for agent steps on this endpoint: the claude
526
+ pins collapse to tool-less narration on review (planning-docs/059), and
527
+ claude-opus-4.8 returns two choices on the non-streamed path the engine
528
+ uses.
529
+ - `gpt-4o-mini` caps chat context at 12288 tokens (it overran on the first
530
+ smoke run).
531
+ - `gpt-4o` and `gpt-4.1` reject any `reasoning_effort` on the chat path.
532
+ - `gpt-5.x` models are Responses-API-only.
533
+ - kimi-k3 accepts only `low`/`high`/`max` effort, and its tool results were
534
+ dropped by the gateway until the string-content policy above.
535
+ - `gpt-4.1` carries a gateway "utility model" rate limit that a sustained dev
536
+ loop can trip; it resets within minutes.
537
+
538
+ See [docs/setup.md](docs/setup.md#copilot-subscription-endpoint-058) for the
539
+ token lifecycle, the probe-validated headers, and the full details.
540
+
465
541
 
466
542
  ## Authoring a loop for your own repo
467
543
 
@@ -7,13 +7,7 @@ trusted actions - and `agentic-dev-loop` runs it end to end: agent and command s
7
7
  kernel-level sandboxing via Docker, verdicts route the graph, every boundary is
8
8
  checkpointed, and nothing gets committed until every gate passes.
9
9
 
10
- ```mermaid
11
- flowchart LR
12
- dev["agent: dev"] --> test{{"test gate"}} --> review["agent: review"] --> qa["agent: qa"] --> commit["action: commit"]
13
- test -- fail --> dev
14
- review -- fail --> dev
15
- qa -- fail --> dev
16
- ```
10
+ ![The live graph view - pipeline graph with fail loopbacks, summary tiles, per-stage consoles, and event log](https://raw.githubusercontent.com/lukedawilson/agentic-dev-loop/main/docs/viz-pipeline.png)
17
11
 
18
12
  - **A tool, not a wiring project.** One CLI entry point, opinionated defaults,
19
13
  fixed safety policies. You write the pipeline and the agent definitions -
@@ -131,7 +125,13 @@ the next startup retries it. Pass
131
125
 
132
126
  **What a run looks like** (the shipped self-loop example):
133
127
 
134
- ![The live graph view - pipeline stages with verdict badges, retry cycles, and per-stage consoles](docs/viz-screenshot.png)
128
+ ```mermaid
129
+ flowchart LR
130
+ dev["agent: dev"] --> test{{"test gate"}} --> review["agent: review"] --> qa["agent: qa"] --> commit["action: commit"]
131
+ test -- fail --> dev
132
+ review -- fail --> dev
133
+ qa -- fail --> dev
134
+ ```
135
135
 
136
136
  1. **dev** - the dev agent works with full context (loaded docs + shared files).
137
137
  Its edits are untracked; only declared `produces:` files are hygiene-tracked.
@@ -347,6 +347,12 @@ steps:
347
347
  engine-written output - captured step output, retry notes, artifacts, and
348
348
  `state_dir` files. (Hygiene: the declaring step's agent has already seen
349
349
  the value in its own env.)
350
+ - **File provider and LLM keys.** `from: file` reads a host file (`key` is the
351
+ path, optional `json_key` selects a JSON field). The `llm:` block accepts
352
+ exactly one of `api_key_env` or `api_key_secret`; the resolved key joins the
353
+ redaction set. See [Copilot as an endpoint](docs/setup.md#copilot-subscription-endpoint-058)
354
+ for the GitHub Copilot subscription profile (`copilot-login` device flow,
355
+ `copilot-models` probe, and the required endpoint headers).
350
356
 
351
357
  ### Per-stage networking
352
358
 
@@ -418,6 +424,76 @@ lines, the sandbox's new lines are appended in order, both sides converge, and
418
424
  the run continues with `[isolation] merged concurrent edits: ...` on the
419
425
  console.
420
426
 
427
+ ### Copilot subscription endpoint
428
+
429
+ GitHub Copilot subscriptions work as an OpenAI-compatible LLM endpoint: log in
430
+ once, then point an `llm:` block at it. The smoke harness runs the same plan
431
+ against both the Copilot endpoint and a standard OpenAI-compatible endpoint,
432
+ so the provider choice is independent of the pipeline design.
433
+
434
+ ```bash
435
+ agentic-dev-loop copilot-login # device flow; writes the token (mode 600)
436
+ agentic-dev-loop copilot-models # the models this token's tier can use
437
+ ```
438
+
439
+ ```yaml
440
+ llm:
441
+ model: openai/gpt-5.6-luna # pro default; probe-validated (see below)
442
+ base_url: https://api.githubcopilot.com
443
+ api_key_secret:
444
+ from: file
445
+ key: ~/.agentic-dev-loop/auth/copilot.json
446
+ json_key: access_token
447
+ headers:
448
+ Copilot-Integration-Id: vscode-chat
449
+ Editor-Version: vscode/1.94.0
450
+ Openai-Intent: conversation-panel
451
+ llms:
452
+ flash:
453
+ model: openai/gpt-4.1 # lite tier; no reasoning_effort (see below)
454
+ base_url: https://api.githubcopilot.com
455
+ api_key_secret:
456
+ from: file
457
+ key: ~/.agentic-dev-loop/auth/copilot.json
458
+ json_key: access_token
459
+ headers:
460
+ Copilot-Integration-Id: vscode-chat
461
+ Editor-Version: vscode/1.94.0
462
+ Openai-Intent: conversation-panel
463
+ ```
464
+
465
+ - The `openai/` prefix on model names is required: litellm otherwise routes a
466
+ bare `claude-*` name through Anthropic's wire format, which the gateway
467
+ rejects. The prefix forces the OpenAI-compatible route.
468
+ - The model pins are probe-validated against the live endpoint (2026-11-06).
469
+ `gpt-5.6-luna` routes through the Responses API (the SDK switches
470
+ automatically), accepts `xhigh`, and returns real verdicts on the review
471
+ step. `gpt-4.1` tool-calls cleanly on the chat path but hard-400s on any
472
+ `reasoning_effort`, so lite-tier steps set none.
473
+ - The brief step runs on the pro model at `medium`: no-effort `gpt-4.1`
474
+ cannot satisfy the mechanical brief-coverage gate.
475
+ - The engine forces string-encoded message content for Copilot configs: the
476
+ gateway silently drops ARRAY-encoded tool results, which would leave an
477
+ agent blind to its own terminal output.
478
+
479
+ Known limitations (probe-validated, not speculation):
480
+
481
+ - Anthropic models are unusable for agent steps on this endpoint: the claude
482
+ pins collapse to tool-less narration on review (planning-docs/059), and
483
+ claude-opus-4.8 returns two choices on the non-streamed path the engine
484
+ uses.
485
+ - `gpt-4o-mini` caps chat context at 12288 tokens (it overran on the first
486
+ smoke run).
487
+ - `gpt-4o` and `gpt-4.1` reject any `reasoning_effort` on the chat path.
488
+ - `gpt-5.x` models are Responses-API-only.
489
+ - kimi-k3 accepts only `low`/`high`/`max` effort, and its tool results were
490
+ dropped by the gateway until the string-content policy above.
491
+ - `gpt-4.1` carries a gateway "utility model" rate limit that a sustained dev
492
+ loop can trip; it resets within minutes.
493
+
494
+ See [docs/setup.md](docs/setup.md#copilot-subscription-endpoint-058) for the
495
+ token lifecycle, the probe-validated headers, and the full details.
496
+
421
497
 
422
498
  ## Authoring a loop for your own repo
423
499
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentic-dev-loop"
7
- version = "0.11.0"
7
+ version = "0.12.0"
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"
@@ -10,12 +10,13 @@ from pathlib import Path
10
10
  from typing import Any, Literal
11
11
 
12
12
  import yaml
13
- from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
13
+ from pydantic import (BaseModel, ConfigDict, Field, field_validator,
14
+ model_serializer, model_validator)
14
15
 
15
16
  from agentic_dev_loop.core.paths import _has_wildcards, path_excluded, relative_parts
16
17
 
17
18
  TERMINALS = frozenset({"success", "failure"})
18
- KNOWN_SECRET_PROVIDERS = frozenset({"env"})
19
+ KNOWN_SECRET_PROVIDERS = frozenset({"env", "file"})
19
20
  MOUNT_MODES = frozenset({"rw", "ro"})
20
21
  # The egress preset names the engine ships (BR-9). Config validates the
21
22
  # name here; the target data lives in engine/presets.py (preset data,
@@ -183,14 +184,17 @@ def route_target(route: RouteTarget | None) -> str | None:
183
184
 
184
185
  class SecretSpec(BaseModel):
185
186
  """One ``secrets:`` ref - the locator only, never a value. ``from`` names
186
- the provider (``env`` first); ``key`` is that provider's locator (for
187
+ the provider (``env`` or ``file``); ``key`` is that provider's locator (for
187
188
  ``env``, the host environment variable to read and the step env name to
188
- inject). Unknown keys fail at load (extra="forbid")."""
189
+ inject; for ``file``, the JSON file path). ``json_key`` selects a key
190
+ inside the JSON file and is file-provider-only. Unknown keys fail at load
191
+ (extra="forbid")."""
189
192
 
190
193
  model_config = ConfigDict(extra="forbid", populate_by_name=True)
191
194
 
192
195
  from_: str = Field(alias="from")
193
196
  key: str
197
+ json_key: str | None = None
194
198
 
195
199
  @field_validator("from_")
196
200
  @classmethod
@@ -208,6 +212,28 @@ class SecretSpec(BaseModel):
208
212
  raise ValueError("secret key must be a non-empty string")
209
213
  return v
210
214
 
215
+ @field_validator("json_key")
216
+ @classmethod
217
+ def _nonempty_json_key(cls, v: str | None) -> str | None:
218
+ if v is not None and not v.strip():
219
+ raise ValueError("secret json_key must be a non-empty string")
220
+ return v
221
+
222
+ @model_validator(mode="after")
223
+ def _json_key_only_for_file(self) -> "SecretSpec":
224
+ if self.json_key is not None and self.from_ != "file":
225
+ raise ValueError(
226
+ "secret json_key is only valid with the 'file' provider")
227
+ return self
228
+
229
+ @model_serializer(mode="wrap")
230
+ def _omit_absent_json_key(self, handler):
231
+ """Keep the pre-058 env-provider dump shape byte-identical."""
232
+ data = handler(self)
233
+ if data.get("json_key") is None:
234
+ data.pop("json_key", None)
235
+ return data
236
+
211
237
 
212
238
  class IsolationConfig(BaseModel):
213
239
  """Optional `isolation:` section - configures the always-on sandbox (BR-2).
@@ -600,13 +626,29 @@ class LlmConfig(BaseModel):
600
626
 
601
627
  model: str
602
628
  base_url: str | None = None
603
- api_key_env: str
629
+ # 058 (D3): exactly one host-side key mechanism - an environment variable
630
+ # (the existing shape) or a file-backed secret spec.
631
+ api_key_env: str | None = None
632
+ api_key_secret: SecretSpec | None = None
633
+ # 058 (D2): extra HTTP headers forwarded verbatim to the SDK LLM
634
+ # (``extra_headers``). Empty = no change for every existing pipeline.
635
+ headers: dict[str, str] = Field(default_factory=dict)
604
636
  timeout: float | None = None # omitted → opinionated no-timeout default
605
637
  # 047 (D1) / 054 (D6): opt-in passthrough; unset means reasoning is off
606
638
  # (the engine forwards none and strips reasoning passback). The SDK
607
639
  # "high" default never engages because build_llm always passes the value.
608
640
  reasoning_effort: ReasoningEffort | None = None
609
641
 
642
+ @model_validator(mode="after")
643
+ def _exactly_one_api_key_mechanism(self) -> "LlmConfig":
644
+ has_env = self.api_key_env is not None
645
+ has_secret = self.api_key_secret is not None
646
+ if has_env == has_secret:
647
+ raise ValueError(
648
+ "set exactly one of 'api_key_env' or 'api_key_secret' on the "
649
+ "llm block (neither or both are invalid)")
650
+ return self
651
+
610
652
 
611
653
  class PipelineConfig(BaseModel):
612
654
  model_config = ConfigDict(extra="forbid")
@@ -10,9 +10,12 @@ import json
10
10
  import os
11
11
  import signal
12
12
  import sys
13
+ import threading
13
14
  from pathlib import Path
14
15
 
15
16
  from agentic_dev_loop.core.config import PipelineConfig, load_config
17
+ from .copilot_auth import (TOKEN_PATH, CopilotAuthError, list_copilot_models,
18
+ login, read_token)
16
19
  from .containers import containers_report
17
20
  from .isolation import isolation_state_dir, reap_orphaned_containers
18
21
  from .loaders import mark_plan_completed
@@ -46,6 +49,14 @@ def _restore_sigterm_handler(previous) -> None:
46
49
  signal.signal(signal.SIGTERM, previous)
47
50
 
48
51
 
52
+ def _wait_for_interrupt() -> None:
53
+ """Block until the user presses ctrl+c; a bounded wait keeps the loop
54
+ responsive to signals without requiring a tty."""
55
+ idle = threading.Event()
56
+ while not idle.wait(1.0):
57
+ pass
58
+
59
+
49
60
  def _version() -> str:
50
61
  """The installed distribution version - the single source of truth."""
51
62
  try:
@@ -149,6 +160,52 @@ def _print_containers_report(report) -> None:
149
160
  f"run={run} pid={pid_s}")
150
161
 
151
162
 
163
+ def _token_path(value: str | None) -> Path:
164
+ return Path(value).expanduser() if value else TOKEN_PATH.expanduser()
165
+
166
+
167
+ def _run_copilot_login(argv: list[str]) -> int:
168
+ parser = argparse.ArgumentParser(
169
+ prog="agentic-dev-loop copilot-login",
170
+ description="Run the GitHub device flow and store the Copilot OAuth "
171
+ "token (~/.agentic-dev-loop/auth/copilot.json, mode 600).")
172
+ parser.add_argument("--token-file", default=None,
173
+ help="token path (default: "
174
+ "~/.agentic-dev-loop/auth/copilot.json)")
175
+ args = parser.parse_args(argv)
176
+ path = _token_path(args.token_file)
177
+ try:
178
+ login(token_path=path)
179
+ except CopilotAuthError as e:
180
+ print(f"copilot-login: {e}", file=sys.stderr)
181
+ return 1
182
+ print(f"Copilot token written to {path}")
183
+ return 0
184
+
185
+
186
+ def _run_copilot_models(argv: list[str]) -> int:
187
+ parser = argparse.ArgumentParser(
188
+ prog="agentic-dev-loop copilot-models",
189
+ description="List the Copilot models available to this token's tier.")
190
+ parser.add_argument("--token-file", default=None,
191
+ help="token path (default: "
192
+ "~/.agentic-dev-loop/auth/copilot.json)")
193
+ parser.add_argument("--json", action="store_true",
194
+ help="emit the model list as JSON")
195
+ args = parser.parse_args(argv)
196
+ try:
197
+ models = list_copilot_models(read_token(_token_path(args.token_file)))
198
+ except CopilotAuthError as e:
199
+ print(f"copilot-models: {e}", file=sys.stderr)
200
+ return 1
201
+ if args.json:
202
+ print(json.dumps({"models": models}, sort_keys=True))
203
+ else:
204
+ for model in models:
205
+ print(model)
206
+ return 0
207
+
208
+
152
209
  def build_parser(cfg_path) -> argparse.ArgumentParser:
153
210
  """The full parser for one pipeline: core flags + loader-contributed."""
154
211
  registry, _ = _registry_for(cfg_path)
@@ -158,13 +215,17 @@ def build_parser(cfg_path) -> argparse.ArgumentParser:
158
215
  return parser
159
216
 
160
217
 
161
- def main(argv: list[str] | None = None) -> int:
218
+ def main(argv: list[str] | None = None, *, keep_alive: bool = True) -> int:
162
219
  if argv is None:
163
220
  argv = sys.argv[1:]
164
221
  else:
165
222
  argv = list(argv)
166
223
  if argv and argv[0] == "containers":
167
224
  return _run_containers(argv[1:])
225
+ if argv and argv[0] == "copilot-login":
226
+ return _run_copilot_login(argv[1:])
227
+ if argv and argv[0] == "copilot-models":
228
+ return _run_copilot_models(argv[1:])
168
229
  if argv and argv[0] == "init":
169
230
  from .onboarding.init import run_init
170
231
  return run_init(argv[1:])
@@ -207,6 +268,12 @@ def main(argv: list[str] | None = None) -> int:
207
268
  if not success:
208
269
  print("agentic-dev-loop: pipeline failed - exiting with code 1",
209
270
  file=sys.stderr)
271
+ if keep_alive and not args.no_viz:
272
+ print("Pipeline finished: press ctrl+c to exit")
273
+ try:
274
+ _wait_for_interrupt()
275
+ except KeyboardInterrupt:
276
+ pass
210
277
  return 0 if success else 1
211
278
  finally:
212
279
  _restore_sigterm_handler(previous)