agentforge-framework 0.2.3__tar.gz → 0.2.5__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.3 → agentforge_framework-0.2.5}/PKG-INFO +48 -12
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/README.md +47 -11
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/pyproject.toml +1 -1
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/__init__.py +1 -1
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/agents/__init__.py +21 -2
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/agents/architect.py +7 -2
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/agents/implementer.py +7 -2
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/agents/orchestrator.py +2 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/agents/reviewer.py +2 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/agents/security.py +19 -7
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/agents/tester.py +15 -2
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/cli.py +11 -6
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/config.py +56 -1
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/contracts.py +36 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/gates.py +35 -3
- agentforge_framework-0.2.5/src/agentforge_framework/core/project.py +445 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/repo.py +14 -0
- agentforge_framework-0.2.5/src/agentforge_framework/core/runlock.py +223 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/runtime.py +64 -1
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/providers/base.py +27 -4
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/providers/claude.py +55 -11
- agentforge_framework-0.2.5/src/agentforge_framework/providers/codex.py +196 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework.egg-info/PKG-INFO +48 -12
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework.egg-info/SOURCES.txt +2 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_agents.py +2 -2
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_architect.py +1 -1
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_cli.py +26 -1
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_config.py +60 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_docs.py +6 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_execution_posture.py +81 -19
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_gates.py +74 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_project.py +183 -1
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_providers.py +70 -2
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_reviewer.py +1 -1
- agentforge_framework-0.2.5/tests/test_runlock.py +189 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_runtime.py +118 -21
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_security.py +23 -5
- agentforge_framework-0.2.3/src/agentforge_framework/core/project.py +0 -270
- agentforge_framework-0.2.3/src/agentforge_framework/providers/codex.py +0 -143
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/LICENSE +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/setup.cfg +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/.claude-plugin/plugin.json +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/agents/decomposer.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/context/__init__.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/context/extractors/__init__.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/context/extractors/base.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/context/extractors/python.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/context/extractors/sql.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/context/extractors/yaml.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/context/prompt.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/context/resolver.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/__init__.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/commands.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/issues.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/plan_format.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/process.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/registry.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/router.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/skills.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/workflow.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/plugins/__init__.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/plugins/databricks/__init__.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/plugins/pyspark/__init__.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/plugins/python/__init__.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/plugins/sql/__init__.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/providers/__init__.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/MANIFEST.yaml +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/NOTICE +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/domain-modeling/ADR-FORMAT.md +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/domain-modeling/CONTEXT-FORMAT.md +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/domain-modeling/SKILL.md +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/domain-modeling/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/grill-with-docs/SKILL.md +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/grilling/SKILL.md +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/grilling/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/to-spec/SKILL.md +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/to-spec/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/to-tickets/SKILL.md +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/to-tickets/agents/openai.yaml +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/SKILL.md +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/evals/fixtures/silhouette/human_reference.json +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/_lang.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/banned_phrase_scan.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/calibrate_pairs.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/calibrate_score.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/check_packs.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/check_suggestions.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/contribute.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/diff_check.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/extract_constraints.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/harvest_classify.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/harvest_samples.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/readability_metrics.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/refresh_status.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/silhouette_scan.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/structure_scan.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/suggest.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/validate_preservation.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/voice_card.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/voice_profile.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/voice_score.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/unslop/scripts/wiki_sync.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/skills/write-plainly/SKILL.md +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/workflows/bugfix.yaml +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/workflows/feature.yaml +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/workflows/review.yaml +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework.egg-info/dependency_links.txt +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework.egg-info/entry_points.txt +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework.egg-info/requires.txt +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework.egg-info/top_level.txt +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_context.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_contracts.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_decompose.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_interview.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_issues.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_plan_format.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_plugins.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_skills.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/tests/test_tester.py +0 -0
- {agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/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.5
|
|
4
4
|
Summary: A modular framework for coordinating specialized software agents.
|
|
5
5
|
Author: Yash Mhatre
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -26,9 +26,9 @@ A human states a Task. The Orchestrator grills them on it, writes a spec, cuts t
|
|
|
26
26
|
|
|
27
27
|
## Status
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
A Workflow runs its Roles in order. The default `feature` Workflow invokes the
|
|
30
|
+
Implementer, the Tester, Security, and then the Reviewer, posting each Agent
|
|
31
|
+
Result to the Issue before starting the next Step.
|
|
32
32
|
|
|
33
33
|
```console
|
|
34
34
|
$ agentforge plan "add a retry to the loader"
|
|
@@ -99,15 +99,20 @@ ADR-0015.
|
|
|
99
99
|
|
|
100
100
|
Without `--allow-commands`, the Implementer remains default-deny and the Tester
|
|
101
101
|
reports that it could not run the suite; it never substitutes reading tests and
|
|
102
|
-
claims completion.
|
|
102
|
+
claims completion. That gate is held by the `claude` adapter. The `codex` CLI
|
|
103
|
+
cannot hold it — `codex exec` discards its approval flag — so a denied Run on
|
|
104
|
+
`codex` is refused before it starts rather than run under a guarantee nothing
|
|
105
|
+
enforces (ADR-0007, amended). Security, the Reviewer, and the Architect need no such flag —
|
|
103
106
|
auditing, reviewing, and designing are reading. All six Roles `CONTEXT.md` names
|
|
104
|
-
|
|
107
|
+
run. Plugins carry the data-engineering conventions: a
|
|
105
108
|
repository whose files import `pyspark` has its Roles told to write DataFrame
|
|
106
109
|
expressions rather than RDDs, and a Databricks workspace gets Unity Catalog
|
|
107
110
|
naming and the Delta MERGE idioms in the prompts that write code and the
|
|
108
111
|
workspace's own posture in the one that audits it. A repository that matches
|
|
109
|
-
neither hears neither. `agentforge init`
|
|
110
|
-
|
|
112
|
+
neither hears neither. `agentforge init` inspects a repository and writes its
|
|
113
|
+
`.agentforge/config.yaml`; the file is optional, and
|
|
114
|
+
[Project configuration](#project-configuration) below says what it holds. What
|
|
115
|
+
is still to come is in [`docs/PLAN.md`](docs/PLAN.md).
|
|
111
116
|
|
|
112
117
|
Before the first Role is invoked, AgentForge resolves a Context Pack from the
|
|
113
118
|
frozen plan — the files it names, the symbols and imports inside them, the
|
|
@@ -145,7 +150,7 @@ without notice. See
|
|
|
145
150
|
```console
|
|
146
151
|
$ pip install agentforge-framework
|
|
147
152
|
$ agentforge --version
|
|
148
|
-
agentforge 0.2.
|
|
153
|
+
agentforge 0.2.5
|
|
149
154
|
```
|
|
150
155
|
|
|
151
156
|
That is the whole of it. The two routes below the naming note are for a machine
|
|
@@ -169,7 +174,7 @@ from the [latest release](https://github.com/yashmhatre/AgentForge/releases/late
|
|
|
169
174
|
and install the file:
|
|
170
175
|
|
|
171
176
|
```console
|
|
172
|
-
$ pip install agentforge_framework-0.2.
|
|
177
|
+
$ pip install agentforge_framework-0.2.5-py3-none-any.whl
|
|
173
178
|
```
|
|
174
179
|
|
|
175
180
|
Publishing a release is a wizard rather than a checklist:
|
|
@@ -206,7 +211,7 @@ AgentForge is a terminal tool. It runs fine from an IDE's terminal, but the IDE
|
|
|
206
211
|
|
|
207
212
|
**Antigravity IDE cannot be a Provider** ([#101](https://github.com/yashmhatre/AgentForge/issues/101)). Checked by running it, so nobody has to derive it again: `antigravity-ide chat` accepts a prompt and reads stdin, which is the right shape for the argument vector — but it is a window launcher. Piping a prompt to it returns exit 0 immediately with `Reading from stdin via: …\code-stdin-XXXX` on stdout and nothing else, ever; the answer goes to a GUI session. A Provider adapter has to read the Agent's result off stdout, so there is nothing for `parse_output` to parse. The same is true of any editor CLI of this shape.
|
|
208
213
|
|
|
209
|
-
**Do not run another agent against a checkout while a Run is going.** A Run commits every change to a file git already tracks, however it got there ([ADR-0015](docs/adr/0015-a-run-commits-what-it-declared.md)) — so a second agent's half-finished edits are committed into the Run's branch and attributed to a Role. Files nothing in the Run claimed are listed in the pull request body ([ADR-0023](docs/adr/0023-a-commit-names-what-nothing-claimed.md)), which makes it visible at Sign-off but does not prevent it. Use a separate clone.
|
|
214
|
+
**Do not run another agent against a checkout while a Run is going.** A Run commits every change to a file git already tracks, however it got there ([ADR-0015](docs/adr/0015-a-run-commits-what-it-declared.md)) — so a second agent's half-finished edits are committed into the Run's branch and attributed to a Role. Files nothing in the Run claimed are listed in the pull request body ([ADR-0023](docs/adr/0023-a-commit-names-what-nothing-claimed.md)), which makes it visible at Sign-off but does not prevent it. Use a separate clone. A second `agentforge implement` is refused outright rather than merely disclosed — a Run holds its checkout for the duration ([ADR-0026](docs/adr/0026-a-run-holds-the-checkout-it-was-pointed-at.md)) — but nothing else consults that lock, so an IDE assistant is still yours to keep away.
|
|
210
215
|
|
|
211
216
|
## Commands
|
|
212
217
|
|
|
@@ -274,22 +279,53 @@ if the repository has no GitHub remote, because ADR-0002 makes that a
|
|
|
274
279
|
precondition for every Run. Re-running never clobbers a config you have edited:
|
|
275
280
|
it reports what differs and writes nothing, and `--force` replaces it.
|
|
276
281
|
|
|
282
|
+
A Python suite is written pinned to the interpreter that will run it —
|
|
283
|
+
`[".venv/bin/python", "-m", "pytest"]` rather than `pytest` — whenever the
|
|
284
|
+
repository has a virtualenv to pin it to, and init checks that the interpreter
|
|
285
|
+
can run pytest before writing the line. Bare `pytest` is whichever one PATH
|
|
286
|
+
answers with, which in a project with a venv is the one the project does not
|
|
287
|
+
use: the suite then fails on imports that are installed and reads as a broken
|
|
288
|
+
repository rather than a misconfigured one. The interpreter is named relative to
|
|
289
|
+
the repository root, so the file is still right on somebody else's clone, and
|
|
290
|
+
init looks up to two directories down for the project — a suite that lives in
|
|
291
|
+
`subproject/tests/` is found, and named.
|
|
292
|
+
|
|
277
293
|
The file is not a precondition. Without one, the documented Provider capability
|
|
278
294
|
defaults are Claude `native` and every other Provider `fragment`, and the
|
|
279
|
-
`tests` Gate runs `pytest
|
|
295
|
+
`tests` Gate runs `pytest` — the case the pinning above exists to avoid, so a
|
|
296
|
+
repository with a venv is better off with a file.
|
|
280
297
|
|
|
281
298
|
```yaml
|
|
282
299
|
providers:
|
|
283
300
|
claude:
|
|
284
301
|
capability_tier: native
|
|
302
|
+
# Optional. Overrides the adapter's own tier-to-model table; name only the
|
|
303
|
+
# tiers you disagree about, and the rest keep the shipped defaults.
|
|
304
|
+
models:
|
|
305
|
+
deep: claude-opus-5
|
|
285
306
|
codex:
|
|
286
307
|
capability_tier: fragment
|
|
287
308
|
|
|
309
|
+
# Optional. A Role's two declared axes: which class of model runs it, and how
|
|
310
|
+
# hard that model thinks. Both default to the table in ADR-0004.
|
|
311
|
+
roles:
|
|
312
|
+
security:
|
|
313
|
+
tier: deep
|
|
314
|
+
effort: max
|
|
315
|
+
|
|
288
316
|
gates:
|
|
289
317
|
tests:
|
|
290
318
|
suite: pytest
|
|
291
319
|
```
|
|
292
320
|
|
|
321
|
+
There is no `roles.<name>.model` key, and naming one is an error rather than a
|
|
322
|
+
line that gets ignored. A Role declares a tier; the Provider maps that tier onto
|
|
323
|
+
a model. A model named per Role does not survive the next CLI release and does
|
|
324
|
+
not port to another Provider, which is what
|
|
325
|
+
[ADR-0004](docs/adr/0004-model-tiers-declared-not-named.md) exists to prevent --
|
|
326
|
+
override `providers.<name>.models.<tier>` if the mapping is what you disagree
|
|
327
|
+
with.
|
|
328
|
+
|
|
293
329
|
There is no `plugins:` key. Which Plugins answer for a repository is decided per
|
|
294
330
|
Run from the frozen plan's blast radius (ADR-0016), so a repository-level list
|
|
295
331
|
would be inert and misleading -- init prints what it detected instead. The file
|
|
@@ -8,9 +8,9 @@ A human states a Task. The Orchestrator grills them on it, writes a spec, cuts t
|
|
|
8
8
|
|
|
9
9
|
## Status
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
A Workflow runs its Roles in order. The default `feature` Workflow invokes the
|
|
12
|
+
Implementer, the Tester, Security, and then the Reviewer, posting each Agent
|
|
13
|
+
Result to the Issue before starting the next Step.
|
|
14
14
|
|
|
15
15
|
```console
|
|
16
16
|
$ agentforge plan "add a retry to the loader"
|
|
@@ -81,15 +81,20 @@ ADR-0015.
|
|
|
81
81
|
|
|
82
82
|
Without `--allow-commands`, the Implementer remains default-deny and the Tester
|
|
83
83
|
reports that it could not run the suite; it never substitutes reading tests and
|
|
84
|
-
claims completion.
|
|
84
|
+
claims completion. That gate is held by the `claude` adapter. The `codex` CLI
|
|
85
|
+
cannot hold it — `codex exec` discards its approval flag — so a denied Run on
|
|
86
|
+
`codex` is refused before it starts rather than run under a guarantee nothing
|
|
87
|
+
enforces (ADR-0007, amended). Security, the Reviewer, and the Architect need no such flag —
|
|
85
88
|
auditing, reviewing, and designing are reading. All six Roles `CONTEXT.md` names
|
|
86
|
-
|
|
89
|
+
run. Plugins carry the data-engineering conventions: a
|
|
87
90
|
repository whose files import `pyspark` has its Roles told to write DataFrame
|
|
88
91
|
expressions rather than RDDs, and a Databricks workspace gets Unity Catalog
|
|
89
92
|
naming and the Delta MERGE idioms in the prompts that write code and the
|
|
90
93
|
workspace's own posture in the one that audits it. A repository that matches
|
|
91
|
-
neither hears neither. `agentforge init`
|
|
92
|
-
|
|
94
|
+
neither hears neither. `agentforge init` inspects a repository and writes its
|
|
95
|
+
`.agentforge/config.yaml`; the file is optional, and
|
|
96
|
+
[Project configuration](#project-configuration) below says what it holds. What
|
|
97
|
+
is still to come is in [`docs/PLAN.md`](docs/PLAN.md).
|
|
93
98
|
|
|
94
99
|
Before the first Role is invoked, AgentForge resolves a Context Pack from the
|
|
95
100
|
frozen plan — the files it names, the symbols and imports inside them, the
|
|
@@ -127,7 +132,7 @@ without notice. See
|
|
|
127
132
|
```console
|
|
128
133
|
$ pip install agentforge-framework
|
|
129
134
|
$ agentforge --version
|
|
130
|
-
agentforge 0.2.
|
|
135
|
+
agentforge 0.2.5
|
|
131
136
|
```
|
|
132
137
|
|
|
133
138
|
That is the whole of it. The two routes below the naming note are for a machine
|
|
@@ -151,7 +156,7 @@ from the [latest release](https://github.com/yashmhatre/AgentForge/releases/late
|
|
|
151
156
|
and install the file:
|
|
152
157
|
|
|
153
158
|
```console
|
|
154
|
-
$ pip install agentforge_framework-0.2.
|
|
159
|
+
$ pip install agentforge_framework-0.2.5-py3-none-any.whl
|
|
155
160
|
```
|
|
156
161
|
|
|
157
162
|
Publishing a release is a wizard rather than a checklist:
|
|
@@ -188,7 +193,7 @@ AgentForge is a terminal tool. It runs fine from an IDE's terminal, but the IDE
|
|
|
188
193
|
|
|
189
194
|
**Antigravity IDE cannot be a Provider** ([#101](https://github.com/yashmhatre/AgentForge/issues/101)). Checked by running it, so nobody has to derive it again: `antigravity-ide chat` accepts a prompt and reads stdin, which is the right shape for the argument vector — but it is a window launcher. Piping a prompt to it returns exit 0 immediately with `Reading from stdin via: …\code-stdin-XXXX` on stdout and nothing else, ever; the answer goes to a GUI session. A Provider adapter has to read the Agent's result off stdout, so there is nothing for `parse_output` to parse. The same is true of any editor CLI of this shape.
|
|
190
195
|
|
|
191
|
-
**Do not run another agent against a checkout while a Run is going.** A Run commits every change to a file git already tracks, however it got there ([ADR-0015](docs/adr/0015-a-run-commits-what-it-declared.md)) — so a second agent's half-finished edits are committed into the Run's branch and attributed to a Role. Files nothing in the Run claimed are listed in the pull request body ([ADR-0023](docs/adr/0023-a-commit-names-what-nothing-claimed.md)), which makes it visible at Sign-off but does not prevent it. Use a separate clone.
|
|
196
|
+
**Do not run another agent against a checkout while a Run is going.** A Run commits every change to a file git already tracks, however it got there ([ADR-0015](docs/adr/0015-a-run-commits-what-it-declared.md)) — so a second agent's half-finished edits are committed into the Run's branch and attributed to a Role. Files nothing in the Run claimed are listed in the pull request body ([ADR-0023](docs/adr/0023-a-commit-names-what-nothing-claimed.md)), which makes it visible at Sign-off but does not prevent it. Use a separate clone. A second `agentforge implement` is refused outright rather than merely disclosed — a Run holds its checkout for the duration ([ADR-0026](docs/adr/0026-a-run-holds-the-checkout-it-was-pointed-at.md)) — but nothing else consults that lock, so an IDE assistant is still yours to keep away.
|
|
192
197
|
|
|
193
198
|
## Commands
|
|
194
199
|
|
|
@@ -256,22 +261,53 @@ if the repository has no GitHub remote, because ADR-0002 makes that a
|
|
|
256
261
|
precondition for every Run. Re-running never clobbers a config you have edited:
|
|
257
262
|
it reports what differs and writes nothing, and `--force` replaces it.
|
|
258
263
|
|
|
264
|
+
A Python suite is written pinned to the interpreter that will run it —
|
|
265
|
+
`[".venv/bin/python", "-m", "pytest"]` rather than `pytest` — whenever the
|
|
266
|
+
repository has a virtualenv to pin it to, and init checks that the interpreter
|
|
267
|
+
can run pytest before writing the line. Bare `pytest` is whichever one PATH
|
|
268
|
+
answers with, which in a project with a venv is the one the project does not
|
|
269
|
+
use: the suite then fails on imports that are installed and reads as a broken
|
|
270
|
+
repository rather than a misconfigured one. The interpreter is named relative to
|
|
271
|
+
the repository root, so the file is still right on somebody else's clone, and
|
|
272
|
+
init looks up to two directories down for the project — a suite that lives in
|
|
273
|
+
`subproject/tests/` is found, and named.
|
|
274
|
+
|
|
259
275
|
The file is not a precondition. Without one, the documented Provider capability
|
|
260
276
|
defaults are Claude `native` and every other Provider `fragment`, and the
|
|
261
|
-
`tests` Gate runs `pytest
|
|
277
|
+
`tests` Gate runs `pytest` — the case the pinning above exists to avoid, so a
|
|
278
|
+
repository with a venv is better off with a file.
|
|
262
279
|
|
|
263
280
|
```yaml
|
|
264
281
|
providers:
|
|
265
282
|
claude:
|
|
266
283
|
capability_tier: native
|
|
284
|
+
# Optional. Overrides the adapter's own tier-to-model table; name only the
|
|
285
|
+
# tiers you disagree about, and the rest keep the shipped defaults.
|
|
286
|
+
models:
|
|
287
|
+
deep: claude-opus-5
|
|
267
288
|
codex:
|
|
268
289
|
capability_tier: fragment
|
|
269
290
|
|
|
291
|
+
# Optional. A Role's two declared axes: which class of model runs it, and how
|
|
292
|
+
# hard that model thinks. Both default to the table in ADR-0004.
|
|
293
|
+
roles:
|
|
294
|
+
security:
|
|
295
|
+
tier: deep
|
|
296
|
+
effort: max
|
|
297
|
+
|
|
270
298
|
gates:
|
|
271
299
|
tests:
|
|
272
300
|
suite: pytest
|
|
273
301
|
```
|
|
274
302
|
|
|
303
|
+
There is no `roles.<name>.model` key, and naming one is an error rather than a
|
|
304
|
+
line that gets ignored. A Role declares a tier; the Provider maps that tier onto
|
|
305
|
+
a model. A model named per Role does not survive the next CLI release and does
|
|
306
|
+
not port to another Provider, which is what
|
|
307
|
+
[ADR-0004](docs/adr/0004-model-tiers-declared-not-named.md) exists to prevent --
|
|
308
|
+
override `providers.<name>.models.<tier>` if the mapping is what you disagree
|
|
309
|
+
with.
|
|
310
|
+
|
|
275
311
|
There is no `plugins:` key. Which Plugins answer for a repository is decided per
|
|
276
312
|
Run from the frozen plan's blast radius (ADR-0016), so a repository-level list
|
|
277
313
|
would be inert and misleading -- init prints what it detected instead. The file
|
|
@@ -12,7 +12,7 @@ would be the most expensive default in the project.
|
|
|
12
12
|
|
|
13
13
|
from __future__ import annotations
|
|
14
14
|
|
|
15
|
-
from ..core.contracts import ModelTier, Role
|
|
15
|
+
from ..core.contracts import Effort, ModelTier, Role
|
|
16
16
|
from .architect import ARCHITECT, Architect
|
|
17
17
|
from .implementer import IMPLEMENTER, Implementer
|
|
18
18
|
from .orchestrator import ORCHESTRATOR
|
|
@@ -53,12 +53,30 @@ RUNNERS: dict[str, type] = {
|
|
|
53
53
|
KNOWN_TIERS: dict[str, ModelTier] = {
|
|
54
54
|
"orchestrator": ModelTier.DEEP,
|
|
55
55
|
"architect": ModelTier.DEEP,
|
|
56
|
-
"security": ModelTier.DEEP,
|
|
57
56
|
"reviewer": ModelTier.DEEP,
|
|
58
57
|
"implementer": ModelTier.STANDARD,
|
|
58
|
+
"security": ModelTier.STANDARD,
|
|
59
59
|
"tester": ModelTier.CHEAP,
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
#: The second axis, keyed the same way. A tier says which model runs; this says
|
|
63
|
+
#: how hard it thinks, and the two are set independently — see ADR-0004's
|
|
64
|
+
#: 2026-09-03 amendment for why they were ever one thing.
|
|
65
|
+
#:
|
|
66
|
+
#: The Security row is the reason the axes split. It reads `standard` above and
|
|
67
|
+
#: `high` here, which is not a demotion: ADR-0004 bought it `deep` because a
|
|
68
|
+
#: missed finding is silent, and that argument was about reasoning depth. It
|
|
69
|
+
#: keeps the depth — above the Implementer whose work it audits — and stops
|
|
70
|
+
#: buying a frontier model to get it.
|
|
71
|
+
KNOWN_EFFORTS: dict[str, Effort] = {
|
|
72
|
+
"orchestrator": Effort.HIGH,
|
|
73
|
+
"architect": Effort.HIGH,
|
|
74
|
+
"reviewer": Effort.HIGH,
|
|
75
|
+
"security": Effort.HIGH,
|
|
76
|
+
"implementer": Effort.MEDIUM,
|
|
77
|
+
"tester": Effort.MEDIUM,
|
|
78
|
+
}
|
|
79
|
+
|
|
62
80
|
|
|
63
81
|
class UnknownRole(LookupError):
|
|
64
82
|
"""A Roster names a Role this version of AgentForge cannot run."""
|
|
@@ -80,6 +98,7 @@ def resolve_role(name: str) -> Role:
|
|
|
80
98
|
__all__ = [
|
|
81
99
|
"ARCHITECT",
|
|
82
100
|
"IMPLEMENTER",
|
|
101
|
+
"KNOWN_EFFORTS",
|
|
83
102
|
"KNOWN_TIERS",
|
|
84
103
|
"ORCHESTRATOR",
|
|
85
104
|
"REVIEWER",
|
|
@@ -27,7 +27,7 @@ from __future__ import annotations
|
|
|
27
27
|
from pathlib import Path
|
|
28
28
|
|
|
29
29
|
from ..context.prompt import render_context_block
|
|
30
|
-
from ..core.contracts import AgentResult, ContextPack, ModelTier, Plan, Role
|
|
30
|
+
from ..core.contracts import AgentResult, ContextPack, Effort, ModelTier, Plan, Role
|
|
31
31
|
from ..core.plan_format import RESULT_CLOSE, RESULT_OPEN
|
|
32
32
|
from .implementer import render_steps
|
|
33
33
|
|
|
@@ -90,7 +90,12 @@ step and the mismatch in `summary`.\
|
|
|
90
90
|
"""
|
|
91
91
|
|
|
92
92
|
#: The Architect runs `deep`: a boundary in the wrong place outlives the Run.
|
|
93
|
-
ARCHITECT = Role(
|
|
93
|
+
ARCHITECT = Role(
|
|
94
|
+
name="architect",
|
|
95
|
+
tier=ModelTier.DEEP,
|
|
96
|
+
effort=Effort.HIGH,
|
|
97
|
+
instructions=INSTRUCTIONS,
|
|
98
|
+
)
|
|
94
99
|
|
|
95
100
|
|
|
96
101
|
def build_prompt(
|
|
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
21
|
from ..context.prompt import render_context_block
|
|
22
|
-
from ..core.contracts import AgentResult, ContextPack, ModelTier, Plan, Role
|
|
22
|
+
from ..core.contracts import AgentResult, ContextPack, Effort, ModelTier, Plan, Role
|
|
23
23
|
from ..core.plan_format import RESULT_CLOSE, RESULT_OPEN
|
|
24
24
|
|
|
25
25
|
INSTRUCTIONS = """\
|
|
@@ -77,7 +77,12 @@ actually contains. Change no files when you escalate.\
|
|
|
77
77
|
"""
|
|
78
78
|
|
|
79
79
|
#: The Implementer runs at `standard`: it executes a plan it did not write.
|
|
80
|
-
IMPLEMENTER = Role(
|
|
80
|
+
IMPLEMENTER = Role(
|
|
81
|
+
name="implementer",
|
|
82
|
+
tier=ModelTier.STANDARD,
|
|
83
|
+
effort=Effort.MEDIUM,
|
|
84
|
+
instructions=INSTRUCTIONS,
|
|
85
|
+
)
|
|
81
86
|
|
|
82
87
|
|
|
83
88
|
def render_steps(plan: Plan) -> str:
|
|
@@ -38,6 +38,7 @@ from ..core.contracts import (
|
|
|
38
38
|
DEFAULT_WORKFLOW,
|
|
39
39
|
AgentResult,
|
|
40
40
|
ContextPack,
|
|
41
|
+
Effort,
|
|
41
42
|
ModelTier,
|
|
42
43
|
Outcome,
|
|
43
44
|
Plan,
|
|
@@ -239,6 +240,7 @@ repository itself uses them, and do not start a glossary during an interview.\
|
|
|
239
240
|
ORCHESTRATOR = Role(
|
|
240
241
|
name="orchestrator",
|
|
241
242
|
tier=ModelTier.DEEP,
|
|
243
|
+
effort=Effort.HIGH,
|
|
242
244
|
instructions=INSTRUCTIONS,
|
|
243
245
|
#: What a planning pass works with: the project's vocabulary, the synthesis
|
|
244
246
|
#: of a conversation into a spec, and the breakdown of that spec into work.
|
|
@@ -33,6 +33,7 @@ from ..context.prompt import render_context_block
|
|
|
33
33
|
from ..core.contracts import (
|
|
34
34
|
AgentResult,
|
|
35
35
|
ContextPack,
|
|
36
|
+
Effort,
|
|
36
37
|
ModelTier,
|
|
37
38
|
Outcome,
|
|
38
39
|
Plan,
|
|
@@ -158,6 +159,7 @@ End your reply with this block and nothing after it:
|
|
|
158
159
|
REVIEWER = Role(
|
|
159
160
|
name="reviewer",
|
|
160
161
|
tier=ModelTier.DEEP,
|
|
162
|
+
effort=Effort.HIGH,
|
|
161
163
|
instructions=INSTRUCTIONS,
|
|
162
164
|
skills=WRITING_SKILLS,
|
|
163
165
|
)
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
"""The Security Role: audits the change, and fixes nothing.
|
|
2
2
|
|
|
3
|
-
It runs at `
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Role that cannot be run cheaply.
|
|
3
|
+
It runs at `standard` and thinks at `high` (ADR-0004, amended 2026-09-03). The
|
|
4
|
+
tier used to be `deep`, on the one argument in the table that was not about
|
|
5
|
+
cost: a missed finding is silent — nobody reviews the audit that did not happen
|
|
6
|
+
— so the Role that looks for what nobody asked about cannot be run cheaply.
|
|
7
|
+
|
|
8
|
+
That argument survives; it was only ever about reasoning. When effort became a
|
|
9
|
+
Role's own axis, the depth it was really buying became purchasable on its own,
|
|
10
|
+
and Security kept it — `high`, above the Implementer whose work it reads —
|
|
11
|
+
while the tier came down. If audits start missing what a human then finds at
|
|
12
|
+
Sign-off, the tier is the half to move back.
|
|
7
13
|
|
|
8
14
|
Its output is a list of Findings rather than prose, because the Gate downstream
|
|
9
15
|
of it has to tell "audited, nothing found" from "did not audit", and a paragraph
|
|
@@ -16,7 +22,7 @@ from __future__ import annotations
|
|
|
16
22
|
from pathlib import Path
|
|
17
23
|
|
|
18
24
|
from ..context.prompt import render_context_block
|
|
19
|
-
from ..core.contracts import AgentResult, ContextPack, ModelTier, Plan, Role
|
|
25
|
+
from ..core.contracts import AgentResult, ContextPack, Effort, ModelTier, Plan, Role
|
|
20
26
|
from ..core.plan_format import RESULT_CLOSE, RESULT_OPEN
|
|
21
27
|
from .implementer import render_steps
|
|
22
28
|
|
|
@@ -86,8 +92,14 @@ Use `"outcome": "escalated"` only when the Plan does not match the repository, \
|
|
|
86
92
|
or when you could not audit the change at all. Say which in `summary`.\
|
|
87
93
|
"""
|
|
88
94
|
|
|
89
|
-
#:
|
|
90
|
-
|
|
95
|
+
#: A finding nobody makes is a finding nobody reviews — bought as depth rather
|
|
96
|
+
#: than as model size. See the module docstring and ADR-0004.
|
|
97
|
+
SECURITY = Role(
|
|
98
|
+
name="security",
|
|
99
|
+
tier=ModelTier.STANDARD,
|
|
100
|
+
effort=Effort.HIGH,
|
|
101
|
+
instructions=INSTRUCTIONS,
|
|
102
|
+
)
|
|
91
103
|
|
|
92
104
|
|
|
93
105
|
def build_prompt(
|
{agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/agents/tester.py
RENAMED
|
@@ -5,7 +5,15 @@ from __future__ import annotations
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
from ..context.prompt import render_context_block
|
|
8
|
-
from ..core.contracts import
|
|
8
|
+
from ..core.contracts import (
|
|
9
|
+
AgentResult,
|
|
10
|
+
ContextPack,
|
|
11
|
+
Effort,
|
|
12
|
+
ModelTier,
|
|
13
|
+
Outcome,
|
|
14
|
+
Plan,
|
|
15
|
+
Role,
|
|
16
|
+
)
|
|
9
17
|
from ..core.plan_format import RESULT_CLOSE, RESULT_OPEN
|
|
10
18
|
from .implementer import render_steps
|
|
11
19
|
|
|
@@ -66,7 +74,12 @@ from verifying. Reading tests is not a substitute for running them.\
|
|
|
66
74
|
#: recorded in ADR-0004 — reasoning about an edge case nobody wrote a test for is
|
|
67
75
|
#: the part that gets worse here, and it is the part a human reads the findings
|
|
68
76
|
#: for anyway.
|
|
69
|
-
TESTER = Role(
|
|
77
|
+
TESTER = Role(
|
|
78
|
+
name="tester",
|
|
79
|
+
tier=ModelTier.CHEAP,
|
|
80
|
+
effort=Effort.MEDIUM,
|
|
81
|
+
instructions=INSTRUCTIONS,
|
|
82
|
+
)
|
|
70
83
|
|
|
71
84
|
|
|
72
85
|
def build_prompt(
|
|
@@ -512,7 +512,7 @@ def _run_init(args: argparse.Namespace, runner=None) -> int:
|
|
|
512
512
|
from .core.config import load_config
|
|
513
513
|
from .core.contracts import Plan
|
|
514
514
|
from .core.process import SubprocessRunner
|
|
515
|
-
from .core.project import config_path, detect, differences, render_config
|
|
515
|
+
from .core.project import config_path, detect, differences, render_config, verified
|
|
516
516
|
from .core.registry import activate
|
|
517
517
|
from .core.repo import PreconditionFailed, open_repository
|
|
518
518
|
from .providers import DEFAULT_PROVIDER, PROVIDERS
|
|
@@ -531,11 +531,14 @@ def _run_init(args: argparse.Namespace, runner=None) -> int:
|
|
|
531
531
|
return 2
|
|
532
532
|
|
|
533
533
|
active = activate(Plan(summary=""), repo.root)
|
|
534
|
-
context =
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
534
|
+
context = verified(
|
|
535
|
+
detect(
|
|
536
|
+
repo.root,
|
|
537
|
+
provider,
|
|
538
|
+
tracked=repo.tracked_files(),
|
|
539
|
+
plugins=tuple(plugin.name for plugin in active.plugins),
|
|
540
|
+
),
|
|
541
|
+
runner,
|
|
539
542
|
)
|
|
540
543
|
|
|
541
544
|
print(f"Repository: {repo.root}")
|
|
@@ -544,6 +547,8 @@ def _run_init(args: argparse.Namespace, runner=None) -> int:
|
|
|
544
547
|
suite = " ".join(context.test_suite)
|
|
545
548
|
where = context.suite_detected or "not detected, so this is the documented default"
|
|
546
549
|
print(f" Suite: `{suite}` — {where}")
|
|
550
|
+
if context.suite_note:
|
|
551
|
+
print(f" {context.suite_note}")
|
|
547
552
|
print(f" Plugins: {', '.join(context.plugins) or 'none by root marker'}")
|
|
548
553
|
print(
|
|
549
554
|
" printed, not written: which Plugins answer is decided per Run\n"
|
{agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/config.py
RENAMED
|
@@ -9,6 +9,8 @@ from pathlib import Path
|
|
|
9
9
|
|
|
10
10
|
import yaml
|
|
11
11
|
|
|
12
|
+
from .contracts import Effort, ModelTier
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class CapabilityTier(StrEnum):
|
|
14
16
|
"""How a Provider receives a Role's declared Vendored Skills."""
|
|
@@ -48,9 +50,27 @@ class Config:
|
|
|
48
50
|
#: code's turns this on. See ADR-0024.
|
|
49
51
|
publish_pack_inventory: bool = False
|
|
50
52
|
|
|
53
|
+
#: Per-Provider overrides of the adapter's tier-to-model table, keyed by
|
|
54
|
+
#: provider name and then by tier. ADR-0004 promised this in its first
|
|
55
|
+
#: paragraph and nothing read it until 2026-09-03. A tier absent here keeps
|
|
56
|
+
#: the adapter's default, so a project pins the one tier it disagrees about
|
|
57
|
+
#: rather than restating all three.
|
|
58
|
+
provider_models: dict[str, dict[ModelTier, str]] = field(default_factory=dict)
|
|
59
|
+
|
|
60
|
+
#: Per-Role overrides of the two declared axes. The Role still never names a
|
|
61
|
+
#: model — it names a tier, and the Provider maps that. `roles.x.model` is
|
|
62
|
+
#: deliberately not a key: a model named per Role does not survive a release
|
|
63
|
+
#: and does not port across Providers, which is the whole of ADR-0004.
|
|
64
|
+
role_tiers: dict[str, ModelTier] = field(default_factory=dict)
|
|
65
|
+
role_efforts: dict[str, Effort] = field(default_factory=dict)
|
|
66
|
+
|
|
51
67
|
def capability_for(self, provider: str) -> CapabilityTier:
|
|
52
68
|
return self.provider_capabilities.get(provider, CapabilityTier.FRAGMENT)
|
|
53
69
|
|
|
70
|
+
def model_for(self, provider: str, tier: ModelTier) -> str | None:
|
|
71
|
+
"""The configured model for a tier, or None to keep the adapter's own."""
|
|
72
|
+
return self.provider_models.get(provider, {}).get(tier)
|
|
73
|
+
|
|
54
74
|
|
|
55
75
|
def load_config(root: Path | str) -> Config:
|
|
56
76
|
"""Read ``.agentforge/config.yaml``, or return documented defaults."""
|
|
@@ -60,17 +80,52 @@ def load_config(root: Path | str) -> Config:
|
|
|
60
80
|
|
|
61
81
|
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
62
82
|
capabilities = dict(DEFAULT_CAPABILITIES)
|
|
83
|
+
models: dict[str, dict[ModelTier, str]] = {}
|
|
63
84
|
for name, provider in (data.get("providers") or {}).items():
|
|
64
|
-
|
|
85
|
+
if "capability_tier" in provider:
|
|
86
|
+
capabilities[str(name)] = CapabilityTier(provider["capability_tier"])
|
|
87
|
+
if provider.get("models"):
|
|
88
|
+
models[str(name)] = {
|
|
89
|
+
ModelTier(tier): str(slug) for tier, slug in provider["models"].items()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
tiers, efforts = _role_overrides(data)
|
|
65
93
|
return Config(
|
|
66
94
|
provider_capabilities=capabilities,
|
|
67
95
|
test_suite=_test_suite(data),
|
|
68
96
|
publish_pack_inventory=bool(
|
|
69
97
|
(data.get("context") or {}).get("publish_inventory", False)
|
|
70
98
|
),
|
|
99
|
+
provider_models=models,
|
|
100
|
+
role_tiers=tiers,
|
|
101
|
+
role_efforts=efforts,
|
|
71
102
|
)
|
|
72
103
|
|
|
73
104
|
|
|
105
|
+
def _role_overrides(data: dict) -> tuple[dict[str, ModelTier], dict[str, Effort]]:
|
|
106
|
+
"""`roles.<name>.tier` and `roles.<name>.effort`, the two declared axes.
|
|
107
|
+
|
|
108
|
+
A `model` key here is refused rather than ignored. Silently dropping it
|
|
109
|
+
would leave a project believing it had pinned a model per Role, which reads
|
|
110
|
+
as working right up until a Provider changes — and the whole reason
|
|
111
|
+
ADR-0004 gives a Role a tier is that the failure is otherwise invisible.
|
|
112
|
+
"""
|
|
113
|
+
tiers: dict[str, ModelTier] = {}
|
|
114
|
+
efforts: dict[str, Effort] = {}
|
|
115
|
+
for name, role in (data.get("roles") or {}).items():
|
|
116
|
+
if "model" in role:
|
|
117
|
+
raise ValueError(
|
|
118
|
+
f"`roles.{name}.model` names a model per Role, which ADR-0004 "
|
|
119
|
+
f"does not allow; set `roles.{name}.tier` and override "
|
|
120
|
+
f"`providers.<name>.models.<tier>` if the mapping is wrong"
|
|
121
|
+
)
|
|
122
|
+
if "tier" in role:
|
|
123
|
+
tiers[str(name)] = ModelTier(role["tier"])
|
|
124
|
+
if "effort" in role:
|
|
125
|
+
efforts[str(name)] = Effort(role["effort"])
|
|
126
|
+
return tiers, efforts
|
|
127
|
+
|
|
128
|
+
|
|
74
129
|
def _test_suite(data: dict) -> tuple[str, ...]:
|
|
75
130
|
"""`gates.tests.suite`: what the test-suite Gate runs in this repository.
|
|
76
131
|
|
{agentforge_framework-0.2.3 → agentforge_framework-0.2.5}/src/agentforge_framework/core/contracts.py
RENAMED
|
@@ -45,6 +45,28 @@ class ModelTier(StrEnum):
|
|
|
45
45
|
CHEAP = "cheap"
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
class Effort(StrEnum):
|
|
49
|
+
"""How hard a Role thinks, named by intent and independent of its tier.
|
|
50
|
+
|
|
51
|
+
The second axis ADR-0004 originally folded into the first. A Model Tier
|
|
52
|
+
chooses which model runs; an Effort chooses how much reasoning it spends
|
|
53
|
+
getting there, and the two are not the same purchase -- the Security Role
|
|
54
|
+
is the case that proves it, auditing at `standard` and thinking at `high`.
|
|
55
|
+
|
|
56
|
+
The levels are the intersection of what the shipped adapters' CLIs accept:
|
|
57
|
+
`claude --effort` takes low through max, and every slug in a real codex
|
|
58
|
+
install takes the same five. `ultra` is offered by one codex model alone
|
|
59
|
+
and is deliberately absent -- a level a Role could declare and most
|
|
60
|
+
Providers could not honor is not an intent-named level.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
LOW = "low"
|
|
64
|
+
MEDIUM = "medium"
|
|
65
|
+
HIGH = "high"
|
|
66
|
+
XHIGH = "xhigh"
|
|
67
|
+
MAX = "max"
|
|
68
|
+
|
|
69
|
+
|
|
48
70
|
class Outcome(StrEnum):
|
|
49
71
|
"""How an Agent finished.
|
|
50
72
|
|
|
@@ -278,11 +300,25 @@ class Role:
|
|
|
278
300
|
tier: ModelTier
|
|
279
301
|
instructions: str = ""
|
|
280
302
|
skills: tuple[str, ...] = ()
|
|
303
|
+
#: How much reasoning the Role spends, independent of which model runs it.
|
|
304
|
+
#: `medium` rather than a tier-derived value: a Role that has not thought
|
|
305
|
+
#: about the question gets the level both CLIs already default most models
|
|
306
|
+
#: to, and the ones that have thought about it say so.
|
|
307
|
+
effort: Effort = Effort.MEDIUM
|
|
281
308
|
|
|
282
309
|
def at_tier(self, tier: ModelTier) -> Role:
|
|
283
310
|
"""The same Role with its tier overridden, per user request or config."""
|
|
284
311
|
return replace(self, tier=tier)
|
|
285
312
|
|
|
313
|
+
def at_effort(self, effort: Effort) -> Role:
|
|
314
|
+
"""The same Role with its effort overridden, per config.
|
|
315
|
+
|
|
316
|
+
Separate from `at_tier` because the axes move separately -- overriding
|
|
317
|
+
one has never implied anything about the other, which is the whole
|
|
318
|
+
reason there are two.
|
|
319
|
+
"""
|
|
320
|
+
return replace(self, effort=effort)
|
|
321
|
+
|
|
286
322
|
|
|
287
323
|
@dataclass(frozen=True)
|
|
288
324
|
class Roster:
|