agentic-dev-loop 0.11.1__tar.gz → 0.12.1__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.1/src/agentic_dev_loop.egg-info → agentic_dev_loop-0.12.1}/PKG-INFO +116 -2
  2. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/README.md +115 -1
  3. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/pyproject.toml +1 -1
  4. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/core/config.py +47 -5
  5. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/cli.py +68 -1
  6. agentic_dev_loop-0.12.1/src/agentic_dev_loop/engine/copilot_auth.py +224 -0
  7. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/llm.py +23 -1
  8. agentic_dev_loop-0.12.1/src/agentic_dev_loop/engine/provider_wire.py +38 -0
  9. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/secrets.py +94 -7
  10. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/stages.py +3 -1
  11. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1/src/agentic_dev_loop.egg-info}/PKG-INFO +116 -2
  12. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop.egg-info/SOURCES.txt +5 -0
  13. agentic_dev_loop-0.12.1/tests/test_055_cli.py +190 -0
  14. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_cli.py +6 -4
  15. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_config.py +65 -1
  16. agentic_dev_loop-0.12.1/tests/test_copilot_auth.py +197 -0
  17. agentic_dev_loop-0.12.1/tests/test_copilot_cli.py +90 -0
  18. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_graph.py +2 -2
  19. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_llm.py +56 -0
  20. agentic_dev_loop-0.12.1/tests/test_provider_wire.py +118 -0
  21. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_secrets.py +114 -2
  22. agentic_dev_loop-0.11.1/tests/test_055_cli.py +0 -23
  23. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/LICENSE +0 -0
  24. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/setup.cfg +0 -0
  25. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/__init__.py +0 -0
  26. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/core/__init__.py +0 -0
  27. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/core/paths.py +0 -0
  28. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/core/profiles/__init__.py +0 -0
  29. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/core/profiles/data/dotnet-node.yaml +0 -0
  30. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/core/profiles/data/python.yaml +0 -0
  31. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/core/results.py +0 -0
  32. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/core/verdict.py +0 -0
  33. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/__init__.py +0 -0
  34. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/actions.py +0 -0
  35. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/agent_error_guide.md +0 -0
  36. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/agent_error_guide.py +0 -0
  37. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/agents.py +0 -0
  38. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/allowlist.py +0 -0
  39. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/brief_coverage.py +0 -0
  40. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/brief_prompt.md +0 -0
  41. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/brief_prompt.py +0 -0
  42. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/containers.py +0 -0
  43. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/deps.py +0 -0
  44. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/dryrun.py +0 -0
  45. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/egress_policy.py +0 -0
  46. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/egress_proxy.py +0 -0
  47. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/extensions/__init__.py +0 -0
  48. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/extensions/aidlc.py +0 -0
  49. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/forbidden.py +0 -0
  50. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/graph.py +0 -0
  51. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/hostboundary.py +0 -0
  52. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/isolation.py +0 -0
  53. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/loaders.py +0 -0
  54. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/mounts.py +0 -0
  55. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/__init__.py +0 -0
  56. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/checks.py +0 -0
  57. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/doctor.py +0 -0
  58. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/init.py +0 -0
  59. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/recipes/dotnet-node.yaml +0 -0
  60. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/recipes/python.yaml +0 -0
  61. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/recipes.py +0 -0
  62. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/templates/dev.agent.md +0 -0
  63. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/templates/env.example +0 -0
  64. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/templates/qa.agent.md +0 -0
  65. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/templates/review.agent.md +0 -0
  66. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/templates/trigger-agentic-dev-loop.sh +0 -0
  67. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/templates.py +0 -0
  68. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/onboarding/upgrade.py +0 -0
  69. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/outputs.py +0 -0
  70. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/presets.py +0 -0
  71. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/proc.py +0 -0
  72. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/registry.py +0 -0
  73. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/seccomp/__init__.py +0 -0
  74. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/seccomp/chromium-userns.json +0 -0
  75. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/step_workspace.py +0 -0
  76. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/engine/sync_back.py +0 -0
  77. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/py.typed +0 -0
  78. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/viz/__init__.py +0 -0
  79. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/viz/__main__.py +0 -0
  80. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/viz/layout.py +0 -0
  81. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/viz/page.py +0 -0
  82. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop/viz/server.py +0 -0
  83. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop.egg-info/dependency_links.txt +0 -0
  84. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop.egg-info/entry_points.txt +0 -0
  85. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop.egg-info/requires.txt +0 -0
  86. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/src/agentic_dev_loop.egg-info/top_level.txt +0 -0
  87. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_029_merge_on_conflict.py +0 -0
  88. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_033_brief_step.py +0 -0
  89. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_035_finalize_nudge.py +0 -0
  90. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_036_banned_commands_and_lane_boundary.py +0 -0
  91. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_041_brief_coverage.py +0 -0
  92. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_046_brief_prompt.py +0 -0
  93. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_055_doctor.py +0 -0
  94. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_055_init.py +0 -0
  95. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_055_profile_loading.py +0 -0
  96. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_055_profiles.py +0 -0
  97. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_055_recipes.py +0 -0
  98. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_055_schema_lock.py +0 -0
  99. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_055_templates.py +0 -0
  100. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_055_upgrade.py +0 -0
  101. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_actions.py +0 -0
  102. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_agent_error_guide.py +0 -0
  103. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_agents.py +0 -0
  104. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_aidlc.py +0 -0
  105. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_build_viz_page.py +0 -0
  106. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_containers.py +0 -0
  107. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_deps.py +0 -0
  108. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_dry_run.py +0 -0
  109. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_e2e_fixtures.py +0 -0
  110. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_egress_proxy.py +0 -0
  111. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_hostboundary.py +0 -0
  112. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_hostboundary_integration.py +0 -0
  113. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_isolation.py +0 -0
  114. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_isolation_integration.py +0 -0
  115. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_isolation_pbt.py +0 -0
  116. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_loaders.py +0 -0
  117. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_module_contract.py +0 -0
  118. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_outputs.py +0 -0
  119. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_outputs_pbt.py +0 -0
  120. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_proc.py +0 -0
  121. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_publish.py +0 -0
  122. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_qa_prompt.py +0 -0
  123. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_registry.py +0 -0
  124. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_repo_ascii.py +0 -0
  125. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_results.py +0 -0
  126. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_sdk_compat.py +0 -0
  127. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_secrets_integration.py +0 -0
  128. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_secrets_pipeline.py +0 -0
  129. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_source_resolution.py +0 -0
  130. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_stages.py +0 -0
  131. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_step_workspace.py +0 -0
  132. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_sync_back.py +0 -0
  133. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_trigger_script.py +0 -0
  134. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_unit5_config.py +0 -0
  135. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_unit5_pbt.py +0 -0
  136. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_unit5_pure.py +0 -0
  137. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_verdict.py +0 -0
  138. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_viz.py +0 -0
  139. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/tests/test_viz_demo.py +0 -0
  140. {agentic_dev_loop-0.11.1 → agentic_dev_loop-0.12.1}/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.1
3
+ Version: 0.12.1
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
@@ -60,6 +60,10 @@ checkpointed, and nothing gets committed until every gate passes.
60
60
  parsed from files, retries are bounded, nothing is best-effort.
61
61
  - **Sandboxed by default.** The agent's shell and every command step run inside
62
62
  an always-on Docker sandbox. There is no opt-out.
63
+ - **Any OpenAI-compatible model, or your Copilot subscription.** Point
64
+ `llm.base_url` at any OpenAI-compatible endpoint with an API key, or log in
65
+ with `agentic-dev-loop copilot-login` and run on Copilot models. See
66
+ [LLM providers](#llm-providers).
63
67
 
64
68
  ---
65
69
 
@@ -94,6 +98,40 @@ Docker engine does:
94
98
  Native Windows (outside WSL2) is not supported: every Windows-specific code
95
99
  path and test was removed, and CI runs on Ubuntu only.
96
100
 
101
+ ## LLM providers
102
+
103
+ Two ways to run the same pipeline. The engine speaks OpenAI-compatible wire
104
+ format throughout, so every step, profile, and gate works identically on
105
+ either provider style:
106
+
107
+ | Provider | Setup |
108
+ | --- | --- |
109
+ | **Any OpenAI-compatible endpoint** | API key via `api_key_env` + `base_url` in the `llm:` block: DeepSeek, OpenAI, or any OpenAI-compatible API. Full reference: [pipeline.yaml reference](#pipelineyaml-reference). |
110
+ | **GitHub Copilot subscription** | `agentic-dev-loop copilot-login` once, then point the `llm:` block at `https://api.githubcopilot.com` with the file-backed token. Probe-validated pins: `openai/gpt-5.6-luna` (pro) and `openai/gpt-4.1` (lite). Full config and known limitations: [Copilot subscription endpoint](#copilot-subscription-endpoint). |
111
+
112
+ ```yaml
113
+ # OpenAI-compatible endpoint (any provider)
114
+ llm:
115
+ model: openai/deepseek-v4-pro
116
+ base_url: https://api.deepseek.com
117
+ api_key_env: OPENAI_API_KEY
118
+ ```
119
+
120
+ ```yaml
121
+ # GitHub Copilot subscription
122
+ llm:
123
+ model: openai/gpt-5.6-luna # pro; probe-validated pin
124
+ base_url: https://api.githubcopilot.com
125
+ api_key_secret:
126
+ from: file
127
+ key: ~/.agentic-dev-loop/auth/copilot.json
128
+ json_key: access_token
129
+ headers:
130
+ Copilot-Integration-Id: vscode-chat
131
+ Editor-Version: vscode/1.94.0
132
+ Openai-Intent: conversation-panel
133
+ ```
134
+
97
135
  ## Installation
98
136
 
99
137
  **Prerequisites**
@@ -102,7 +140,7 @@ path and test was removed, and CI runs on Ubuntu only.
102
140
  | --- | --- |
103
141
  | Python | ≥ 3.12 |
104
142
  | Docker engine | Engine API ≥ 1.43 (Engine 24). Any compliant engine works - Docker Desktop, OrbStack, Colima, Rancher Desktop, podman docker-compat. On Windows, run under WSL2: the repo must live on the WSL filesystem, not /mnt/c. |
105
- | LLM API key | The env var named by your pipeline's `api_key_env` (e.g. `OPENAI_API_KEY`) must be set - a missing key fails fast, naming it. |
143
+ | LLM API key | The env var named by your pipeline's `api_key_env` (e.g. `OPENAI_API_KEY`) must be set - a missing key fails fast, naming it. Alternatively, `agentic-dev-loop copilot-login` authenticates a Copilot subscription (see [LLM providers](#llm-providers)). |
106
144
 
107
145
  ```bash
108
146
  pip install "agentic-dev-loop==1.0.0a17"
@@ -391,6 +429,12 @@ steps:
391
429
  engine-written output - captured step output, retry notes, artifacts, and
392
430
  `state_dir` files. (Hygiene: the declaring step's agent has already seen
393
431
  the value in its own env.)
432
+ - **File provider and LLM keys.** `from: file` reads a host file (`key` is the
433
+ path, optional `json_key` selects a JSON field). The `llm:` block accepts
434
+ exactly one of `api_key_env` or `api_key_secret`; the resolved key joins the
435
+ redaction set. See [Copilot as an endpoint](docs/setup.md#copilot-subscription-endpoint-058)
436
+ for the GitHub Copilot subscription profile (`copilot-login` device flow,
437
+ `copilot-models` probe, and the required endpoint headers).
394
438
 
395
439
  ### Per-stage networking
396
440
 
@@ -462,6 +506,76 @@ lines, the sandbox's new lines are appended in order, both sides converge, and
462
506
  the run continues with `[isolation] merged concurrent edits: ...` on the
463
507
  console.
464
508
 
509
+ ### Copilot subscription endpoint
510
+
511
+ GitHub Copilot subscriptions work as an OpenAI-compatible LLM endpoint: log in
512
+ once, then point an `llm:` block at it. The smoke harness runs the same plan
513
+ against both the Copilot endpoint and a standard OpenAI-compatible endpoint,
514
+ so the provider choice is independent of the pipeline design.
515
+
516
+ ```bash
517
+ agentic-dev-loop copilot-login # device flow; writes the token (mode 600)
518
+ agentic-dev-loop copilot-models # the models this token's tier can use
519
+ ```
520
+
521
+ ```yaml
522
+ llm:
523
+ model: openai/gpt-5.6-luna # pro default; probe-validated (see below)
524
+ base_url: https://api.githubcopilot.com
525
+ api_key_secret:
526
+ from: file
527
+ key: ~/.agentic-dev-loop/auth/copilot.json
528
+ json_key: access_token
529
+ headers:
530
+ Copilot-Integration-Id: vscode-chat
531
+ Editor-Version: vscode/1.94.0
532
+ Openai-Intent: conversation-panel
533
+ llms:
534
+ flash:
535
+ model: openai/gpt-4.1 # lite tier; no reasoning_effort (see below)
536
+ base_url: https://api.githubcopilot.com
537
+ api_key_secret:
538
+ from: file
539
+ key: ~/.agentic-dev-loop/auth/copilot.json
540
+ json_key: access_token
541
+ headers:
542
+ Copilot-Integration-Id: vscode-chat
543
+ Editor-Version: vscode/1.94.0
544
+ Openai-Intent: conversation-panel
545
+ ```
546
+
547
+ - The `openai/` prefix on model names is required: litellm otherwise routes a
548
+ bare `claude-*` name through Anthropic's wire format, which the gateway
549
+ rejects. The prefix forces the OpenAI-compatible route.
550
+ - The model pins are probe-validated against the live endpoint (2026-11-06).
551
+ `gpt-5.6-luna` routes through the Responses API (the SDK switches
552
+ automatically), accepts `xhigh`, and returns real verdicts on the review
553
+ step. `gpt-4.1` tool-calls cleanly on the chat path but hard-400s on any
554
+ `reasoning_effort`, so lite-tier steps set none.
555
+ - The brief step runs on the pro model at `medium`: no-effort `gpt-4.1`
556
+ cannot satisfy the mechanical brief-coverage gate.
557
+ - The engine forces string-encoded message content for Copilot configs: the
558
+ gateway silently drops ARRAY-encoded tool results, which would leave an
559
+ agent blind to its own terminal output.
560
+
561
+ Known limitations (probe-validated, not speculation):
562
+
563
+ - Anthropic models are unusable for agent steps on this endpoint: the claude
564
+ pins collapse to tool-less narration on review (planning-docs/059), and
565
+ claude-opus-4.8 returns two choices on the non-streamed path the engine
566
+ uses.
567
+ - `gpt-4o-mini` caps chat context at 12288 tokens (it overran on the first
568
+ smoke run).
569
+ - `gpt-4o` and `gpt-4.1` reject any `reasoning_effort` on the chat path.
570
+ - `gpt-5.x` models are Responses-API-only.
571
+ - kimi-k3 accepts only `low`/`high`/`max` effort, and its tool results were
572
+ dropped by the gateway until the string-content policy above.
573
+ - `gpt-4.1` carries a gateway "utility model" rate limit that a sustained dev
574
+ loop can trip; it resets within minutes.
575
+
576
+ See [docs/setup.md](docs/setup.md#copilot-subscription-endpoint-058) for the
577
+ token lifecycle, the probe-validated headers, and the full details.
578
+
465
579
 
466
580
  ## Authoring a loop for your own repo
467
581
 
@@ -16,6 +16,10 @@ checkpointed, and nothing gets committed until every gate passes.
16
16
  parsed from files, retries are bounded, nothing is best-effort.
17
17
  - **Sandboxed by default.** The agent's shell and every command step run inside
18
18
  an always-on Docker sandbox. There is no opt-out.
19
+ - **Any OpenAI-compatible model, or your Copilot subscription.** Point
20
+ `llm.base_url` at any OpenAI-compatible endpoint with an API key, or log in
21
+ with `agentic-dev-loop copilot-login` and run on Copilot models. See
22
+ [LLM providers](#llm-providers).
19
23
 
20
24
  ---
21
25
 
@@ -50,6 +54,40 @@ Docker engine does:
50
54
  Native Windows (outside WSL2) is not supported: every Windows-specific code
51
55
  path and test was removed, and CI runs on Ubuntu only.
52
56
 
57
+ ## LLM providers
58
+
59
+ Two ways to run the same pipeline. The engine speaks OpenAI-compatible wire
60
+ format throughout, so every step, profile, and gate works identically on
61
+ either provider style:
62
+
63
+ | Provider | Setup |
64
+ | --- | --- |
65
+ | **Any OpenAI-compatible endpoint** | API key via `api_key_env` + `base_url` in the `llm:` block: DeepSeek, OpenAI, or any OpenAI-compatible API. Full reference: [pipeline.yaml reference](#pipelineyaml-reference). |
66
+ | **GitHub Copilot subscription** | `agentic-dev-loop copilot-login` once, then point the `llm:` block at `https://api.githubcopilot.com` with the file-backed token. Probe-validated pins: `openai/gpt-5.6-luna` (pro) and `openai/gpt-4.1` (lite). Full config and known limitations: [Copilot subscription endpoint](#copilot-subscription-endpoint). |
67
+
68
+ ```yaml
69
+ # OpenAI-compatible endpoint (any provider)
70
+ llm:
71
+ model: openai/deepseek-v4-pro
72
+ base_url: https://api.deepseek.com
73
+ api_key_env: OPENAI_API_KEY
74
+ ```
75
+
76
+ ```yaml
77
+ # GitHub Copilot subscription
78
+ llm:
79
+ model: openai/gpt-5.6-luna # pro; probe-validated pin
80
+ base_url: https://api.githubcopilot.com
81
+ api_key_secret:
82
+ from: file
83
+ key: ~/.agentic-dev-loop/auth/copilot.json
84
+ json_key: access_token
85
+ headers:
86
+ Copilot-Integration-Id: vscode-chat
87
+ Editor-Version: vscode/1.94.0
88
+ Openai-Intent: conversation-panel
89
+ ```
90
+
53
91
  ## Installation
54
92
 
55
93
  **Prerequisites**
@@ -58,7 +96,7 @@ path and test was removed, and CI runs on Ubuntu only.
58
96
  | --- | --- |
59
97
  | Python | ≥ 3.12 |
60
98
  | Docker engine | Engine API ≥ 1.43 (Engine 24). Any compliant engine works - Docker Desktop, OrbStack, Colima, Rancher Desktop, podman docker-compat. On Windows, run under WSL2: the repo must live on the WSL filesystem, not /mnt/c. |
61
- | LLM API key | The env var named by your pipeline's `api_key_env` (e.g. `OPENAI_API_KEY`) must be set - a missing key fails fast, naming it. |
99
+ | LLM API key | The env var named by your pipeline's `api_key_env` (e.g. `OPENAI_API_KEY`) must be set - a missing key fails fast, naming it. Alternatively, `agentic-dev-loop copilot-login` authenticates a Copilot subscription (see [LLM providers](#llm-providers)). |
62
100
 
63
101
  ```bash
64
102
  pip install "agentic-dev-loop==1.0.0a17"
@@ -347,6 +385,12 @@ steps:
347
385
  engine-written output - captured step output, retry notes, artifacts, and
348
386
  `state_dir` files. (Hygiene: the declaring step's agent has already seen
349
387
  the value in its own env.)
388
+ - **File provider and LLM keys.** `from: file` reads a host file (`key` is the
389
+ path, optional `json_key` selects a JSON field). The `llm:` block accepts
390
+ exactly one of `api_key_env` or `api_key_secret`; the resolved key joins the
391
+ redaction set. See [Copilot as an endpoint](docs/setup.md#copilot-subscription-endpoint-058)
392
+ for the GitHub Copilot subscription profile (`copilot-login` device flow,
393
+ `copilot-models` probe, and the required endpoint headers).
350
394
 
351
395
  ### Per-stage networking
352
396
 
@@ -418,6 +462,76 @@ lines, the sandbox's new lines are appended in order, both sides converge, and
418
462
  the run continues with `[isolation] merged concurrent edits: ...` on the
419
463
  console.
420
464
 
465
+ ### Copilot subscription endpoint
466
+
467
+ GitHub Copilot subscriptions work as an OpenAI-compatible LLM endpoint: log in
468
+ once, then point an `llm:` block at it. The smoke harness runs the same plan
469
+ against both the Copilot endpoint and a standard OpenAI-compatible endpoint,
470
+ so the provider choice is independent of the pipeline design.
471
+
472
+ ```bash
473
+ agentic-dev-loop copilot-login # device flow; writes the token (mode 600)
474
+ agentic-dev-loop copilot-models # the models this token's tier can use
475
+ ```
476
+
477
+ ```yaml
478
+ llm:
479
+ model: openai/gpt-5.6-luna # pro default; probe-validated (see below)
480
+ base_url: https://api.githubcopilot.com
481
+ api_key_secret:
482
+ from: file
483
+ key: ~/.agentic-dev-loop/auth/copilot.json
484
+ json_key: access_token
485
+ headers:
486
+ Copilot-Integration-Id: vscode-chat
487
+ Editor-Version: vscode/1.94.0
488
+ Openai-Intent: conversation-panel
489
+ llms:
490
+ flash:
491
+ model: openai/gpt-4.1 # lite tier; no reasoning_effort (see below)
492
+ base_url: https://api.githubcopilot.com
493
+ api_key_secret:
494
+ from: file
495
+ key: ~/.agentic-dev-loop/auth/copilot.json
496
+ json_key: access_token
497
+ headers:
498
+ Copilot-Integration-Id: vscode-chat
499
+ Editor-Version: vscode/1.94.0
500
+ Openai-Intent: conversation-panel
501
+ ```
502
+
503
+ - The `openai/` prefix on model names is required: litellm otherwise routes a
504
+ bare `claude-*` name through Anthropic's wire format, which the gateway
505
+ rejects. The prefix forces the OpenAI-compatible route.
506
+ - The model pins are probe-validated against the live endpoint (2026-11-06).
507
+ `gpt-5.6-luna` routes through the Responses API (the SDK switches
508
+ automatically), accepts `xhigh`, and returns real verdicts on the review
509
+ step. `gpt-4.1` tool-calls cleanly on the chat path but hard-400s on any
510
+ `reasoning_effort`, so lite-tier steps set none.
511
+ - The brief step runs on the pro model at `medium`: no-effort `gpt-4.1`
512
+ cannot satisfy the mechanical brief-coverage gate.
513
+ - The engine forces string-encoded message content for Copilot configs: the
514
+ gateway silently drops ARRAY-encoded tool results, which would leave an
515
+ agent blind to its own terminal output.
516
+
517
+ Known limitations (probe-validated, not speculation):
518
+
519
+ - Anthropic models are unusable for agent steps on this endpoint: the claude
520
+ pins collapse to tool-less narration on review (planning-docs/059), and
521
+ claude-opus-4.8 returns two choices on the non-streamed path the engine
522
+ uses.
523
+ - `gpt-4o-mini` caps chat context at 12288 tokens (it overran on the first
524
+ smoke run).
525
+ - `gpt-4o` and `gpt-4.1` reject any `reasoning_effort` on the chat path.
526
+ - `gpt-5.x` models are Responses-API-only.
527
+ - kimi-k3 accepts only `low`/`high`/`max` effort, and its tool results were
528
+ dropped by the gateway until the string-content policy above.
529
+ - `gpt-4.1` carries a gateway "utility model" rate limit that a sustained dev
530
+ loop can trip; it resets within minutes.
531
+
532
+ See [docs/setup.md](docs/setup.md#copilot-subscription-endpoint-058) for the
533
+ token lifecycle, the probe-validated headers, and the full details.
534
+
421
535
 
422
536
  ## Authoring a loop for your own repo
423
537
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentic-dev-loop"
7
- version = "0.11.1"
7
+ version = "0.12.1"
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)