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.
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/PKG-INFO +36 -16
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/README.md +35 -15
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/pyproject.toml +1 -1
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/__init__.py +1 -1
- agentforge_framework-0.2.1/src/agentforge_framework/agents/decomposer.py +440 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/cli.py +162 -24
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/contracts.py +52 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/issues.py +81 -3
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/plan_format.py +111 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/registry.py +9 -1
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/runtime.py +212 -35
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/PKG-INFO +36 -16
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/SOURCES.txt +2 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_agents.py +67 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_cli.py +111 -7
- agentforge_framework-0.2.1/tests/test_decompose.py +502 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_docs.py +7 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_runtime.py +51 -33
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/LICENSE +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/setup.cfg +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/.claude-plugin/plugin.json +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/architect.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/implementer.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/orchestrator.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/reviewer.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/security.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/agents/tester.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/base.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/python.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/sql.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/extractors/yaml.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/prompt.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/context/resolver.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/commands.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/config.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/gates.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/process.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/project.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/repo.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/router.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/skills.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/core/workflow.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/databricks/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/pyspark/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/python/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/plugins/sql/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/providers/__init__.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/providers/base.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/providers/claude.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/providers/codex.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/MANIFEST.yaml +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/NOTICE +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/domain-modeling/ADR-FORMAT.md +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/domain-modeling/CONTEXT-FORMAT.md +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/domain-modeling/SKILL.md +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/domain-modeling/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/grill-with-docs/SKILL.md +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/grilling/SKILL.md +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/grilling/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/to-spec/SKILL.md +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/to-spec/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/to-tickets/SKILL.md +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/to-tickets/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/SKILL.md +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/evals/fixtures/silhouette/human_reference.json +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/_lang.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/banned_phrase_scan.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/calibrate_pairs.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/calibrate_score.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/check_packs.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/check_suggestions.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/contribute.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/diff_check.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/extract_constraints.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/harvest_classify.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/harvest_samples.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/readability_metrics.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/refresh_status.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/silhouette_scan.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/structure_scan.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/suggest.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/validate_preservation.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/voice_card.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/voice_profile.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/voice_score.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/unslop/scripts/wiki_sync.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/skills/write-plainly/SKILL.md +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/workflows/bugfix.yaml +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/workflows/feature.yaml +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework/workflows/review.yaml +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/dependency_links.txt +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/entry_points.txt +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/requires.txt +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/src/agentforge_framework.egg-info/top_level.txt +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_architect.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_config.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_context.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_contracts.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_execution_posture.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_gates.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_interview.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_issues.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_plan_format.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_plugins.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_project.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_providers.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_reviewer.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_security.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_skills.py +0 -0
- {agentforge_framework-0.2.0 → agentforge_framework-0.2.1}/tests/test_tester.py +0 -0
- {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.
|
|
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
|
|
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
|

|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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**,
|
|
147
|
-
|
|
148
|
-
[latest release](https://github.com/yashmhatre/AgentForge/releases/latest)
|
|
149
|
-
install the file
|
|
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.
|
|
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>"` |
|
|
189
|
-
| `agentforge
|
|
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
|
|
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
|

|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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**,
|
|
129
|
-
|
|
130
|
-
[latest release](https://github.com/yashmhatre/AgentForge/releases/latest)
|
|
131
|
-
install the file
|
|
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.
|
|
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>"` |
|
|
171
|
-
| `agentforge
|
|
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
|
|
|
@@ -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]
|