agentforge-framework 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentforge_framework/.claude-plugin/plugin.json +4 -0
- agentforge_framework/__init__.py +3 -0
- agentforge_framework/agents/__init__.py +92 -0
- agentforge_framework/agents/architect.py +146 -0
- agentforge_framework/agents/implementer.py +162 -0
- agentforge_framework/agents/orchestrator.py +588 -0
- agentforge_framework/agents/reviewer.py +335 -0
- agentforge_framework/agents/security.py +138 -0
- agentforge_framework/agents/tester.py +125 -0
- agentforge_framework/cli.py +461 -0
- agentforge_framework/context/__init__.py +1 -0
- agentforge_framework/context/extractors/__init__.py +76 -0
- agentforge_framework/context/extractors/base.py +47 -0
- agentforge_framework/context/extractors/python.py +65 -0
- agentforge_framework/context/extractors/sql.py +121 -0
- agentforge_framework/context/extractors/yaml.py +59 -0
- agentforge_framework/context/prompt.py +104 -0
- agentforge_framework/context/resolver.py +185 -0
- agentforge_framework/core/__init__.py +1 -0
- agentforge_framework/core/commands.py +170 -0
- agentforge_framework/core/config.py +90 -0
- agentforge_framework/core/contracts.py +875 -0
- agentforge_framework/core/gates.py +333 -0
- agentforge_framework/core/issues.py +697 -0
- agentforge_framework/core/plan_format.py +272 -0
- agentforge_framework/core/process.py +141 -0
- agentforge_framework/core/project.py +262 -0
- agentforge_framework/core/registry.py +455 -0
- agentforge_framework/core/repo.py +185 -0
- agentforge_framework/core/router.py +1 -0
- agentforge_framework/core/runtime.py +639 -0
- agentforge_framework/core/skills.py +255 -0
- agentforge_framework/core/workflow.py +215 -0
- agentforge_framework/plugins/__init__.py +35 -0
- agentforge_framework/plugins/databricks/__init__.py +86 -0
- agentforge_framework/plugins/pyspark/__init__.py +57 -0
- agentforge_framework/plugins/python/__init__.py +45 -0
- agentforge_framework/plugins/sql/__init__.py +377 -0
- agentforge_framework/providers/__init__.py +48 -0
- agentforge_framework/providers/base.py +248 -0
- agentforge_framework/providers/claude.py +159 -0
- agentforge_framework/providers/codex.py +139 -0
- agentforge_framework/skills/MANIFEST.yaml +157 -0
- agentforge_framework/skills/NOTICE +49 -0
- agentforge_framework/skills/domain-modeling/ADR-FORMAT.md +47 -0
- agentforge_framework/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- agentforge_framework/skills/domain-modeling/SKILL.md +74 -0
- agentforge_framework/skills/domain-modeling/agents/openai.yaml +3 -0
- agentforge_framework/skills/grill-with-docs/SKILL.md +76 -0
- agentforge_framework/skills/grilling/SKILL.md +28 -0
- agentforge_framework/skills/grilling/agents/openai.yaml +3 -0
- agentforge_framework/skills/to-spec/SKILL.md +75 -0
- agentforge_framework/skills/to-spec/agents/openai.yaml +5 -0
- agentforge_framework/skills/to-tickets/SKILL.md +105 -0
- agentforge_framework/skills/to-tickets/agents/openai.yaml +5 -0
- agentforge_framework/skills/unslop/SKILL.md +131 -0
- agentforge_framework/skills/unslop/evals/fixtures/silhouette/human_reference.json +66 -0
- agentforge_framework/skills/unslop/scripts/_lang.py +106 -0
- agentforge_framework/skills/unslop/scripts/banned_phrase_scan.py +784 -0
- agentforge_framework/skills/unslop/scripts/calibrate_pairs.py +580 -0
- agentforge_framework/skills/unslop/scripts/calibrate_score.py +273 -0
- agentforge_framework/skills/unslop/scripts/check_packs.py +80 -0
- agentforge_framework/skills/unslop/scripts/check_suggestions.py +225 -0
- agentforge_framework/skills/unslop/scripts/contribute.py +373 -0
- agentforge_framework/skills/unslop/scripts/diff_check.py +139 -0
- agentforge_framework/skills/unslop/scripts/extract_constraints.py +201 -0
- agentforge_framework/skills/unslop/scripts/harvest_classify.py +223 -0
- agentforge_framework/skills/unslop/scripts/harvest_samples.py +534 -0
- agentforge_framework/skills/unslop/scripts/readability_metrics.py +295 -0
- agentforge_framework/skills/unslop/scripts/refresh_status.py +154 -0
- agentforge_framework/skills/unslop/scripts/silhouette_scan.py +390 -0
- agentforge_framework/skills/unslop/scripts/structure_scan.py +322 -0
- agentforge_framework/skills/unslop/scripts/suggest.py +211 -0
- agentforge_framework/skills/unslop/scripts/validate_preservation.py +409 -0
- agentforge_framework/skills/unslop/scripts/voice_card.py +496 -0
- agentforge_framework/skills/unslop/scripts/voice_profile.py +194 -0
- agentforge_framework/skills/unslop/scripts/voice_score.py +271 -0
- agentforge_framework/skills/unslop/scripts/wiki_sync.py +479 -0
- agentforge_framework/skills/write-plainly/SKILL.md +94 -0
- agentforge_framework/workflows/bugfix.yaml +8 -0
- agentforge_framework/workflows/feature.yaml +16 -0
- agentforge_framework/workflows/review.yaml +10 -0
- agentforge_framework-0.2.0.dist-info/METADATA +321 -0
- agentforge_framework-0.2.0.dist-info/RECORD +89 -0
- agentforge_framework-0.2.0.dist-info/WHEEL +5 -0
- agentforge_framework-0.2.0.dist-info/entry_points.txt +3 -0
- agentforge_framework-0.2.0.dist-info/licenses/LICENSE +202 -0
- agentforge_framework-0.2.0.dist-info/licenses/src/agentforge_framework/skills/NOTICE +49 -0
- agentforge_framework-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Built-in AgentForge Roles.
|
|
2
|
+
|
|
3
|
+
A Role is a definition — a name, a default Model Tier, and standing instructions
|
|
4
|
+
(CONTEXT.md). The tiers here are ADR-0004's default table; a user overrides them
|
|
5
|
+
per Run on the command line, and per project in configuration once
|
|
6
|
+
`agentforge init` exists.
|
|
7
|
+
|
|
8
|
+
All six Roles CONTEXT.md names can run. The Architect is in no shipped Workflow
|
|
9
|
+
even so — most Tasks do not need a design pass, and one at `deep` on every Run
|
|
10
|
+
would be the most expensive default in the project.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from ..core.contracts import ModelTier, Role
|
|
16
|
+
from .architect import ARCHITECT, Architect
|
|
17
|
+
from .implementer import IMPLEMENTER, Implementer
|
|
18
|
+
from .orchestrator import ORCHESTRATOR
|
|
19
|
+
from .reviewer import REVIEWER, Reviewer
|
|
20
|
+
from .security import SECURITY, Security
|
|
21
|
+
from .tester import TESTER, Tester
|
|
22
|
+
|
|
23
|
+
#: Roles that can actually run today.
|
|
24
|
+
ROLES: dict[str, Role] = {
|
|
25
|
+
ORCHESTRATOR.name: ORCHESTRATOR,
|
|
26
|
+
ARCHITECT.name: ARCHITECT,
|
|
27
|
+
IMPLEMENTER.name: IMPLEMENTER,
|
|
28
|
+
TESTER.name: TESTER,
|
|
29
|
+
SECURITY.name: SECURITY,
|
|
30
|
+
REVIEWER.name: REVIEWER,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#: How to run each Role, keyed the same way. A runner takes a Provider and
|
|
34
|
+
#: exposes `run(plan=, context=, cwd=, role=, tier=)`.
|
|
35
|
+
#:
|
|
36
|
+
#: This registry is why the Workflow runtime names no Role: it looks a runner up
|
|
37
|
+
#: by the name the Workflow step declared. Adding a Role is an entry here and a
|
|
38
|
+
#: line of YAML, never an edit to the engine. The Orchestrator is absent
|
|
39
|
+
#: deliberately — it produces Issues rather than running inside a Workflow.
|
|
40
|
+
RUNNERS: dict[str, type] = {
|
|
41
|
+
ARCHITECT.name: Architect,
|
|
42
|
+
IMPLEMENTER.name: Implementer,
|
|
43
|
+
TESTER.name: Tester,
|
|
44
|
+
SECURITY.name: Security,
|
|
45
|
+
REVIEWER.name: Reviewer,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#: The full cast from CONTEXT.md, with ADR-0004's default tiers. Every name here
|
|
49
|
+
#: now has a runner, so the two sets are equal — the difference between them is
|
|
50
|
+
#: what the Orchestrator uses to recognize a reasonable-but-unavailable choice
|
|
51
|
+
#: and say so, and it is what makes naming a seventh Role's tier before writing
|
|
52
|
+
#: its runner safe. The Architect was the last name to sit in the gap.
|
|
53
|
+
KNOWN_TIERS: dict[str, ModelTier] = {
|
|
54
|
+
"orchestrator": ModelTier.DEEP,
|
|
55
|
+
"architect": ModelTier.DEEP,
|
|
56
|
+
"security": ModelTier.DEEP,
|
|
57
|
+
"reviewer": ModelTier.DEEP,
|
|
58
|
+
"implementer": ModelTier.STANDARD,
|
|
59
|
+
"tester": ModelTier.CHEAP,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class UnknownRole(LookupError):
|
|
64
|
+
"""A Roster names a Role this version of AgentForge cannot run."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def resolve_role(name: str) -> Role:
|
|
68
|
+
"""Look a Role up by name, for rebuilding a Roster out of an Issue body."""
|
|
69
|
+
key = name.strip().lower()
|
|
70
|
+
if key in ROLES:
|
|
71
|
+
return ROLES[key]
|
|
72
|
+
if key in KNOWN_TIERS:
|
|
73
|
+
raise UnknownRole(
|
|
74
|
+
f"the {key!r} Role is not implemented in this version of AgentForge; "
|
|
75
|
+
f"runnable Roles: {', '.join(sorted(ROLES))}"
|
|
76
|
+
)
|
|
77
|
+
raise UnknownRole(f"no Role named {name!r}; available: {', '.join(sorted(ROLES))}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = [
|
|
81
|
+
"ARCHITECT",
|
|
82
|
+
"IMPLEMENTER",
|
|
83
|
+
"KNOWN_TIERS",
|
|
84
|
+
"ORCHESTRATOR",
|
|
85
|
+
"REVIEWER",
|
|
86
|
+
"ROLES",
|
|
87
|
+
"RUNNERS",
|
|
88
|
+
"SECURITY",
|
|
89
|
+
"TESTER",
|
|
90
|
+
"UnknownRole",
|
|
91
|
+
"resolve_role",
|
|
92
|
+
]
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""The Architect: decides the shape of a change it will not make.
|
|
2
|
+
|
|
3
|
+
It runs `deep` because a design error is the expensive kind. An Implementer's
|
|
4
|
+
mistake is caught by a Tester; a boundary drawn in the wrong place is caught
|
|
5
|
+
six months later by whoever has to work across it.
|
|
6
|
+
|
|
7
|
+
It is in none of the three shipped Workflows, and that is deliberate: most
|
|
8
|
+
Tasks do not need a design pass, and paying `deep` for one on every Run would
|
|
9
|
+
be the most expensive default in the project. The Orchestrator selects it for
|
|
10
|
+
Tasks that are design-heavy, and a project may name it in a Workflow of its own.
|
|
11
|
+
|
|
12
|
+
What it does not do is plan. The Plan is frozen before any Agent runs
|
|
13
|
+
(ADR-0003), and a Role that re-planned would be a second Orchestrator with none
|
|
14
|
+
of the human's context. The Architect decides how the frozen Plan should be
|
|
15
|
+
built, and escalates when it cannot be built as described.
|
|
16
|
+
|
|
17
|
+
Know this before putting it in a Workflow: its design reaches the Run Log and
|
|
18
|
+
not the next Role's prompt. Every Role is handed the frozen Plan and the Context
|
|
19
|
+
Pack, and nothing today folds an earlier Step's result into a later Step's
|
|
20
|
+
invocation. So an Architect Step informs the human reading the Issue, and an
|
|
21
|
+
Implementer only through them. Closing that gap is a change to what a Context
|
|
22
|
+
Pack carries rather than a change to this Role.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
from ..context.prompt import render_context_block
|
|
30
|
+
from ..core.contracts import AgentResult, ContextPack, ModelTier, Plan, Role
|
|
31
|
+
from ..core.plan_format import RESULT_CLOSE, RESULT_OPEN
|
|
32
|
+
from .implementer import render_steps
|
|
33
|
+
|
|
34
|
+
INSTRUCTIONS = """\
|
|
35
|
+
You are the Architect in AgentForge. The Plan below is frozen and you are not \
|
|
36
|
+
rewriting it. Somebody decided what to build; you decide how it should be \
|
|
37
|
+
shaped.
|
|
38
|
+
|
|
39
|
+
Say where the seam goes: which module owns which decision, what the interface \
|
|
40
|
+
between them is, and what must not leak across it. Name the approach you \
|
|
41
|
+
rejected and why, because the next person to read this will otherwise rediscover \
|
|
42
|
+
it as a good idea.
|
|
43
|
+
|
|
44
|
+
Prefer the shape the repository already uses. A design that is better in the \
|
|
45
|
+
abstract and unlike everything around it costs every reader afterwards.
|
|
46
|
+
|
|
47
|
+
Write no code and change no files. What you produce is a decision, recorded on \
|
|
48
|
+
the Issue for the human who reads it before the work is built.
|
|
49
|
+
|
|
50
|
+
If the Plan cannot be built as described -- two steps require incompatible \
|
|
51
|
+
designs, or a step assumes a structure the repository does not have -- escalate \
|
|
52
|
+
rather than designing around it. That mismatch is the Orchestrator's to fix.\
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
PROMPT = """\
|
|
56
|
+
{instructions}
|
|
57
|
+
|
|
58
|
+
## The frozen Plan
|
|
59
|
+
|
|
60
|
+
{summary}
|
|
61
|
+
|
|
62
|
+
### Steps
|
|
63
|
+
|
|
64
|
+
{steps}
|
|
65
|
+
{constraints}{context}
|
|
66
|
+
## Working directory
|
|
67
|
+
|
|
68
|
+
{cwd}
|
|
69
|
+
|
|
70
|
+
Read the modules the Plan names and the code they reach into, so the design you \
|
|
71
|
+
give back fits the repository rather than a description of it.
|
|
72
|
+
|
|
73
|
+
## Required output
|
|
74
|
+
|
|
75
|
+
End your reply with this block and nothing after it:
|
|
76
|
+
|
|
77
|
+
{result_open}
|
|
78
|
+
```json
|
|
79
|
+
{{
|
|
80
|
+
"outcome": "completed",
|
|
81
|
+
"summary": "one line: the shape you chose",
|
|
82
|
+
"detail": "the design: the seam, the interfaces, what each module owns, and the approach you rejected",
|
|
83
|
+
"files_changed": []
|
|
84
|
+
}}
|
|
85
|
+
```
|
|
86
|
+
{result_close}
|
|
87
|
+
|
|
88
|
+
Use `"outcome": "escalated"` when the Plan cannot be built as written. Name the \
|
|
89
|
+
step and the mismatch in `summary`.\
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
#: The Architect runs `deep`: a boundary in the wrong place outlives the Run.
|
|
93
|
+
ARCHITECT = Role(name="architect", tier=ModelTier.DEEP, instructions=INSTRUCTIONS)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def build_prompt(
|
|
97
|
+
plan: Plan,
|
|
98
|
+
context: ContextPack,
|
|
99
|
+
cwd: Path,
|
|
100
|
+
role: Role = ARCHITECT,
|
|
101
|
+
) -> str:
|
|
102
|
+
constraints = ""
|
|
103
|
+
if plan.constraints:
|
|
104
|
+
constraints = "\n### Constraints\n\n" + "\n".join(f"- {c}" for c in plan.constraints) + "\n"
|
|
105
|
+
|
|
106
|
+
return PROMPT.format(
|
|
107
|
+
instructions=role.instructions,
|
|
108
|
+
summary=plan.summary.strip(),
|
|
109
|
+
steps=render_steps(plan),
|
|
110
|
+
constraints=constraints,
|
|
111
|
+
context=render_context_block(context),
|
|
112
|
+
cwd=cwd,
|
|
113
|
+
result_open=RESULT_OPEN,
|
|
114
|
+
result_close=RESULT_CLOSE,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Architect:
|
|
119
|
+
"""One Architect invocation through the shared Provider port.
|
|
120
|
+
|
|
121
|
+
No denial path: designing is reading, so ADR-0007's shut gate costs it
|
|
122
|
+
nothing.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(self, provider) -> None:
|
|
126
|
+
self.provider = provider
|
|
127
|
+
|
|
128
|
+
def run(
|
|
129
|
+
self,
|
|
130
|
+
*,
|
|
131
|
+
plan: Plan,
|
|
132
|
+
context: ContextPack,
|
|
133
|
+
cwd: Path,
|
|
134
|
+
role: Role = ARCHITECT,
|
|
135
|
+
tier: ModelTier | None = None,
|
|
136
|
+
) -> AgentResult:
|
|
137
|
+
return self.provider.invoke(
|
|
138
|
+
role=role,
|
|
139
|
+
prompt=build_prompt(plan, context, cwd, role),
|
|
140
|
+
context=context,
|
|
141
|
+
tier=tier or role.tier,
|
|
142
|
+
cwd=cwd,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
__all__ = ["ARCHITECT", "INSTRUCTIONS", "Architect", "build_prompt"]
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""The Implementer: executes a Plan it did not write.
|
|
2
|
+
|
|
3
|
+
It is handed the Plan and the Context Pack and never the human's original
|
|
4
|
+
phrasing (ADR-0003). The pack is the reading already done for it, so that six
|
|
5
|
+
Roles do not each rediscover one repository — but it is a head start rather than
|
|
6
|
+
a boundary, and a step that needs a file the pack does not name is a step that
|
|
7
|
+
reads it (ADR-0010). What the Implementer does not do is re-scope: it executes
|
|
8
|
+
the Plan it was given and nothing adjacent to it.
|
|
9
|
+
|
|
10
|
+
Its second job is to refuse. A plan written on Monday can be wrong by Thursday,
|
|
11
|
+
and an Agent that improvises a correction produces confident wrong work that
|
|
12
|
+
looks like success all the way to the pull request. So the prompt gives it an
|
|
13
|
+
escalation path and the runtime treats escalation as a result rather than an
|
|
14
|
+
error.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from ..context.prompt import render_context_block
|
|
22
|
+
from ..core.contracts import AgentResult, ContextPack, ModelTier, Plan, Role
|
|
23
|
+
from ..core.plan_format import RESULT_CLOSE, RESULT_OPEN
|
|
24
|
+
|
|
25
|
+
INSTRUCTIONS = """\
|
|
26
|
+
You are the Implementer in AgentForge. Another Role planned this work and is \
|
|
27
|
+
not available to answer questions.
|
|
28
|
+
|
|
29
|
+
Execute the plan below and nothing else. Do not re-scope it, do not add \
|
|
30
|
+
improvements it did not ask for, and do not widen it because something nearby \
|
|
31
|
+
looks wrong.
|
|
32
|
+
|
|
33
|
+
If the plan does not match the repository -- a file it names is gone, a step \
|
|
34
|
+
assumes something untrue, two steps contradict each other -- stop and escalate. \
|
|
35
|
+
Do not guess at what was meant. A wrong plan caught now costs a comment; a \
|
|
36
|
+
wrong plan improvised around costs a review.\
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
PROMPT = """\
|
|
40
|
+
{instructions}
|
|
41
|
+
|
|
42
|
+
## Plan
|
|
43
|
+
|
|
44
|
+
{summary}
|
|
45
|
+
|
|
46
|
+
### Steps
|
|
47
|
+
|
|
48
|
+
{steps}
|
|
49
|
+
{constraints}{context}
|
|
50
|
+
## Working directory
|
|
51
|
+
|
|
52
|
+
{cwd}
|
|
53
|
+
|
|
54
|
+
You are on a branch created for this work. Commit nothing; AgentForge commits \
|
|
55
|
+
for you. It commits every file it already tracks that you changed, and a file it \
|
|
56
|
+
does not yet track only if the plan above names it or your `files_changed` does. \
|
|
57
|
+
List every new file you create there, or it will not reach the pull request.
|
|
58
|
+
{execution}
|
|
59
|
+
## Required output
|
|
60
|
+
|
|
61
|
+
End your reply with this block and nothing after it:
|
|
62
|
+
|
|
63
|
+
{result_open}
|
|
64
|
+
```json
|
|
65
|
+
{{
|
|
66
|
+
"outcome": "completed",
|
|
67
|
+
"summary": "one line stating what you changed",
|
|
68
|
+
"detail": "anything a reviewer needs to know",
|
|
69
|
+
"files_changed": ["path/one.py"]
|
|
70
|
+
}}
|
|
71
|
+
```
|
|
72
|
+
{result_close}
|
|
73
|
+
|
|
74
|
+
Use `"outcome": "escalated"` instead if the plan cannot be executed as written. \
|
|
75
|
+
Put the specific mismatch in `summary` -- which step, and what the repository \
|
|
76
|
+
actually contains. Change no files when you escalate.\
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
#: The Implementer runs at `standard`: it executes a plan it did not write.
|
|
80
|
+
IMPLEMENTER = Role(name="implementer", tier=ModelTier.STANDARD, instructions=INSTRUCTIONS)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def render_steps(plan: Plan) -> str:
|
|
84
|
+
if not plan.steps:
|
|
85
|
+
return "_The plan carries no steps. Escalate rather than inventing them._"
|
|
86
|
+
|
|
87
|
+
lines = []
|
|
88
|
+
for index, step in enumerate(plan.steps, start=1):
|
|
89
|
+
lines.append(f"{index}. **{step.id}** — {step.intent}")
|
|
90
|
+
if step.files:
|
|
91
|
+
lines.append(" - Files: " + ", ".join(f"`{path}`" for path in step.files))
|
|
92
|
+
if step.acceptance:
|
|
93
|
+
lines.append(f" - Done when: {step.acceptance}")
|
|
94
|
+
return "\n".join(lines)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
#: What a Role is told when ADR-0007's gate is shut. The posture itself lives in
|
|
98
|
+
#: the argument vector; this is only what to do on hitting it. Without it the
|
|
99
|
+
#: Agent does what the M1 acceptance run did -- trace the tests by hand and
|
|
100
|
+
#: report `completed`, which is the one failure a Run cannot detect.
|
|
101
|
+
DENIED_COMMANDS = """
|
|
102
|
+
## Commands
|
|
103
|
+
|
|
104
|
+
You cannot run commands in this Run. You may read and edit files, and nothing \
|
|
105
|
+
else. If a step's acceptance criterion asks you to run something, do not \
|
|
106
|
+
substitute reading for running: state in `detail` which criterion you could \
|
|
107
|
+
not verify, and escalate if that leaves the step unfinished. Reporting \
|
|
108
|
+
`completed` on a criterion you could not check is worse than stopping.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def build_prompt(
|
|
113
|
+
plan: Plan,
|
|
114
|
+
context: ContextPack,
|
|
115
|
+
cwd: Path,
|
|
116
|
+
role: Role = IMPLEMENTER,
|
|
117
|
+
allow_commands: bool = False,
|
|
118
|
+
) -> str:
|
|
119
|
+
constraints = ""
|
|
120
|
+
if plan.constraints:
|
|
121
|
+
constraints = "\n### Constraints\n\n" + "\n".join(f"- {c}" for c in plan.constraints) + "\n"
|
|
122
|
+
|
|
123
|
+
return PROMPT.format(
|
|
124
|
+
instructions=role.instructions,
|
|
125
|
+
summary=plan.summary.strip(),
|
|
126
|
+
steps=render_steps(plan),
|
|
127
|
+
constraints=constraints,
|
|
128
|
+
context=render_context_block(context),
|
|
129
|
+
cwd=cwd,
|
|
130
|
+
execution="" if allow_commands else DENIED_COMMANDS,
|
|
131
|
+
result_open=RESULT_OPEN,
|
|
132
|
+
result_close=RESULT_CLOSE,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Implementer:
|
|
137
|
+
"""One Provider invocation against a frozen Plan."""
|
|
138
|
+
|
|
139
|
+
def __init__(self, provider) -> None:
|
|
140
|
+
self.provider = provider
|
|
141
|
+
|
|
142
|
+
def run(
|
|
143
|
+
self,
|
|
144
|
+
*,
|
|
145
|
+
plan: Plan,
|
|
146
|
+
context: ContextPack,
|
|
147
|
+
cwd: Path,
|
|
148
|
+
role: Role = IMPLEMENTER,
|
|
149
|
+
tier: ModelTier | None = None,
|
|
150
|
+
) -> AgentResult:
|
|
151
|
+
tier = tier or role.tier
|
|
152
|
+
allow_commands = getattr(self.provider, "allow_commands", False)
|
|
153
|
+
return self.provider.invoke(
|
|
154
|
+
role=role,
|
|
155
|
+
prompt=build_prompt(plan, context, cwd, role, allow_commands=allow_commands),
|
|
156
|
+
context=context,
|
|
157
|
+
tier=tier,
|
|
158
|
+
cwd=cwd,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
__all__ = ["IMPLEMENTER", "INSTRUCTIONS", "Implementer", "build_prompt", "render_steps"]
|