agentforge-framework 0.2.0__tar.gz → 0.2.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 (116) hide show
  1. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/PKG-INFO +36 -16
  2. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/README.md +35 -15
  3. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/pyproject.toml +1 -1
  4. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/__init__.py +1 -1
  5. agentforge_framework-0.2.1/src/agentforge_framework/agents/decomposer.py +440 -0
  6. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/cli.py +162 -24
  7. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/contracts.py +52 -0
  8. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/issues.py +81 -3
  9. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/plan_format.py +111 -0
  10. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/registry.py +9 -1
  11. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/runtime.py +212 -35
  12. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/PKG-INFO +36 -16
  13. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/SOURCES.txt +2 -0
  14. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_agents.py +67 -0
  15. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_cli.py +111 -7
  16. agentforge_framework-0.2.1/tests/test_decompose.py +502 -0
  17. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_docs.py +7 -0
  18. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_runtime.py +51 -33
  19. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/LICENSE +0 -0
  20. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/setup.cfg +0 -0
  21. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/.claude-plugin/plugin.json +0 -0
  22. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/__init__.py +0 -0
  23. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/architect.py +0 -0
  24. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/implementer.py +0 -0
  25. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/orchestrator.py +0 -0
  26. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/reviewer.py +0 -0
  27. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/security.py +0 -0
  28. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/tester.py +0 -0
  29. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/__init__.py +0 -0
  30. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/__init__.py +0 -0
  31. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/base.py +0 -0
  32. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/python.py +0 -0
  33. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/sql.py +0 -0
  34. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/yaml.py +0 -0
  35. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/prompt.py +0 -0
  36. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/resolver.py +0 -0
  37. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/__init__.py +0 -0
  38. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/commands.py +0 -0
  39. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/config.py +0 -0
  40. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/gates.py +0 -0
  41. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/process.py +0 -0
  42. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/project.py +0 -0
  43. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/repo.py +0 -0
  44. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/router.py +0 -0
  45. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/skills.py +0 -0
  46. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/workflow.py +0 -0
  47. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/__init__.py +0 -0
  48. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/databricks/__init__.py +0 -0
  49. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/pyspark/__init__.py +0 -0
  50. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/python/__init__.py +0 -0
  51. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/sql/__init__.py +0 -0
  52. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/providers/__init__.py +0 -0
  53. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/providers/base.py +0 -0
  54. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/providers/claude.py +0 -0
  55. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/providers/codex.py +0 -0
  56. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/MANIFEST.yaml +0 -0
  57. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/NOTICE +0 -0
  58. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/domain-modeling/ADR-FORMAT.md +0 -0
  59. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/domain-modeling/CONTEXT-FORMAT.md +0 -0
  60. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/domain-modeling/SKILL.md +0 -0
  61. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/domain-modeling/agents/openai.yaml +0 -0
  62. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/grill-with-docs/SKILL.md +0 -0
  63. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/grilling/SKILL.md +0 -0
  64. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/grilling/agents/openai.yaml +0 -0
  65. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/to-spec/SKILL.md +0 -0
  66. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/to-spec/agents/openai.yaml +0 -0
  67. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/to-tickets/SKILL.md +0 -0
  68. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/to-tickets/agents/openai.yaml +0 -0
  69. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/SKILL.md +0 -0
  70. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/evals/fixtures/silhouette/human_reference.json +0 -0
  71. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/_lang.py +0 -0
  72. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/banned_phrase_scan.py +0 -0
  73. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/calibrate_pairs.py +0 -0
  74. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/calibrate_score.py +0 -0
  75. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/check_packs.py +0 -0
  76. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/check_suggestions.py +0 -0
  77. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/contribute.py +0 -0
  78. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/diff_check.py +0 -0
  79. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/extract_constraints.py +0 -0
  80. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/harvest_classify.py +0 -0
  81. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/harvest_samples.py +0 -0
  82. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/readability_metrics.py +0 -0
  83. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/refresh_status.py +0 -0
  84. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/silhouette_scan.py +0 -0
  85. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/structure_scan.py +0 -0
  86. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/suggest.py +0 -0
  87. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/validate_preservation.py +0 -0
  88. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/voice_card.py +0 -0
  89. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/voice_profile.py +0 -0
  90. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/voice_score.py +0 -0
  91. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/wiki_sync.py +0 -0
  92. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/write-plainly/SKILL.md +0 -0
  93. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/workflows/bugfix.yaml +0 -0
  94. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/workflows/feature.yaml +0 -0
  95. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/workflows/review.yaml +0 -0
  96. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/dependency_links.txt +0 -0
  97. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/entry_points.txt +0 -0
  98. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/requires.txt +0 -0
  99. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/top_level.txt +0 -0
  100. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_architect.py +0 -0
  101. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_config.py +0 -0
  102. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_context.py +0 -0
  103. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_contracts.py +0 -0
  104. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_execution_posture.py +0 -0
  105. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_gates.py +0 -0
  106. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_interview.py +0 -0
  107. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_issues.py +0 -0
  108. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_plan_format.py +0 -0
  109. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_plugins.py +0 -0
  110. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_project.py +0 -0
  111. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_providers.py +0 -0
  112. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_reviewer.py +0 -0
  113. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_security.py +0 -0
  114. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_skills.py +0 -0
  115. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_tester.py +0 -0
  116. {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_workflow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentforge-framework
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A modular framework for coordinating specialized software agents.
5
5
  Author: Yash Mhatre
6
6
  License-Expression: Apache-2.0
@@ -20,7 +20,7 @@ Dynamic: license-file
20
20
 
21
21
  AgentForge is a standalone Python framework for coordinating specialized software agents through reusable workflows.
22
22
 
23
- A human states a Task. The Orchestrator files a GitHub issue carrying a frozen plan and the Roster of Roles that will execute it. `agentforge implement <n>` runs the Issue's Workflow and opens a draft pull request for a human to sign off. No workflow ever merges.
23
+ A human states a Task. The Orchestrator grills them on it, writes a spec, cuts the spec into Slices, and files a GitHub issue for each -- every one carrying a frozen plan, the Roster of Roles that will execute it, and the issues that block it. `agentforge implement <n>` runs one Issue's Workflow and opens a draft pull request for a human to sign off. No workflow ever merges.
24
24
 
25
25
  ![An Issue carries the frozen plan and the Roster. Each Agent reads that Issue and appends its result as the Run Log. The Workflow ends at a draft pull request that only a human can merge.](docs/assets/one-issue-number.svg)
26
26
 
@@ -42,11 +42,26 @@ Answer them, or press Enter on an empty line to plan with what it has.
42
42
  Retry on 5xx only, or timeouts too?
43
43
  > both, cap it at three attempts
44
44
 
45
+ This cuts into 1 Slice(s), each filed as its own issue:
46
+
47
+ 1. Add a bounded retry to the orders loader [retry]
48
+ Delivers: The orders loader retries a failed fetch up to three times.
49
+ Blocked by: nothing -- can start immediately
50
+ - A 5xx or a timeout is retried; a 4xx is not.
51
+
52
+ Blockers are filed first, and a Slice waits for the ones it names.
53
+
54
+ File these? [y/N] y
55
+
56
+ Interview: 2 question(s) answered
57
+
45
58
  Filed issue #12: https://github.com/acme/pipelines/issues/12
59
+ Add a bounded retry to the orders loader
46
60
  Roster: implementer (standard)
47
- Interview: 2 question(s) answered
48
61
 
49
- Run it with: agentforge implement 12
62
+ Start with: agentforge implement 12
63
+
64
+ Cost: $0.41
50
65
 
51
66
  $ agentforge implement 12 --allow-commands
52
67
  [ok] implementer (standard) — Wrapped the fetch in a bounded retry.
@@ -127,9 +142,14 @@ without notice. See
127
142
 
128
143
  ## Install
129
144
 
130
- AgentForge publishes to no package index yet, so neither route below is
131
- `pip install agentforge-framework`. Both put the same `agentforge` command on
132
- your path.
145
+ ```console
146
+ $ pip install agentforge-framework
147
+ $ agentforge --version
148
+ agentforge 0.2.1
149
+ ```
150
+
151
+ That is the whole of it. The two routes below the naming note are for a machine
152
+ that cannot reach PyPI, and for working on AgentForge itself.
133
153
 
134
154
  The distribution is `agentforge-framework` and it imports as
135
155
  `agentforge_framework`, because an older and larger project holds `agentforge`
@@ -143,15 +163,13 @@ other project installed, because its 0.5.0 through 0.6.5 declare an `agentforge`
143
163
  command too and whichever was installed last wins. See
144
164
  [ADR-0013](docs/adr/0013-the-name-stays-the-import-path-moves.md).
145
165
 
146
- **From a release wheel**, to run AgentForge against your own repositories.
147
- Download the wheel attached to the
148
- [latest release](https://github.com/yashmhatre/AgentForge/releases/latest) and
149
- install the file you downloaded:
166
+ **From a release wheel**, for a machine with no route to PyPI. Every release
167
+ attaches the same wheel and sdist that were uploaded to the index; download one
168
+ from the [latest release](https://github.com/yashmhatre/AgentForge/releases/latest)
169
+ and install the file:
150
170
 
151
171
  ```console
152
- $ pip install agentforge_framework-0.2.0-py3-none-any.whl
153
- $ agentforge --version
154
- agentforge 0.2.0
172
+ $ pip install agentforge_framework-0.2.1-py3-none-any.whl
155
173
  ```
156
174
 
157
175
  Publishing a release is a wizard rather than a checklist:
@@ -170,6 +188,7 @@ $ cd AgentForge
170
188
  $ pip install -e ".[dev]"
171
189
  ```
172
190
 
191
+ Step by step, with every command and flag: [docs/GUIDE.md](docs/GUIDE.md).
173
192
  What each release contains is in [CHANGELOG.md](CHANGELOG.md).
174
193
 
175
194
  ## Requirements
@@ -185,8 +204,9 @@ AgentForge never touches a model API and handles no credentials of its own. What
185
204
 
186
205
  | Command | What it does |
187
206
  | --- | --- |
188
- | `agentforge plan "<task>"` | Runs the Orchestrator at the `deep` tier and files an issue carrying the plan and roster. |
189
- | `agentforge implement <n>` | Reads Issue `<n>`, runs its Workflow on a branch, posts each Agent Result, and opens a draft PR. Add `--allow-commands` when the Workflow must execute a suite. |
207
+ | `agentforge plan "<task>"` | Grills you on the task, writes a spec, cuts it into Slices, and files one issue per Slice carrying the plan, the roster, and the issues that block it. Add `--yes` to file without reviewing the cut. |
208
+ | `agentforge decompose <path>` | The same pipeline over a plan document you already wrote. Use it when the plan is longer than a sentence and already lives in the repository. |
209
+ | `agentforge implement <n>` | Reads Issue `<n>`, runs its Workflow on a branch, posts each Agent Result, and opens a draft PR. Refuses to start while an issue it declares as a blocker has not signed off; `--ignore-blockers` overrides. Add `--allow-commands` when the Workflow must execute a suite. |
190
210
  | `agentforge run [<command> args]` | Runs a chore a Plugin contributes -- `agentforge run scaffold-dbt-model orders` writes the files and exits. With no command name it lists what this repository has. No issue, no Run, no model involved. |
191
211
  | `agentforge unslop <file>` | Scans prose for machine-writing tells. Deterministic; no model involved. |
192
212
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  AgentForge is a standalone Python framework for coordinating specialized software agents through reusable workflows.
4
4
 
5
- A human states a Task. The Orchestrator files a GitHub issue carrying a frozen plan and the Roster of Roles that will execute it. `agentforge implement <n>` runs the Issue's Workflow and opens a draft pull request for a human to sign off. No workflow ever merges.
5
+ A human states a Task. The Orchestrator grills them on it, writes a spec, cuts the spec into Slices, and files a GitHub issue for each -- every one carrying a frozen plan, the Roster of Roles that will execute it, and the issues that block it. `agentforge implement <n>` runs one Issue's Workflow and opens a draft pull request for a human to sign off. No workflow ever merges.
6
6
 
7
7
  ![An Issue carries the frozen plan and the Roster. Each Agent reads that Issue and appends its result as the Run Log. The Workflow ends at a draft pull request that only a human can merge.](docs/assets/one-issue-number.svg)
8
8
 
@@ -24,11 +24,26 @@ Answer them, or press Enter on an empty line to plan with what it has.
24
24
  Retry on 5xx only, or timeouts too?
25
25
  > both, cap it at three attempts
26
26
 
27
+ This cuts into 1 Slice(s), each filed as its own issue:
28
+
29
+ 1. Add a bounded retry to the orders loader [retry]
30
+ Delivers: The orders loader retries a failed fetch up to three times.
31
+ Blocked by: nothing -- can start immediately
32
+ - A 5xx or a timeout is retried; a 4xx is not.
33
+
34
+ Blockers are filed first, and a Slice waits for the ones it names.
35
+
36
+ File these? [y/N] y
37
+
38
+ Interview: 2 question(s) answered
39
+
27
40
  Filed issue #12: https://github.com/acme/pipelines/issues/12
41
+ Add a bounded retry to the orders loader
28
42
  Roster: implementer (standard)
29
- Interview: 2 question(s) answered
30
43
 
31
- Run it with: agentforge implement 12
44
+ Start with: agentforge implement 12
45
+
46
+ Cost: $0.41
32
47
 
33
48
  $ agentforge implement 12 --allow-commands
34
49
  [ok] implementer (standard) — Wrapped the fetch in a bounded retry.
@@ -109,9 +124,14 @@ without notice. See
109
124
 
110
125
  ## Install
111
126
 
112
- AgentForge publishes to no package index yet, so neither route below is
113
- `pip install agentforge-framework`. Both put the same `agentforge` command on
114
- your path.
127
+ ```console
128
+ $ pip install agentforge-framework
129
+ $ agentforge --version
130
+ agentforge 0.2.1
131
+ ```
132
+
133
+ That is the whole of it. The two routes below the naming note are for a machine
134
+ that cannot reach PyPI, and for working on AgentForge itself.
115
135
 
116
136
  The distribution is `agentforge-framework` and it imports as
117
137
  `agentforge_framework`, because an older and larger project holds `agentforge`
@@ -125,15 +145,13 @@ other project installed, because its 0.5.0 through 0.6.5 declare an `agentforge`
125
145
  command too and whichever was installed last wins. See
126
146
  [ADR-0013](docs/adr/0013-the-name-stays-the-import-path-moves.md).
127
147
 
128
- **From a release wheel**, to run AgentForge against your own repositories.
129
- Download the wheel attached to the
130
- [latest release](https://github.com/yashmhatre/AgentForge/releases/latest) and
131
- install the file you downloaded:
148
+ **From a release wheel**, for a machine with no route to PyPI. Every release
149
+ attaches the same wheel and sdist that were uploaded to the index; download one
150
+ from the [latest release](https://github.com/yashmhatre/AgentForge/releases/latest)
151
+ and install the file:
132
152
 
133
153
  ```console
134
- $ pip install agentforge_framework-0.2.0-py3-none-any.whl
135
- $ agentforge --version
136
- agentforge 0.2.0
154
+ $ pip install agentforge_framework-0.2.1-py3-none-any.whl
137
155
  ```
138
156
 
139
157
  Publishing a release is a wizard rather than a checklist:
@@ -152,6 +170,7 @@ $ cd AgentForge
152
170
  $ pip install -e ".[dev]"
153
171
  ```
154
172
 
173
+ Step by step, with every command and flag: [docs/GUIDE.md](docs/GUIDE.md).
155
174
  What each release contains is in [CHANGELOG.md](CHANGELOG.md).
156
175
 
157
176
  ## Requirements
@@ -167,8 +186,9 @@ AgentForge never touches a model API and handles no credentials of its own. What
167
186
 
168
187
  | Command | What it does |
169
188
  | --- | --- |
170
- | `agentforge plan "<task>"` | Runs the Orchestrator at the `deep` tier and files an issue carrying the plan and roster. |
171
- | `agentforge implement <n>` | Reads Issue `<n>`, runs its Workflow on a branch, posts each Agent Result, and opens a draft PR. Add `--allow-commands` when the Workflow must execute a suite. |
189
+ | `agentforge plan "<task>"` | Grills you on the task, writes a spec, cuts it into Slices, and files one issue per Slice carrying the plan, the roster, and the issues that block it. Add `--yes` to file without reviewing the cut. |
190
+ | `agentforge decompose <path>` | The same pipeline over a plan document you already wrote. Use it when the plan is longer than a sentence and already lives in the repository. |
191
+ | `agentforge implement <n>` | Reads Issue `<n>`, runs its Workflow on a branch, posts each Agent Result, and opens a draft PR. Refuses to start while an issue it declares as a blocker has not signed off; `--ignore-blockers` overrides. Add `--allow-commands` when the Workflow must execute a suite. |
172
192
  | `agentforge run [<command> args]` | Runs a chore a Plugin contributes -- `agentforge run scaffold-dbt-model orders` writes the files and exits. With no command name it lists what this repository has. No issue, no Run, no model involved. |
173
193
  | `agentforge unslop <file>` | Scans prose for machine-writing tells. Deterministic; no model involved. |
174
194
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentforge-framework"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "A modular framework for coordinating specialized software agents."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """AgentForge — coordination of specialized software agents through reusable workflows."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
@@ -0,0 +1,440 @@
1
+ """The Decomposer: a Task becomes a set of Issues, in dependency order.
2
+
3
+ Every planning pass comes through here, whether the source was typed at a shell
4
+ (`agentforge plan`) or read from a document the project already keeps
5
+ (`agentforge decompose`). The two differ in where the text comes from and in
6
+ nothing else, so there is one pipeline rather than a small one and a large one
7
+ that drift apart.
8
+
9
+ It does in four movements what a single Orchestrator pass used to do in one:
10
+
11
+ - **Grill.** `grill-with-docs`, the same interview `agentforge plan` runs, aimed
12
+ at the document instead of a sentence. A plan document is longer than a Task
13
+ and no less ambiguous -- it records what its author decided and rarely what
14
+ they rejected, and it is the rejected alternatives that a Slice boundary turns
15
+ on.
16
+ - **Spec.** `to-spec`, which synthesizes and does not interview. It has the
17
+ document and the grill transcript, and it commits to one reading of them.
18
+ - **Cut.** `to-tickets`, which turns that reading into Slices: vertical, each
19
+ sized for one fresh context window, each naming the Slices that block it.
20
+ - **Plan.** One ordinary Orchestrator planning pass per approved Slice, so that
21
+ what gets filed is an Issue like any other and `agentforge implement` learns
22
+ nothing new.
23
+
24
+ Four stages rather than one prompt because they are four jobs, and a single pass
25
+ asked to interview and commit to a breakdown at once commits to the breakdown it
26
+ had before it asked. The cut is shown to the human before anything is filed:
27
+ fifteen wrong Issues take longer to close than one wrong plan takes to reject.
28
+
29
+ See ADR-0021.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ from collections.abc import Callable, Sequence
35
+ from dataclasses import dataclass, replace
36
+ from pathlib import Path
37
+
38
+ from ..core.contracts import (
39
+ AgentResult,
40
+ ContextPack,
41
+ ModelTier,
42
+ Outcome,
43
+ PlanDocument,
44
+ Slice,
45
+ Task,
46
+ )
47
+ from ..core.plan_format import (
48
+ SLICES_CLOSE,
49
+ SLICES_OPEN,
50
+ SPEC_CLOSE,
51
+ SPEC_OPEN,
52
+ PlanFormatError,
53
+ extract_slices,
54
+ extract_spec,
55
+ order_slices,
56
+ )
57
+ from .orchestrator import (
58
+ ORCHESTRATOR,
59
+ Exchange,
60
+ Interviewer,
61
+ Orchestrator,
62
+ render_transcript,
63
+ )
64
+
65
+ #: The skill each stage is delivered. One per stage, and never two: a pass given
66
+ #: both `to-spec` and `to-tickets` has been handed the method for a job it is not
67
+ #: doing yet, and both skills end by publishing to a tracker.
68
+ SPEC_SKILLS = ("to-spec",)
69
+ SLICE_SKILLS = ("to-tickets",)
70
+
71
+ #: Slices one document may be cut into. Past this the breakdown is not a
72
+ #: breakdown, it is the plan re-typed, and no human is reading forty of them
73
+ #: before approving. The cut is asked to merge rather than truncated, because a
74
+ #: list cut off at the cap silently drops the end of the plan.
75
+ MAX_SLICES = 25
76
+
77
+ SPEC = """\
78
+ You are the Orchestrator in AgentForge, synthesizing what a human gave you into \
79
+ a spec that one breakdown pass can cut into work.
80
+
81
+ Do not interview. The questions have been asked; what came back is below, and it \
82
+ is part of the source now. Where the source and an answer disagree, the answer \
83
+ is later and wins. Where neither settles something, say so in the spec rather \
84
+ than choosing quietly -- an unresolved question named in a spec gets resolved, \
85
+ and one you resolved by yourself gets discovered during implementation.
86
+
87
+ ## What the human gave you
88
+
89
+ {source}
90
+
91
+ {document}
92
+ {interview}
93
+ ## The repository
94
+
95
+ You are running in {cwd}. Read whatever you need in order to synthesize \
96
+ accurately -- existing structure, conventions, tests, and any CONTEXT.md, \
97
+ AGENTS.md, or docs/adr/ the project keeps. Use the project's own vocabulary \
98
+ throughout. Do not change any files. This is a synthesis pass.
99
+
100
+ ## What you do not do
101
+
102
+ You have no issue tracker and no triage labels. AgentForge files every Issue \
103
+ itself, later, through its own GitHub boundary, and this pass files nothing. Do \
104
+ not publish the spec, do not open an issue, and do not apply a label.
105
+
106
+ ## Required output
107
+
108
+ End your reply with this block and nothing after it. Markdown inside it, not \
109
+ JSON:
110
+
111
+ {spec_open}
112
+ ## Problem Statement
113
+ ...
114
+
115
+ ## Solution
116
+ ...
117
+
118
+ ## User Stories
119
+ ...
120
+
121
+ ## Implementation Decisions
122
+ ...
123
+
124
+ ## Testing Decisions
125
+ ...
126
+
127
+ ## Out of Scope
128
+ ...
129
+ {spec_close}
130
+
131
+ Cover the whole source. Anything in it that reaches no user story reaches no \
132
+ Issue and does not get built. Scale the spec to what you were given: a \
133
+ one-sentence Task earns a short spec, and padding one out invents scope nobody \
134
+ asked for.\
135
+ """
136
+
137
+ SLICES = """\
138
+ You are the Orchestrator in AgentForge, cutting a spec into the Slices that will \
139
+ be filed as Issues.
140
+
141
+ A Slice is a tracer bullet: a narrow but complete path through every layer the \
142
+ work touches, demoable or verifiable on its own. It is not a layer -- "the \
143
+ schema changes" is not a Slice, and neither is "the tests".
144
+
145
+ Two rules decide the size:
146
+
147
+ - **One Slice is one fresh context window.** Each will be planned and executed \
148
+ by an Agent that has never seen this spec, starting from nothing and stopping \
149
+ when it opens a pull request. A Slice it cannot finish in one sitting is one it \
150
+ finishes badly.
151
+ - **Together the Slices are the whole spec.** Every user story lands in exactly \
152
+ one. Anything the spec put out of scope stays out.
153
+
154
+ A wide refactor is the exception to cutting vertically. Where one mechanical \
155
+ change fans across the codebase and no vertical slice can land green, sequence \
156
+ it expand-migrate-contract instead: add the new form beside the old, migrate the \
157
+ call sites in batches each blocked by the expand, then delete the old form in a \
158
+ Slice blocked by every batch.
159
+
160
+ Give each Slice the Slices that must be finished before it can start. A Slice \
161
+ with no blockers can start immediately, and there must be at least one of those \
162
+ or nothing can begin. Do not invent an edge to express a preference: an edge \
163
+ means the later Slice genuinely cannot be built until the earlier one is, and \
164
+ every edge you add is a Slice that cannot run beside another.
165
+
166
+ Cut as many Slices as the spec has work in it and no more. A spec that is \
167
+ genuinely one sitting's work cuts to one Slice, and manufacturing a breakdown \
168
+ for it files Issues nobody needed and puts edges between them. Never cut more \
169
+ than {cap}: if the spec seems to need more, the Slices are too small, so merge \
170
+ the ones that share a seam rather than dropping any of the spec.
171
+
172
+ ## The spec
173
+
174
+ {spec}
175
+
176
+ ## The repository
177
+
178
+ You are running in {cwd}. Read whatever you need. Title each Slice in the \
179
+ project's own vocabulary. Do not change any files.
180
+
181
+ ## What you do not do
182
+
183
+ You have no issue tracker and no triage labels. AgentForge files these itself, \
184
+ in the order your blocking edges imply, through its own GitHub boundary. Do not \
185
+ publish anything, do not open an issue, and do not apply a label.
186
+
187
+ ## Required output
188
+
189
+ End your reply with this block and nothing after it:
190
+
191
+ {slices_open}
192
+ ```json
193
+ {{
194
+ "slices": [
195
+ {{
196
+ "id": "a short slug, unique, referenced by other slices",
197
+ "title": "what a human reads in a list of thirty issues",
198
+ "delivers": "the end-to-end behaviour this Slice makes work, from the user's perspective, not a layer-by-layer implementation list",
199
+ "acceptance": ["how this Slice is known to be done, checkable by reading the repository"],
200
+ "blocked_by": ["the ids of the Slices that must finish first, or an empty list"]
201
+ }}
202
+ ]
203
+ }}
204
+ ```
205
+ {slices_close}\
206
+ """
207
+
208
+ #: What one approved Slice looks like when it is handed to a planning pass. The
209
+ #: Spec travels with it: the Slice says what to build and the Spec says what the
210
+ #: rest of the work is, which is how a planning pass knows what not to build
211
+ #: here. The blockers are named because a Slice that assumes work an earlier one
212
+ #: does should say so rather than repeating it.
213
+ SLICE_TASK = """\
214
+ {delivers}
215
+
216
+ This is one Slice of a larger plan, filed as its own Issue. Plan this Slice and \
217
+ nothing else: the rest of the plan is other Issues, and work you do here that \
218
+ belongs to one of them is work done twice.
219
+
220
+ ## Acceptance criteria for this Slice
221
+
222
+ {acceptance}
223
+
224
+ ## What is already done when this starts
225
+
226
+ {blockers}
227
+
228
+ ## The wider spec, for context only
229
+
230
+ Everything below is the whole plan. It is here so that you know where this Slice \
231
+ sits and what it must not duplicate. Do not plan any of it.
232
+
233
+ {spec}\
234
+ """
235
+
236
+
237
+ @dataclass(frozen=True)
238
+ class Filed:
239
+ """One Slice, once a planning pass has turned it into something fileable."""
240
+
241
+ slice: Slice
242
+ document: PlanDocument
243
+ #: Issue numbers, filled in by the runtime as blockers are filed. A Slice
244
+ #: names its blockers by id; only the runtime knows what number each got.
245
+ blocked_by: tuple[int, ...] = ()
246
+
247
+
248
+ @dataclass(frozen=True)
249
+ class Decomposed:
250
+ """What a decomposition pass produced, or the reason there is nothing."""
251
+
252
+ #: Every invocation the pass made, in order, so the caller can price it and
253
+ #: say which stage stopped when one did.
254
+ results: tuple[AgentResult, ...] = ()
255
+ spec: str = ""
256
+ slices: tuple[Slice, ...] = ()
257
+ interview: tuple[Exchange, ...] = ()
258
+ #: Set when a stage did not produce what the next one needs. The pass stops
259
+ #: there; nothing is filed.
260
+ failure: AgentResult | None = None
261
+
262
+ @property
263
+ def ok(self) -> bool:
264
+ return self.failure is None and bool(self.slices)
265
+
266
+
267
+ class Decomposer:
268
+ """Runs the stages that turn one document into an approved breakdown."""
269
+
270
+ def __init__(self, provider, tier: ModelTier | None = None) -> None:
271
+ self.provider = provider
272
+ self.tier = tier or ORCHESTRATOR.tier
273
+
274
+ # --- the stages --------------------------------------------------------
275
+
276
+ def _invoke(self, skills: Sequence[str], prompt: str, cwd: Path) -> AgentResult:
277
+ role = replace(ORCHESTRATOR.at_tier(self.tier), skills=tuple(skills))
278
+ return self.provider.invoke(
279
+ role=role, prompt=prompt, context=ContextPack(), tier=self.tier, cwd=cwd
280
+ )
281
+
282
+ def synthesize(
283
+ self, document: str, source: str, cwd: Path, exchanges: Sequence[Exchange] = ()
284
+ ) -> AgentResult:
285
+ interview = ""
286
+ if exchanges:
287
+ interview = (
288
+ "\n## What the human told you when you asked\n\n"
289
+ + render_transcript(exchanges)
290
+ + "\n\nThese answers are part of the plan now.\n"
291
+ )
292
+ return self._invoke(
293
+ SPEC_SKILLS,
294
+ SPEC.format(
295
+ document=document.strip(),
296
+ source=source,
297
+ interview=interview,
298
+ cwd=cwd,
299
+ spec_open=SPEC_OPEN,
300
+ spec_close=SPEC_CLOSE,
301
+ ),
302
+ cwd,
303
+ )
304
+
305
+ def cut(self, spec: str, cwd: Path) -> AgentResult:
306
+ return self._invoke(
307
+ SLICE_SKILLS,
308
+ SLICES.format(
309
+ spec=spec.strip(),
310
+ cwd=cwd,
311
+ cap=MAX_SLICES,
312
+ slices_open=SLICES_OPEN,
313
+ slices_close=SLICES_CLOSE,
314
+ ),
315
+ cwd,
316
+ )
317
+
318
+ # --- the pass ----------------------------------------------------------
319
+
320
+ def decompose(
321
+ self,
322
+ document: str,
323
+ source: str,
324
+ cwd: Path,
325
+ interviewer: Interviewer | None = None,
326
+ ) -> Decomposed:
327
+ """Grill, synthesize, cut. Nothing here files anything.
328
+
329
+ Each stage stops the pass rather than degrading into the next one. A
330
+ breakdown cut from a spec that was never written is a breakdown of
331
+ whatever the model remembered of the document, which is the one failure
332
+ that would not look like a failure.
333
+ """
334
+ task = Task(statement=document)
335
+ exchanges = (
336
+ Orchestrator(self.provider, tier=self.tier).interview(task, cwd, interviewer)
337
+ if interviewer
338
+ else ()
339
+ )
340
+
341
+ spec_result = self.synthesize(document, source, cwd, exchanges)
342
+ results: tuple[AgentResult, ...] = (spec_result,)
343
+ if spec_result.outcome is not Outcome.COMPLETED:
344
+ return Decomposed(results=results, interview=exchanges, failure=spec_result)
345
+
346
+ try:
347
+ spec = extract_spec(spec_result.raw)
348
+ except PlanFormatError as exc:
349
+ return Decomposed(
350
+ results=results,
351
+ interview=exchanges,
352
+ failure=_failed(
353
+ self.tier, f"the synthesis pass wrote no usable spec: {exc}", spec_result
354
+ ),
355
+ )
356
+
357
+ cut_result = self.cut(spec, cwd)
358
+ results += (cut_result,)
359
+ if cut_result.outcome is not Outcome.COMPLETED:
360
+ return Decomposed(results=results, spec=spec, interview=exchanges, failure=cut_result)
361
+
362
+ try:
363
+ slices = order_slices(extract_slices(cut_result.raw))
364
+ except PlanFormatError as exc:
365
+ return Decomposed(
366
+ results=results,
367
+ spec=spec,
368
+ interview=exchanges,
369
+ failure=_failed(
370
+ self.tier, f"the breakdown pass wrote no usable slices: {exc}", cut_result
371
+ ),
372
+ )
373
+
374
+ if len(slices) > MAX_SLICES:
375
+ return Decomposed(
376
+ results=results,
377
+ spec=spec,
378
+ interview=exchanges,
379
+ failure=_failed(
380
+ self.tier,
381
+ f"the breakdown pass cut {len(slices)} Slices against a cap of "
382
+ f"{MAX_SLICES}; the plan document wants splitting before it is decomposed",
383
+ cut_result,
384
+ ),
385
+ )
386
+
387
+ return Decomposed(results=results, spec=spec, slices=slices, interview=exchanges)
388
+
389
+
390
+ def slice_task(one: Slice, spec: str, blockers: Sequence[Slice] = ()) -> Task:
391
+ """One Slice, phrased as the Task a planning pass is given."""
392
+ acceptance = "\n".join(f"- {criterion}" for criterion in one.acceptance) or (
393
+ "- None stated. Write acceptance criteria the executing Role can check."
394
+ )
395
+ done = "\n".join(
396
+ f"- {blocker.title}: {blocker.delivers}".rstrip(": ") for blocker in blockers
397
+ ) or "- Nothing. This Slice starts from the repository as it is."
398
+
399
+ return Task(
400
+ statement=SLICE_TASK.format(
401
+ delivers=(one.delivers or one.title).strip(),
402
+ acceptance=acceptance,
403
+ blockers=done,
404
+ spec=spec.strip(),
405
+ )
406
+ )
407
+
408
+
409
+ def render_breakdown(slices: Sequence[Slice]) -> list[str]:
410
+ """The cut, as the human is shown it before anything is filed."""
411
+ by_id = {one.id: one for one in slices}
412
+ lines: list[str] = []
413
+ for index, one in enumerate(slices, start=1):
414
+ lines.append(f"{index}. {one.title} [{one.id}]")
415
+ if one.delivers:
416
+ lines.append(f" Delivers: {one.delivers}")
417
+ blockers = ", ".join(by_id[b].title for b in one.blocked_by if b in by_id)
418
+ lines.append(f" Blocked by: {blockers or 'nothing -- can start immediately'}")
419
+ for criterion in one.acceptance:
420
+ lines.append(f" - {criterion}")
421
+ return lines
422
+
423
+
424
+ def _failed(tier: ModelTier, summary: str, source: AgentResult) -> AgentResult:
425
+ """A stage that answered, but not in the shape the next stage needs."""
426
+ return AgentResult(
427
+ role=ORCHESTRATOR.name,
428
+ tier=tier,
429
+ outcome=Outcome.FAILED,
430
+ summary=summary,
431
+ detail=source.raw,
432
+ raw=source.raw,
433
+ usage=source.usage,
434
+ )
435
+
436
+
437
+ #: The human, as a callable, answering once. `None` where nobody is attached --
438
+ #: and there the breakdown is not filed, because approving fifteen Issues on
439
+ #: somebody's behalf is not a default worth having.
440
+ Approver = Callable[[Sequence[Slice]], bool]