-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmanager.py
More file actions
241 lines (200 loc) · 9.61 KB
/
manager.py
File metadata and controls
241 lines (200 loc) · 9.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
import json
import os
from textwrap import dedent, indent
from beeai_framework.agents.requirement import RequirementAgent
from beeai_framework.agents.requirement.prompts import (
RequirementAgentSystemPromptInput,
RequirementAgentTaskPromptInput,
)
from beeai_framework.agents.requirement.requirements.ask_permission import AskPermissionRequirement
from beeai_framework.agents.requirement.requirements.conditional import ConditionalRequirement
from beeai_framework.middleware.trajectory import GlobalTrajectoryMiddleware
from beeai_framework.template import PromptTemplate, PromptTemplateInput
from beeai_framework.tools import Tool
from github_issue_creator.agents.analyst import get_agent_analyst
from github_issue_creator.agents.writer import get_agent_writer
from github_issue_creator.tools.artifact_handoff import ArtifactHandoffTool, ArtifactStore
from github_issue_creator.tools.github_tools import create_repo_scoped_tool, get_tools_by_names
from github_issue_creator.tools.think_tool import SimpleThinkTool
from github_issue_creator.utils.artifact_middleware import ArtifactMiddleware
from github_issue_creator.utils.config import llm, session_manager
from github_issue_creator.utils.exceptions import ToolNotFoundError
async def get_agent_manager():
"""Create and configure the issue workflow management agent."""
tools = await session_manager.get_tools()
try:
tools = await get_tools_by_names(tools, ["issue_write", "list_issue_types", "list_label"])
issue_write = None
list_issue_types = None
list_label = None
for tool in tools:
if tool.name == "issue_write":
issue_write = await create_repo_scoped_tool(tool)
elif tool.name == "list_issue_types":
list_issue_types = await create_repo_scoped_tool(tool)
elif tool.name == "list_label":
list_label = await create_repo_scoped_tool(tool)
except ToolNotFoundError as e:
raise RuntimeError(f"Failed to configure the agent: {e}") from e
# Get issue types with fallback
fallback_types = [
{"name": "Feature", "description": "A request, idea, or new functionality."},
{"name": "Bug", "description": "An unexpected problem or behavior"},
]
try:
response = await list_issue_types.run(input={})
issue_types_data = json.loads(response) if response else fallback_types
except Exception:
# Fallback to default types on any error (including 404)
issue_types_data = fallback_types
issue_types_lines = [f"- {issue_type['name']}: {issue_type['description']}" for issue_type in issue_types_data]
issue_types_text = indent("\n".join(issue_types_lines), " ")
# Get labels with fallback
fallback_labels = []
try:
response = await list_label.run(input={})
# Parse nested response structure
response_data = json.loads(response.get_text_content())
# Extract text from first content block
text_content = response_data[0]["text"]
# Parse the JSON string inside
labels_response = json.loads(text_content)
# Extract labels array
labels_data = labels_response["labels"]
except Exception:
# Fallback to empty list on any error (including 404, parsing errors)
labels_data = fallback_labels
# Extract only name and description from each label
labels_lines = [f"- {label['name']}: {label.get('description', '')}" for label in labels_data]
labels_text = indent("\n".join(labels_lines), " ")
repository = os.getenv("GITHUB_REPOSITORY")
role = "helpful coordinator"
instruction = f"""\
As the Coordinator, your responsibilities include routing tasks to experts, managing processes sequentially, and handling all user-facing communication. You do not perform technical writing or reasoning yourself.
You work in the following repository: {repository}
## Operating Principles
- Manage the full lifecycle of a GitHub issue from user request to creation.
- Keep the user in control; never move forward without explicit consent.
- Communicate with the user only when a phase is complete or when experts request clarifications.
- Do not dispatch placeholder or deferred instructions (e.g., "HOLD", "wait until approval", "queue this"). Only issue tool calls that can execute immediately in the current phase.
## Working with Artifacts
- Experts may return artifact references like `<artifact id="draft_k3x9" />` containing large content (drafts, etc.).
- Artifacts are **immutable**—they cannot be modified, only replaced by creating a new one.
- To show the full content to the user: include the artifact tag in your `final_answer` (it will auto-expand).
- To request changes: pass the artifact tag back to an expert along with change instructions (they see the full content and create a new artifact).
- Never create artifact tags yourself—only use what experts return.
## Phases
### 1. Draft
- Action: call `transfer_to_writer`.
- Do not add, expand, interpret, or restructure the user’s request yourself.
- If the writer asks for clarification, relay the question verbatim to the user.
- Relay policy for drafts:
- Return the writer's draft to the user **exactly as received**.
- Place your questions/notes **outside** the fence.
### 2. Review / Approval
- Action: call `final_answer` to share the draft exactly as received and ask: "Approve as-is, or request changes?"
- If changes are requested, return to **Draft**.
- Treat any of these as explicit approval: “approve”, “approved”, “looks good”, “LGTM”, “ship it”, “create it”, “go ahead”, “proceed”, “yes, create”.
### 3. Duplicate Check
- After approval, call `transfer_to_analyst` to search for similar issues.
- If duplicates found: let user decide to stop or continue.
- If unclear: ask user for refined search terms.
### 4. Create
- Only after explicit user confirmation, call `issue_write`.
- When creating the issue:
- Use the first line inside the fenced block ([Feature]: ..., [Bug]: ..., etc.) as the issue title.
- Remove that first line from the body so it does not appear twice.
- Keep the remaining markdown inside the body exactly as written (do not expand, reformat, or add text).
- Select appropriate type from available issue types:
{issue_types_text}
- Select appropriate labels from available labels:
{labels_text}
- Then send brief confirmation with link/ID via `final_answer`.
## Output Rules
- Tone: professional, neutral, concise, and actionable.
## Reasoning Discipline
- Do not summarize, expand, or rewrite expert output.
- Do not anticipate clarifications yourself. Relay them only if explicitly requested by an expert.
- If the next step is to communicate with the user, **call `final_answer` now** (do not call other tools or pre-stage future work).
## Guardrails
- It is acceptable to remain in a phase across multiple messages until ready to proceed.
- Attempt a first pass autonomously unless critical input is missing; if so, stop and request clarification before proceeding.
"""
# Create shared artifact store
artifact_store = ArtifactStore()
# Get the specialized agents
writer = await get_agent_writer()
analyst = await get_agent_analyst()
# Use artifact handoff for writer (drafts can be large)
handoff_writer = ArtifactHandoffTool(
target=writer,
artifact_store=artifact_store,
name="transfer_to_writer",
description="Assign to Technical Writer for drafting.",
propagate_inputs=False,
)
# Analyst needs full draft to search for duplicates
handoff_analyst = ArtifactHandoffTool(
target=analyst,
artifact_store=artifact_store,
name="transfer_to_analyst",
description="Assign to Analyst for duplicate issue search.",
propagate_inputs=False,
reveal_policy="full", # Analyst sees full draft content
)
template = dedent(
"""\
# Role
Assume the role of {{role}}.
# Instructions
{{#instructions}}
{{&.}}
{{/instructions}}
{{#final_answer_schema}}
The final answer must fulfill the following.
```
{{&final_answer_schema}}
```
{{/final_answer_schema}}
{{#final_answer_instructions}}
{{&final_answer_instructions}}
{{/final_answer_instructions}}
IMPORTANT: The facts mentioned in the final answer must be backed by evidence provided by relevant tool outputs.
# Tools
Never use the tool twice with the same input if not stated otherwise.
{{#tools.0}}
{{#tools}}
Name: {{name}}
Description: {{description}}
{{/tools}}
{{/tools.0}}
{{#notes}}
{{&.}}
{{/notes}}
""",
)
return RequirementAgent(
name="Project Manager",
llm=llm,
role=role,
instructions=instruction,
tools=[
SimpleThinkTool(),
handoff_writer,
handoff_analyst,
issue_write,
],
requirements=[
ConditionalRequirement(SimpleThinkTool, force_at_step=1, force_after=[Tool], consecutive_allowed=False),
AskPermissionRequirement(issue_write),
],
templates={
"system": PromptTemplate(PromptTemplateInput(schema=RequirementAgentSystemPromptInput, template=template)),
"task": PromptTemplate(PromptTemplateInput(schema=RequirementAgentTaskPromptInput, template="{{prompt}}")),
},
save_intermediate_steps=False,
middlewares=[
ArtifactMiddleware(artifact_store),
GlobalTrajectoryMiddleware(included=[Tool]),
],
)