agents-gl 0.0.1__py3-none-any.whl

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 (115) hide show
  1. agents_gl-0.0.1.dist-info/METADATA +53 -0
  2. agents_gl-0.0.1.dist-info/RECORD +115 -0
  3. agents_gl-0.0.1.dist-info/WHEEL +4 -0
  4. agents_gl-0.0.1.dist-info/entry_points.txt +6 -0
  5. agents_gl-0.0.1.dist-info/licenses/LICENSE +21 -0
  6. agl/__init__.py +0 -0
  7. agl/adapters/__init__.py +0 -0
  8. agl/adapters/claude_code/__init__.py +0 -0
  9. agl/adapters/claude_code/_session.py +127 -0
  10. agl/adapters/claude_code/_tools.py +72 -0
  11. agl/adapters/claude_code/fake.py +191 -0
  12. agl/adapters/claude_code/runner.py +160 -0
  13. agl/adapters/claude_code/translate.py +241 -0
  14. agl/adapters/filesystem/__init__.py +0 -0
  15. agl/adapters/filesystem/_documents.py +35 -0
  16. agl/adapters/filesystem/memory_store.py +67 -0
  17. agl/adapters/filesystem/store.py +140 -0
  18. agl/adapters/git/__init__.py +0 -0
  19. agl/adapters/git/_changes.py +53 -0
  20. agl/adapters/git/_conflicts.py +61 -0
  21. agl/adapters/git/_merging.py +144 -0
  22. agl/adapters/git/_patches.py +69 -0
  23. agl/adapters/git/_runner.py +192 -0
  24. agl/adapters/git/_snapshots.py +178 -0
  25. agl/adapters/git/_trees.py +139 -0
  26. agl/adapters/git/_working.py +82 -0
  27. agl/adapters/git/fake.py +306 -0
  28. agl/adapters/git/history.py +125 -0
  29. agl/adapters/git/integrator.py +98 -0
  30. agl/adapters/git/workspace.py +175 -0
  31. agl/adapters/openai/__init__.py +0 -0
  32. agl/adapters/openai/_http.py +137 -0
  33. agl/adapters/openai/_session.py +220 -0
  34. agl/adapters/openai/_tools.py +153 -0
  35. agl/adapters/openai/fake.py +183 -0
  36. agl/adapters/openai/runner.py +177 -0
  37. agl/adapters/openai/translate.py +247 -0
  38. agl/adapters/rich_terminal/__init__.py +0 -0
  39. agl/adapters/rich_terminal/_display.py +81 -0
  40. agl/adapters/rich_terminal/_render.py +49 -0
  41. agl/adapters/rich_terminal/headless.py +65 -0
  42. agl/adapters/rich_terminal/queues.py +117 -0
  43. agl/adapters/rich_terminal/scripted.py +95 -0
  44. agl/adapters/rich_terminal/terminal.py +235 -0
  45. agl/adapters/routing.py +55 -0
  46. agl/adapters/shell/__init__.py +0 -0
  47. agl/adapters/shell/fake.py +25 -0
  48. agl/adapters/shell/verifier.py +102 -0
  49. agl/adapters/system_clock.py +39 -0
  50. agl/api.py +204 -0
  51. agl/cli/__init__.py +0 -0
  52. agl/cli/commands/__init__.py +19 -0
  53. agl/cli/commands/clear.py +50 -0
  54. agl/cli/commands/init.py +35 -0
  55. agl/cli/commands/resume.py +51 -0
  56. agl/cli/commands/run.py +88 -0
  57. agl/cli/commands/workflows.py +68 -0
  58. agl/cli/exit_codes.py +21 -0
  59. agl/cli/main.py +173 -0
  60. agl/config/__init__.py +0 -0
  61. agl/config/container.py +198 -0
  62. agl/config/registry.py +69 -0
  63. agl/config/schema.py +81 -0
  64. agl/config/sources.py +202 -0
  65. agl/config/toml_file.py +347 -0
  66. agl/ports/__init__.py +0 -0
  67. agl/ports/agent.py +183 -0
  68. agl/ports/clock.py +13 -0
  69. agl/ports/errors.py +73 -0
  70. agl/ports/history.py +118 -0
  71. agl/ports/home_layout.py +169 -0
  72. agl/ports/ids.py +136 -0
  73. agl/ports/integration.py +82 -0
  74. agl/ports/run.py +253 -0
  75. agl/ports/store.py +69 -0
  76. agl/ports/terminal.py +105 -0
  77. agl/ports/tree_layout.py +90 -0
  78. agl/ports/verifier.py +24 -0
  79. agl/ports/workspace.py +91 -0
  80. agl/sdk/__init__.py +99 -0
  81. agl/sdk/_declarations.py +19 -0
  82. agl/sdk/_engine/__init__.py +0 -0
  83. agl/sdk/_engine/integration.py +251 -0
  84. agl/sdk/_engine/journal.py +376 -0
  85. agl/sdk/_engine/preflight.py +127 -0
  86. agl/sdk/_engine/services.py +31 -0
  87. agl/sdk/_engine/steps.py +188 -0
  88. agl/sdk/_engine/worktrees.py +48 -0
  89. agl/sdk/errors.py +23 -0
  90. agl/sdk/params.py +235 -0
  91. agl/sdk/roles.py +187 -0
  92. agl/sdk/terminal.py +23 -0
  93. agl/sdk/testing.py +45 -0
  94. agl/sdk/tools.py +333 -0
  95. agl/sdk/workflow.py +249 -0
  96. agl/testing.py +237 -0
  97. agl/workflows/__init__.py +0 -0
  98. agl/workflows/fix/__init__.py +20 -0
  99. agl/workflows/fix/asking.py +60 -0
  100. agl/workflows/fix/findings.py +51 -0
  101. agl/workflows/fix/prompts/implement.md +112 -0
  102. agl/workflows/fix/prompts/review.md +79 -0
  103. agl/workflows/fix/questions.py +33 -0
  104. agl/workflows/fix/roles.py +24 -0
  105. agl/workflows/fix/views/__init__.py +4 -0
  106. agl/workflows/fix/views/board.py +6 -0
  107. agl/workflows/fix/views/question.py +15 -0
  108. agl/workflows/split/__init__.py +35 -0
  109. agl/workflows/split/chunks.py +63 -0
  110. agl/workflows/split/prompts/implement.md +105 -0
  111. agl/workflows/split/prompts/plan.md +120 -0
  112. agl/workflows/split/roles.py +23 -0
  113. agl/workflows/split/views/__init__.py +4 -0
  114. agl/workflows/split/views/board.py +11 -0
  115. agl/workflows/split/views/conflict.py +24 -0
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.5
2
+ Name: agents-gl
3
+ Version: 0.0.1
4
+ Summary: A framework for running AI agent workflows against code repositories.
5
+ Project-URL: Homepage, https://github.com/jashioq/AGL
6
+ Project-URL: Repository, https://github.com/jashioq/AGL
7
+ Author-email: Jan <janwylegalaandrzej@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,git,llm,orchestration,workflows
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Topic :: Software Development :: Code Generators
15
+ Classifier: Topic :: Software Development :: Version Control :: Git
16
+ Requires-Python: >=3.14
17
+ Provides-Extra: all
18
+ Requires-Dist: agl[claude,terminal]; extra == 'all'
19
+ Provides-Extra: claude
20
+ Requires-Dist: claude-agent-sdk>=0.2.140; extra == 'claude'
21
+ Provides-Extra: terminal
22
+ Requires-Dist: rich>=15.0.0; extra == 'terminal'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # AGL
26
+
27
+ AGL runs AI agent workflows against a code repository. A workflow is ordinary Python that asks for
28
+ a worktree, hands roles to agents and lands what comes back: `fix` runs one worktree sequentially
29
+ with Claude implementing and OpenAI reviewing, `split` runs N chunks concurrently.
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ uv tool install "agl[all]"
35
+ ```
36
+
37
+ The `[all]` extra pulls in the Claude adapter (`claude-agent-sdk`) and the rich terminal. Without
38
+ it you get the core, which is stdlib-only.
39
+
40
+ ## Python 3.14 or newer
41
+
42
+ This is the one thing most likely to trip you up. AGL declares `requires-python = ">=3.14"`, so a
43
+ plain `pip install agl` on an older interpreter refuses before it downloads anything, and it
44
+ reports that as not finding a version rather than as your interpreter being too old. `uv tool
45
+ install` sidesteps it by fetching a matching interpreter itself.
46
+
47
+ ## Status
48
+
49
+ Early. 0.0.1 is a name claim rather than a release, and the surface may still change.
50
+
51
+ ## Licence
52
+
53
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,115 @@
1
+ agl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ agl/api.py,sha256=SFxNudzyd_wjNxKoHizXvCbYArbUBbjrDPy_8OGC8bo,8106
3
+ agl/testing.py,sha256=4NhJy2i4fZ4bP6LO3J6bYtrsRVDVV982AyLjolannXM,7816
4
+ agl/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ agl/adapters/routing.py,sha256=mWKR6RoMyoGR5hVXhSpb6xc0AASpmLvEV6-ahHIyPNk,2310
6
+ agl/adapters/system_clock.py,sha256=yChkyzdKS-Cjir1TOhgC9asp0-YSnOoE_z1yD9kzBSQ,1489
7
+ agl/adapters/claude_code/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ agl/adapters/claude_code/_session.py,sha256=8jFXjge0B3bZD9vTtXZ7xcd4ezyKCzI6SRMqoBoIAHQ,4239
9
+ agl/adapters/claude_code/_tools.py,sha256=VN6ylJPs7kPIejlDaZc4Mt1J3DVGo-lfiXWvvai4JK0,2740
10
+ agl/adapters/claude_code/fake.py,sha256=qqteEYq37EDa_8RwnjgdX2iL4GtcVKSQi1BpKpXpwhk,6753
11
+ agl/adapters/claude_code/runner.py,sha256=WNjduqsa_xXs50_HysSgfkQmKe25l0OR7bZXUaCZ15o,6354
12
+ agl/adapters/claude_code/translate.py,sha256=lRLt43SGKuOen0ZE-hPQ1-fPH7RrVYyBubfp6yYL4bY,9138
13
+ agl/adapters/filesystem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ agl/adapters/filesystem/_documents.py,sha256=DiGjpQaIDYftnmoxaoqH7p4OGtcX_YBcZlyiCYL_CTg,1654
15
+ agl/adapters/filesystem/memory_store.py,sha256=d-O6ajH3GmsL4mS-X1viKK4dguY_ktzyNA439rofE28,2436
16
+ agl/adapters/filesystem/store.py,sha256=MooAKDsbK8VkYfnO4Fuflt0wMFtBDIWU8sXx4wipz2A,5488
17
+ agl/adapters/git/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ agl/adapters/git/_changes.py,sha256=NBIUYCzMHTFqp-ntZvsLxPtLAwKh09R0kOI7TK4Gki0,1735
19
+ agl/adapters/git/_conflicts.py,sha256=wn5UMzxc6mResM-lkKHE4lvt-_RAeIg0sIMZ1X6qZ6k,2385
20
+ agl/adapters/git/_merging.py,sha256=-7PtPk72xC19V13AmyO_oX2oWOGBLSrMSa2XQb20Tvs,5058
21
+ agl/adapters/git/_patches.py,sha256=bDQW-0pi3YKKAFX3BtP_U5ZW9di1TRmOVBsW5osRfvU,2902
22
+ agl/adapters/git/_runner.py,sha256=PxY5O6sNFz4nuBr0z-cnCyQPAqvduwk7zbxnyZ7Mg2I,7319
23
+ agl/adapters/git/_snapshots.py,sha256=vueFtdkqM5KyyRqy0wHS6OoM9PK5nBZESS50aZzvaMg,5761
24
+ agl/adapters/git/_trees.py,sha256=VNXsSrFaZeso1iQdDhr-RF4wSFUMT7x08urjqiwW8eE,4959
25
+ agl/adapters/git/_working.py,sha256=3aKCo6dOEiqtenwwos74cmUHahMaa5Ec_UaFunm4T7w,2726
26
+ agl/adapters/git/fake.py,sha256=sRrvZlYAr-I4U8CZNplqcnTAEdRoBHaH6jKKVXI1Mso,13357
27
+ agl/adapters/git/history.py,sha256=pSAG26us5UKH0elnKhwb7fYNS1S7C1jTOG5CWiKbIjQ,4710
28
+ agl/adapters/git/integrator.py,sha256=zFUkSqX3j-exHgK4LRVPbGypVB-2LsRvhbq-ocF07Bk,4052
29
+ agl/adapters/git/workspace.py,sha256=wOvcrNub5gSsm6HAx-X92FGjMbj4pfGQU4CmgMcrQPM,6565
30
+ agl/adapters/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
+ agl/adapters/openai/_http.py,sha256=nJeqkJjZ7atq2kFdNGMCTCtTZl7wLnknRRRXOGHhOEI,5278
32
+ agl/adapters/openai/_session.py,sha256=HAmv7ckAW7fdBE4_awmVy9hXbyZiYGhrIXfrJYnDtAI,7165
33
+ agl/adapters/openai/_tools.py,sha256=RQ5_-XxVpO32zi4MShwpKiz7A-rEDuPUpLmjvY7QbaI,5889
34
+ agl/adapters/openai/fake.py,sha256=xabiQ-VD2NapWIvIPGt8xGF7oOLVb6s5mDKiY9FvL_Q,6299
35
+ agl/adapters/openai/runner.py,sha256=zzrwfixRUgcjd2Jx8gT4vBmNPXuX8Get4nDWHxyUpco,5733
36
+ agl/adapters/openai/translate.py,sha256=CH-nrNrvRqYxL04RkRe2M2dyzh_mGnkQ8zG3YX0wfXE,10799
37
+ agl/adapters/rich_terminal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ agl/adapters/rich_terminal/_display.py,sha256=othKSbWO8cPUcL2OGy4y77XMKWU7C5pZG35yO8SFku0,2029
39
+ agl/adapters/rich_terminal/_render.py,sha256=-qjleot6ZA-Ev2-jHVNdRcHOy6oQSI1rQ9TsCt3d5LU,1605
40
+ agl/adapters/rich_terminal/headless.py,sha256=dMxyHZJphVuZR65uygueqIyFV1s0Jna-GNgJi2P0eeQ,2649
41
+ agl/adapters/rich_terminal/queues.py,sha256=Cj_vz2komrVE5Jx1ZdV1ikOit04GQyJcT4UZap3HLfA,4334
42
+ agl/adapters/rich_terminal/scripted.py,sha256=RABIosqrKD3MsrNZQcm3vSt0mngu997_XhK8Xj1cSA8,3418
43
+ agl/adapters/rich_terminal/terminal.py,sha256=y5US69pALhCaoSbHYG_3Ii076P25DCcQiBixYUCvHME,7828
44
+ agl/adapters/shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
+ agl/adapters/shell/fake.py,sha256=jY2L5lAW1i-h_4QQ0WjLr6YTVe9583WEyEyOWlibVQw,1066
46
+ agl/adapters/shell/verifier.py,sha256=XsC_a9T5Dcku4dYRzYoZcV_YbLWIi2cFlfmQsf-D6Yw,3928
47
+ agl/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
+ agl/cli/exit_codes.py,sha256=X-6j0vMJu2x7yQJOnxX6JhEZIAwXlK_BOe_aW_oU6wg,816
49
+ agl/cli/main.py,sha256=rhkEQRNDd-ld4ZYQLAjnBprwjwWKF76uCPSDjr2n_Lo,7750
50
+ agl/cli/commands/__init__.py,sha256=3DOW8vPNqQbIz2D1dlhuhMv0DmYVYnJiMedo5bM1tkA,716
51
+ agl/cli/commands/clear.py,sha256=-rkOj__Ep0bbdJv63AsNkIWpfuN8BQzK0_W4FMNcdoM,1815
52
+ agl/cli/commands/init.py,sha256=KRyDdBrLhgca1UNJHeP-r-VlLva3DvR2d7MsvP126CU,1273
53
+ agl/cli/commands/resume.py,sha256=r6qzSal3n7d6KCVSZYp-a_HMI4xc6aQ3oCuvOko00mE,1647
54
+ agl/cli/commands/run.py,sha256=xKLFg_rwN1c5H9tR8cpmzvsVMNy4PjAV6IOIMrt0m74,2790
55
+ agl/cli/commands/workflows.py,sha256=AGTPAf10apekPHK1JUSnmHkqOPNoP7SDbAjSlJfwoXk,2590
56
+ agl/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
+ agl/config/container.py,sha256=CVrgFUmexwBFsf_CkZ664EHjZ9LrtFixEF4pxABs-mk,7392
58
+ agl/config/registry.py,sha256=jgVfqZZokO67yj5EFwC5ltrcVOSiOQOnwtelcIkHlw4,3100
59
+ agl/config/schema.py,sha256=S5KwGzSWTg_VNvQrBGhUrVFAq4NXZHhU-4cW9eOMEUc,2921
60
+ agl/config/sources.py,sha256=sInZSBtssUvrtoj0-6BBAHUn_1d35ZT1ItcE6cwX47s,7127
61
+ agl/config/toml_file.py,sha256=dSthg2qCYaAR_lMK3tPQTD9S1eP0BcSJMl92wPdkA10,12916
62
+ agl/ports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
63
+ agl/ports/agent.py,sha256=TvuVyEI0iTPTLi93JUU7bPfkYsm0E0CGjpJ7ysxRNww,5939
64
+ agl/ports/clock.py,sha256=10lDkNWHrVxKrZl43r2lH1f4It8lMTq95p7oovjKots,359
65
+ agl/ports/errors.py,sha256=vx3u2kUdLNYO9dp-F4htm-7xg0cAbQubou6tckMeA7E,1583
66
+ agl/ports/history.py,sha256=4yn0Zv3i-CVD-78HweKd4s67KfatO2eVhWljUHpIh8E,4690
67
+ agl/ports/home_layout.py,sha256=zZT7EgzoIDGk6Pa0M9pFHCIJrYLIVdCIDiXXn8LurtM,7094
68
+ agl/ports/ids.py,sha256=USiWbbp2w8WCLNARWHHiRaLoRqjcETZQaKTXSOLmadg,5427
69
+ agl/ports/integration.py,sha256=ATUWAzuNnAsE0HurhKZxSXJzp_LBW0eFw-iRG9-fobc,3419
70
+ agl/ports/run.py,sha256=88ez5yijeUlIVkIhVMOYG63f4Ai3WvvMtvRjDX1n9mA,10511
71
+ agl/ports/store.py,sha256=qNqVXxVzKguMQExn6awOsk8oBOb7gzDU_BB6HJj8iyY,2990
72
+ agl/ports/terminal.py,sha256=wwNFehOh0yB7v_J1vq1xor8Ht6BjHcWIuN2juFVi-sc,2862
73
+ agl/ports/tree_layout.py,sha256=h2OJ2dtzQ9aYj_QO4voW4qUUQF8zW4qa417L3qB8SrE,3945
74
+ agl/ports/verifier.py,sha256=rahPVsMmvwnTK6CNp64UKkheo5u2bTHKbFtMQGCyhD8,768
75
+ agl/ports/workspace.py,sha256=s4TLa6lrie0SInEATgtwMrf8T5LNAduykPWdMMSUqrw,3678
76
+ agl/sdk/__init__.py,sha256=k0qguxsWLdtQ8GV_IZd7NLaIp3F_RIVFNI3nwCkVV00,1564
77
+ agl/sdk/_declarations.py,sha256=PC6honf_xuEsEW6QPvGnkLw7LW093WxV5Gl-d644pWM,688
78
+ agl/sdk/errors.py,sha256=whKE5BVkDNFY8VuzyWWzh5cyHTezewWrip4Hdld9o-o,410
79
+ agl/sdk/params.py,sha256=PhFYxl5a3NPO8iBdAOItoM2wqgjoy6VtO54HAi7tHbs,11771
80
+ agl/sdk/roles.py,sha256=4zYWQlDpLFhydu2jUcI43VtQHVPcOL4R3xAh2MSifZc,8244
81
+ agl/sdk/terminal.py,sha256=2vNWjAammPjue8eq8y06p3s7js2kYWzcW-oZuA92bzY,290
82
+ agl/sdk/testing.py,sha256=si70wjDrTr0Wn_pj3BN_qltXmrk9N5IuKXGwWbPU4FA,1663
83
+ agl/sdk/tools.py,sha256=M5OWJgrrPGjeDvJKP7JszaRNyC4rN8l6jj-4KToQ3SQ,13960
84
+ agl/sdk/workflow.py,sha256=irvX5ZKPUg_Ub7TZ3Hhbo_5aHLJiu4sqBvYt2jELP7g,11322
85
+ agl/sdk/_engine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
86
+ agl/sdk/_engine/integration.py,sha256=GzHLB3GbXiUDzBXfnXyDJ5mjUQH4huOi54epMJ7_H0Y,8958
87
+ agl/sdk/_engine/journal.py,sha256=YQh26G5YHjpgOCqL9qa5dBl-9Xd7l1JjipYHmOCutXY,14784
88
+ agl/sdk/_engine/preflight.py,sha256=aYyQirjUL250QyX1hnPF76LjGo6FJa4E9y3HQvnc7-g,6719
89
+ agl/sdk/_engine/services.py,sha256=lw8q-ze_OBP_7qu31-8GnW84ihCq4Tv_qJTWt56H6fU,641
90
+ agl/sdk/_engine/steps.py,sha256=qZDFU1_CmRSbQDpXl4jtN_xg-FmR-H3q2GssmGH6Ags,6966
91
+ agl/sdk/_engine/worktrees.py,sha256=JS3RMp6Q8b7Dw8jNXb2Fh7KxCGVueTT5IKroQq7lJlk,1824
92
+ agl/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
+ agl/workflows/fix/__init__.py,sha256=ZgDikYDZ7bWsfqBWucNLJsBnh6JE6bz5jIlIhg_aYGY,808
94
+ agl/workflows/fix/asking.py,sha256=y9_ssjrDj5qsYw6ib2OWn2ilysqA-1XhrMtJEKTyBQE,2298
95
+ agl/workflows/fix/findings.py,sha256=uraLQwRoVrXjRAwyb9g-QhrXztilFnRI48e6obX6M2M,2146
96
+ agl/workflows/fix/questions.py,sha256=hMKAvAVv_TiLjXQnB4wTxyG4oYZ2u28CjDvnY0Cpapc,1259
97
+ agl/workflows/fix/roles.py,sha256=jgHk-IHSG9_AHBWm_MWueo1ylnypAU3XfPONRaYUX-o,868
98
+ agl/workflows/fix/prompts/implement.md,sha256=3SUIEowwNdAbXDm2ca7PdFqtR5qZSkyMa9-QT9Fd2ig,7191
99
+ agl/workflows/fix/prompts/review.md,sha256=U_jwbqD472KRIVrJlYdc3a7ydN4Wpsc4hYGxxZO52eo,4739
100
+ agl/workflows/fix/views/__init__.py,sha256=wR9rlSLnOxQc2PCKyXfGMaCLDJDdjRpjFAlvzd5s1ao,147
101
+ agl/workflows/fix/views/board.py,sha256=nAG8j95Z8qXhL9gGcUstZXAkLiJhBbAvh8wXM06ZLBw,195
102
+ agl/workflows/fix/views/question.py,sha256=rT51_hkIDT2uJuhJ_b8XKMPq-MRXQ956xnW0GOxuCK4,550
103
+ agl/workflows/split/__init__.py,sha256=mkmLbFtaXMWnsSRfD_Ml_8iohKl46kZvMiB7TUMipas,1429
104
+ agl/workflows/split/chunks.py,sha256=-80WnCAV3Qk8EfPuB8eIbj_dEASSCmrbZtMt20MjnVI,2834
105
+ agl/workflows/split/roles.py,sha256=18gW7fkMEAP9m2l-VQzMnrBS2yO_gTbjQJZr3W7qih0,770
106
+ agl/workflows/split/prompts/implement.md,sha256=Zuo1Cb3FcPUecVQhqk69-ZWzvbi_6JDGFJAeMK7fne4,6679
107
+ agl/workflows/split/prompts/plan.md,sha256=RMfISttfjImUmBouSzk_bASV0KcQv4EClO9ajJLC-x4,6837
108
+ agl/workflows/split/views/__init__.py,sha256=Z_EP5drTjGryofFdaIsAkFYeKmNT0dxQpC6Hg9s94iY,139
109
+ agl/workflows/split/views/board.py,sha256=naUg6fHTLTF8NTdY3zwSbGwNj09di5TBCgF7nE_SUwk,404
110
+ agl/workflows/split/views/conflict.py,sha256=1IfFCufSiRH0rHzQX_wx8TMA6nhx-ScJzr2Lyr-zjBc,912
111
+ agents_gl-0.0.1.dist-info/METADATA,sha256=TO8zX1swferkXiTwb7dpAafpSW82sPLxkuKKFJzKzKI,1948
112
+ agents_gl-0.0.1.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
113
+ agents_gl-0.0.1.dist-info/entry_points.txt,sha256=QuUi5XKEQYmJArCNJe4X3BYD2uKttDoYJLXbvk5AiN8,121
114
+ agents_gl-0.0.1.dist-info/licenses/LICENSE,sha256=Z_632xNvXXjmrt3pUfa1CTphR5q4ieR8Wve0XAoflkk,1060
115
+ agents_gl-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ agl = agl.cli.main:main
3
+
4
+ [agl.workflows]
5
+ fix = agl.workflows.fix:fix
6
+ split = agl.workflows.split:split
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
agl/__init__.py ADDED
File without changes
File without changes
File without changes
@@ -0,0 +1,127 @@
1
+ from collections import deque
2
+ from typing import Final
3
+ from claude_agent_sdk import (
4
+ AssistantMessage,
5
+ ClaudeAgentOptions,
6
+ ClaudeSDKError,
7
+ ResultMessage,
8
+ TextBlock,
9
+ ToolUseBlock,
10
+ query,
11
+ )
12
+ from agl.adapters.claude_code._tools import Caller
13
+ from agl.adapters.claude_code.translate import activity, translated
14
+ from agl.ports.agent import ActivityReporter, AgentOutcome, AgentTask, StopReason
15
+ from agl.ports.errors import UpstreamUnavailable, UpstreamUnexpected
16
+
17
+ __all__ = ["Stderr", "outcome_of"]
18
+
19
+ _TERMINAL_REASONS: Final[dict[str, StopReason | None]] = {
20
+ "completed": StopReason.COMPLETED,
21
+ "max_turns": StopReason.LIMIT,
22
+ "aborted_streaming": None,
23
+ "aborted_tools": None,
24
+ }
25
+
26
+ _SUBTYPES: Final[dict[str, StopReason | None]] = {
27
+ "error_max_turns": StopReason.LIMIT,
28
+ }
29
+
30
+ _STOP_REASONS: Final[dict[str, StopReason | None]] = {
31
+ "end_turn": StopReason.COMPLETED,
32
+ "stop_sequence": StopReason.COMPLETED,
33
+ "tool_use": StopReason.COMPLETED,
34
+ "max_tokens": StopReason.LIMIT,
35
+ }
36
+
37
+ _STDERR_LINES: Final = 50
38
+
39
+ class Stderr:
40
+ def __init__(self) -> None:
41
+ self._lines: deque[str] = deque(maxlen=_STDERR_LINES)
42
+
43
+ def __call__(self, line: str) -> None:
44
+ self._lines.append(line.rstrip("\n"))
45
+
46
+ def tail(self) -> str:
47
+ return "\n".join(self._lines)
48
+
49
+ async def outcome_of(
50
+ task: AgentTask,
51
+ prompt: str,
52
+ options: ClaudeAgentOptions,
53
+ *,
54
+ caller: Caller,
55
+ on_activity: ActivityReporter | None,
56
+ stderr: Stderr,
57
+ ) -> AgentOutcome:
58
+ said = ""
59
+ reported: ResultMessage | None = None
60
+ try:
61
+ async for message in query(prompt=prompt, options=options):
62
+ if isinstance(message, AssistantMessage):
63
+ said = _read(message, task, said, on_activity)
64
+ elif isinstance(message, ResultMessage):
65
+ reported = message
66
+ if caller.failure is not None:
67
+ break
68
+ except ClaudeSDKError as error:
69
+ limited = _limited(reported, said)
70
+ if limited is not None:
71
+ return limited
72
+ raise translated(error) from error
73
+ if caller.failure is not None:
74
+ raise caller.failure
75
+ if reported is None:
76
+ raise UpstreamUnexpected(
77
+ f"the Claude Code CLI ran and never said how the run ended, so AGL has no outcome to "
78
+ f"report for it. The same call would answer the same way - this is a version mismatch "
79
+ f"or an AGL bug, not a busy backend. The CLI's last words were: "
80
+ f"{stderr.tail() or '(it printed nothing)'}"
81
+ )
82
+ limited = _limited(reported, said)
83
+ if limited is not None:
84
+ return limited
85
+ if reported.is_error:
86
+ raise UpstreamUnavailable(
87
+ f"the Claude Code CLI ended the run with an error instead of a result: "
88
+ f"{reported.result or reported.subtype}. Nothing usable came back from the far side, "
89
+ f"so the same call may well succeed once whatever stopped it is fixed"
90
+ )
91
+ return _answered(reported, said)
92
+
93
+ def _read(
94
+ message: AssistantMessage,
95
+ task: AgentTask,
96
+ said: str,
97
+ on_activity: ActivityReporter | None,
98
+ ) -> str:
99
+ for block in message.content:
100
+ if isinstance(block, ToolUseBlock):
101
+ if on_activity is not None:
102
+ on_activity(activity(block, task.workspace))
103
+ elif (
104
+ isinstance(block, TextBlock)
105
+ and block.text.strip()
106
+ and message.parent_tool_use_id is None
107
+ ):
108
+ said = block.text
109
+ return said
110
+
111
+ def _limited(reported: ResultMessage | None, said: str) -> AgentOutcome | None:
112
+ if reported is None or _stopped(reported) is not StopReason.LIMIT:
113
+ return None
114
+ return _answered(reported, said)
115
+
116
+ def _answered(reported: ResultMessage, said: str) -> AgentOutcome:
117
+ return AgentOutcome(stop_reason=_stopped(reported), text=reported.result or said)
118
+
119
+ def _stopped(reported: ResultMessage) -> StopReason | None:
120
+ for value, table in (
121
+ (reported.terminal_reason, _TERMINAL_REASONS),
122
+ (reported.subtype, _SUBTYPES),
123
+ (reported.stop_reason, _STOP_REASONS),
124
+ ):
125
+ if value is not None and value in table:
126
+ return table[value]
127
+ return None
@@ -0,0 +1,72 @@
1
+ from collections.abc import Mapping
2
+ from typing import Any, Final
3
+ from claude_agent_sdk import McpServerConfig, SdkMcpTool, create_sdk_mcp_server
4
+ from agl.ports.agent import Tool, ToolResult
5
+ from agl.ports.run import JsonValue
6
+
7
+ __all__ = ["ASKING_MECHANISMS_DENIED", "Caller", "servers"]
8
+
9
+ # Absent from a probed session even on a machine carrying
10
+ # `CLAUDE_CODE_ENABLE_ASK_USER_QUESTION_TOOL`, and denied anyway: a rule naming a tool that is not
11
+ # there costs a startup warning, and a tool that is there and unnamed costs the restriction.
12
+ ASKING_MECHANISMS_DENIED: Final = ("AskUserQuestion",)
13
+
14
+ _SUPPLIED: Final = "agl"
15
+
16
+ _FAILED: Final = (
17
+ "{name} could not do what it was asked: {raised}. This task is being stopped because of it. "
18
+ "Nothing you do from here is kept, so do not call it again and do not work around it."
19
+ )
20
+
21
+ _STOPPING: Final = (
22
+ "{name} was not called. This task is already being stopped because a tool could not do what "
23
+ "it was asked: {raised}. Nothing you do from here is kept."
24
+ )
25
+
26
+ def servers(tools: tuple[Tool, ...], caller: Caller) -> dict[str, McpServerConfig]:
27
+ return {
28
+ _SUPPLIED: create_sdk_mcp_server(
29
+ name=_SUPPLIED, tools=[_wrapped(declared, caller) for declared in tools]
30
+ )
31
+ }
32
+
33
+ class Caller:
34
+ def __init__(self) -> None:
35
+ self.failure: Exception | None = None
36
+
37
+ def fail(self, raised: Exception) -> None:
38
+ if self.failure is None:
39
+ self.failure = raised
40
+
41
+ async def handled(self, tool: Tool, payload: Mapping[str, JsonValue]) -> ToolResult:
42
+ if self.failure is not None:
43
+ return ToolResult(
44
+ text=_STOPPING.format(name=tool.name, raised=self.failure), rejected=True
45
+ )
46
+ try:
47
+ return await tool.handler(payload)
48
+ except Exception as raised:
49
+ self.fail(raised)
50
+ return ToolResult(text=_FAILED.format(name=tool.name, raised=raised), rejected=True)
51
+
52
+ def _wrapped(declared: Tool, caller: Caller) -> SdkMcpTool[Any]:
53
+
54
+ async def invoked(payload: dict[str, Any]) -> dict[str, Any]:
55
+ outcome = await caller.handled(declared, payload)
56
+ return _result(outcome.text, rejected=outcome.rejected)
57
+
58
+ return SdkMcpTool(
59
+ name=declared.name,
60
+ description=declared.description,
61
+ input_schema=_schema(declared.payload_schema),
62
+ handler=invoked,
63
+ )
64
+
65
+ def _schema(payload_schema: Mapping[str, JsonValue]) -> dict[str, Any]:
66
+ schema = dict(payload_schema)
67
+ schema.setdefault("type", "object")
68
+ schema.setdefault("properties", {})
69
+ return schema
70
+
71
+ def _result(text: str, *, rejected: bool = False) -> dict[str, Any]:
72
+ return {"content": [{"type": "text", "text": text}], "is_error": rejected}
@@ -0,0 +1,191 @@
1
+ import json
2
+ from collections.abc import Awaitable, Callable, Mapping
3
+ from typing import Final, cast
4
+ from agl.ports.agent import (
5
+ ActivityReporter,
6
+ AgentOutcome,
7
+ AgentRunner,
8
+ AgentTask,
9
+ Capability,
10
+ Claude,
11
+ ModelId,
12
+ StopReason,
13
+ Tool,
14
+ ToolResult,
15
+ )
16
+ from agl.ports.errors import InputError
17
+ from agl.ports.run import JsonValue
18
+
19
+ __all__ = ["Conversation", "FakeAgentRunner", "Script", "unscripted"]
20
+
21
+ _CAPABILITIES: Final = frozenset(
22
+ {
23
+ Capability.FILE_EDIT,
24
+ Capability.SHELL,
25
+ Capability.TOOL_CALLING,
26
+ }
27
+ )
28
+
29
+ _ROUNDS: Final = 8
30
+
31
+ _OPENING: Final = "Read: the task AGL's Claude Code fake was given"
32
+
33
+ _PLACEHOLDER: Final = "AGL's Claude Code fake produced this: no model was involved."
34
+
35
+ _CLOSING: Final = (
36
+ "AGL's Claude Code fake ran this task with a script where the model would be. It read nothing "
37
+ "in the workspace and changed nothing there."
38
+ )
39
+
40
+ _FAILED: Final = (
41
+ "{name} could not do what it was asked: {raised}. This task is being stopped because of it. "
42
+ "Nothing you do from here is kept, so do not call it again and do not work around it."
43
+ )
44
+
45
+ _STOPPING: Final = (
46
+ "{name} was not called. This task is already being stopped because a tool could not do what "
47
+ "it was asked: {raised}. Nothing you do from here is kept."
48
+ )
49
+
50
+ class Conversation:
51
+ def __init__(
52
+ self,
53
+ task: AgentTask,
54
+ *,
55
+ on_activity: ActivityReporter | None = None,
56
+ ) -> None:
57
+ self.task = task
58
+
59
+ self.failure: Exception | None = None
60
+
61
+ self._on_activity = on_activity
62
+ self._tools: dict[str, Tool] = {declared.name: declared for declared in task.tools}
63
+
64
+ async def call(self, tool: str, payload: Mapping[str, JsonValue]) -> ToolResult:
65
+ declared = self._tools.get(tool)
66
+ if declared is None:
67
+ known = sorted(self._tools)
68
+ raise InputError(
69
+ f"this task declares no tool called {tool!r}: it declares {known}. A session "
70
+ f"registers exactly the tools the task carries, so a call to anything else is not "
71
+ f"a move any agent could have made, and a script that can make it is a script that "
72
+ f"proves a workflow works with a tool the workflow never declared"
73
+ )
74
+ arguments = _as_json(payload, tool)
75
+ if self.failure is not None:
76
+ return ToolResult(
77
+ text=_STOPPING.format(name=declared.name, raised=self.failure), rejected=True
78
+ )
79
+ try:
80
+ return await declared.handler(arguments)
81
+ except Exception as raised:
82
+ self.failure = raised
83
+ return ToolResult(
84
+ text=_FAILED.format(name=declared.name, raised=raised), rejected=True
85
+ )
86
+
87
+ def report(self, line: str) -> None:
88
+ if self._on_activity is not None:
89
+ self._on_activity(line)
90
+
91
+ type Script = Callable[[Conversation], Awaitable[AgentOutcome]]
92
+
93
+ async def unscripted(conversation: Conversation) -> AgentOutcome:
94
+ conversation.report(_OPENING)
95
+
96
+ called: list[str] = []
97
+ for declared in conversation.task.tools:
98
+ if conversation.failure is not None:
99
+ break
100
+ said = _PLACEHOLDER
101
+ for _ in range(_ROUNDS):
102
+ conversation.report(f"{declared.name}: {said}")
103
+ result = await conversation.call(declared.name, _payload(declared.payload_schema, said))
104
+ called.append(declared.name)
105
+ if not result.rejected or conversation.failure is not None:
106
+ break
107
+ said = result.text
108
+
109
+ return AgentOutcome(stop_reason=StopReason.COMPLETED, text=_said(called))
110
+
111
+ class FakeAgentRunner(AgentRunner):
112
+ def __init__(self, script: Script | None = None) -> None:
113
+ self._script: Final = script if script is not None else unscripted
114
+
115
+ async def capabilities(self, model: ModelId) -> frozenset[Capability]:
116
+ _check_model(model)
117
+ return _CAPABILITIES
118
+
119
+ async def check_ready(self, model: ModelId) -> None:
120
+ _check_model(model)
121
+
122
+ async def run(
123
+ self,
124
+ task: AgentTask,
125
+ *,
126
+ on_activity: ActivityReporter | None = None,
127
+ ) -> AgentOutcome:
128
+ _check_model(task.model)
129
+ conversation = Conversation(task, on_activity=on_activity)
130
+ outcome = await self._script(conversation)
131
+ if conversation.failure is not None:
132
+ raise conversation.failure
133
+ return outcome
134
+
135
+ def _check_model(model: ModelId) -> None:
136
+ if not isinstance(model, Claude):
137
+ served = sorted(str(member) for member in Claude)
138
+ raise InputError(
139
+ f"the Claude Code fake cannot run {str(model)!r}: it serves {served} and nothing "
140
+ f"else. It will not stand in another model for this one - the model was named beside "
141
+ f"the prompt because the choice was semantic, and substituting answers a different "
142
+ f"question than the one the workflow asked"
143
+ )
144
+
145
+ def _as_json(payload: Mapping[str, JsonValue], tool: str) -> dict[str, JsonValue]:
146
+ try:
147
+ return cast(dict[str, JsonValue], json.loads(json.dumps(payload, allow_nan=False)))
148
+ except (TypeError, ValueError) as unwritable:
149
+ raise InputError(
150
+ f"the payload for tool {tool!r} is not JSON: {unwritable}. A tool call reaches a real "
151
+ f"handler as JSON a model produced, and a step's result is written down as JSON, so a "
152
+ f"payload that cannot be one is a call no run could have made"
153
+ ) from unwritable
154
+
155
+ def _payload(schema: Mapping[str, JsonValue], said: str) -> dict[str, JsonValue]:
156
+ properties = schema.get("properties")
157
+ required = schema.get("required")
158
+ if not isinstance(properties, dict) or not isinstance(required, list):
159
+ return {}
160
+ return {
161
+ name: _value(properties[name], said)
162
+ for name in required
163
+ if isinstance(name, str) and name in properties
164
+ }
165
+
166
+ def _value(described: JsonValue, said: str) -> JsonValue:
167
+ if not isinstance(described, dict):
168
+ return said
169
+ choices = described.get("enum")
170
+ if isinstance(choices, list) and choices:
171
+ return choices[0]
172
+ kind = described.get("type")
173
+ if isinstance(kind, list):
174
+ kind = next((one for one in kind if isinstance(one, str)), None)
175
+ if kind == "object":
176
+ return _payload(described, said)
177
+ if kind == "array":
178
+ return []
179
+ if kind in ("integer", "number"):
180
+ return 0
181
+ if kind == "boolean":
182
+ return False
183
+ if kind == "null":
184
+ return None
185
+ return said
186
+
187
+ def _said(called: list[str]) -> str:
188
+ parts = [_CLOSING]
189
+ if called:
190
+ parts.append(f"It called: {', '.join(called)}.")
191
+ return " ".join(parts)