emdash-core 0.1.25__py3-none-any.whl → 0.1.33__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.
- emdash_core/agent/__init__.py +4 -0
- emdash_core/agent/events.py +42 -20
- emdash_core/agent/inprocess_subagent.py +123 -10
- emdash_core/agent/prompts/__init__.py +4 -3
- emdash_core/agent/prompts/main_agent.py +32 -2
- emdash_core/agent/prompts/plan_mode.py +236 -107
- emdash_core/agent/prompts/subagents.py +79 -15
- emdash_core/agent/prompts/workflow.py +145 -26
- emdash_core/agent/providers/factory.py +2 -2
- emdash_core/agent/providers/openai_provider.py +67 -15
- emdash_core/agent/runner/__init__.py +49 -0
- emdash_core/agent/runner/agent_runner.py +753 -0
- emdash_core/agent/runner/context.py +451 -0
- emdash_core/agent/runner/factory.py +108 -0
- emdash_core/agent/runner/plan.py +217 -0
- emdash_core/agent/runner/sdk_runner.py +324 -0
- emdash_core/agent/runner/utils.py +67 -0
- emdash_core/agent/skills.py +47 -8
- emdash_core/agent/toolkit.py +46 -14
- emdash_core/agent/toolkits/plan.py +9 -11
- emdash_core/agent/tools/__init__.py +2 -2
- emdash_core/agent/tools/coding.py +48 -4
- emdash_core/agent/tools/modes.py +151 -143
- emdash_core/agent/tools/task.py +41 -2
- emdash_core/api/agent.py +555 -1
- emdash_core/skills/frontend-design/SKILL.md +56 -0
- emdash_core/sse/stream.py +4 -0
- {emdash_core-0.1.25.dist-info → emdash_core-0.1.33.dist-info}/METADATA +2 -1
- {emdash_core-0.1.25.dist-info → emdash_core-0.1.33.dist-info}/RECORD +31 -24
- emdash_core/agent/runner.py +0 -1123
- {emdash_core-0.1.25.dist-info → emdash_core-0.1.33.dist-info}/WHEEL +0 -0
- {emdash_core-0.1.25.dist-info → emdash_core-0.1.33.dist-info}/entry_points.txt +0 -0
emdash_core/agent/__init__.py
CHANGED
|
@@ -19,6 +19,9 @@ def __getattr__(name: str):
|
|
|
19
19
|
elif name == "AgentRunner":
|
|
20
20
|
from .runner import AgentRunner
|
|
21
21
|
return AgentRunner
|
|
22
|
+
elif name == "SafeJSONEncoder":
|
|
23
|
+
from .runner import SafeJSONEncoder
|
|
24
|
+
return SafeJSONEncoder
|
|
22
25
|
elif name == "ToolResult":
|
|
23
26
|
from .tools.base import ToolResult
|
|
24
27
|
return ToolResult
|
|
@@ -32,6 +35,7 @@ __all__ = [
|
|
|
32
35
|
"AgentToolkit",
|
|
33
36
|
"AgentSession",
|
|
34
37
|
"AgentRunner",
|
|
38
|
+
"SafeJSONEncoder",
|
|
35
39
|
"ToolResult",
|
|
36
40
|
"ToolCategory",
|
|
37
41
|
]
|
emdash_core/agent/events.py
CHANGED
|
@@ -17,6 +17,10 @@ class EventType(Enum):
|
|
|
17
17
|
TOOL_START = "tool_start"
|
|
18
18
|
TOOL_RESULT = "tool_result"
|
|
19
19
|
|
|
20
|
+
# Sub-agent lifecycle
|
|
21
|
+
SUBAGENT_START = "subagent_start"
|
|
22
|
+
SUBAGENT_END = "subagent_end"
|
|
23
|
+
|
|
20
24
|
# Agent thinking/progress
|
|
21
25
|
THINKING = "thinking"
|
|
22
26
|
PROGRESS = "progress"
|
|
@@ -24,10 +28,12 @@ class EventType(Enum):
|
|
|
24
28
|
# Output
|
|
25
29
|
RESPONSE = "response"
|
|
26
30
|
PARTIAL_RESPONSE = "partial_response"
|
|
31
|
+
ASSISTANT_TEXT = "assistant_text" # Intermediate text between tool calls
|
|
27
32
|
|
|
28
33
|
# Interaction
|
|
29
34
|
CLARIFICATION = "clarification"
|
|
30
35
|
CLARIFICATION_RESPONSE = "clarification_response"
|
|
36
|
+
PLAN_MODE_REQUESTED = "plan_mode_requested"
|
|
31
37
|
PLAN_SUBMITTED = "plan_submitted"
|
|
32
38
|
|
|
33
39
|
# Errors
|
|
@@ -144,16 +150,23 @@ class AgentEventEmitter:
|
|
|
144
150
|
|
|
145
151
|
return event
|
|
146
152
|
|
|
147
|
-
def emit_tool_start(
|
|
153
|
+
def emit_tool_start(
|
|
154
|
+
self,
|
|
155
|
+
name: str,
|
|
156
|
+
args: dict[str, Any] | None = None,
|
|
157
|
+
tool_id: str | None = None,
|
|
158
|
+
) -> AgentEvent:
|
|
148
159
|
"""Convenience method to emit a tool start event.
|
|
149
160
|
|
|
150
161
|
Args:
|
|
151
162
|
name: Tool name
|
|
152
163
|
args: Tool arguments
|
|
164
|
+
tool_id: Unique ID for this tool call (for matching with result)
|
|
153
165
|
"""
|
|
154
166
|
return self.emit(EventType.TOOL_START, {
|
|
155
167
|
"name": name,
|
|
156
168
|
"args": args or {},
|
|
169
|
+
"tool_id": tool_id,
|
|
157
170
|
})
|
|
158
171
|
|
|
159
172
|
def emit_tool_result(
|
|
@@ -162,6 +175,7 @@ class AgentEventEmitter:
|
|
|
162
175
|
success: bool,
|
|
163
176
|
summary: str | None = None,
|
|
164
177
|
data: dict[str, Any] | None = None,
|
|
178
|
+
tool_id: str | None = None,
|
|
165
179
|
) -> AgentEvent:
|
|
166
180
|
"""Convenience method to emit a tool result event.
|
|
167
181
|
|
|
@@ -170,12 +184,14 @@ class AgentEventEmitter:
|
|
|
170
184
|
success: Whether the tool succeeded
|
|
171
185
|
summary: Brief summary of the result
|
|
172
186
|
data: Full result data (may be truncated by handlers)
|
|
187
|
+
tool_id: Unique ID for this tool call (for matching with start)
|
|
173
188
|
"""
|
|
174
189
|
return self.emit(EventType.TOOL_RESULT, {
|
|
175
190
|
"name": name,
|
|
176
191
|
"success": success,
|
|
177
192
|
"summary": summary,
|
|
178
193
|
"data": data,
|
|
194
|
+
"tool_id": tool_id,
|
|
179
195
|
})
|
|
180
196
|
|
|
181
197
|
def emit_thinking(self, message: str) -> AgentEvent:
|
|
@@ -208,6 +224,14 @@ class AgentEventEmitter:
|
|
|
208
224
|
event_type = EventType.RESPONSE if is_final else EventType.PARTIAL_RESPONSE
|
|
209
225
|
return self.emit(event_type, {"content": content})
|
|
210
226
|
|
|
227
|
+
def emit_assistant_text(self, content: str) -> AgentEvent:
|
|
228
|
+
"""Emit intermediate assistant text (shown between tool calls).
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
content: Text content from assistant (e.g., "Let me read the file...")
|
|
232
|
+
"""
|
|
233
|
+
return self.emit(EventType.ASSISTANT_TEXT, {"content": content})
|
|
234
|
+
|
|
211
235
|
def emit_clarification(
|
|
212
236
|
self,
|
|
213
237
|
question: str,
|
|
@@ -227,32 +251,30 @@ class AgentEventEmitter:
|
|
|
227
251
|
"options": options,
|
|
228
252
|
})
|
|
229
253
|
|
|
230
|
-
def
|
|
254
|
+
def emit_plan_mode_requested(
|
|
231
255
|
self,
|
|
232
|
-
|
|
233
|
-
summary: str,
|
|
234
|
-
files_to_modify: list[dict] | None = None,
|
|
235
|
-
implementation_steps: list[str] | None = None,
|
|
236
|
-
risks: list[str] | None = None,
|
|
237
|
-
testing_strategy: str | None = None,
|
|
256
|
+
reason: str,
|
|
238
257
|
) -> AgentEvent:
|
|
258
|
+
"""Convenience method to emit a plan mode request event.
|
|
259
|
+
|
|
260
|
+
This is emitted when the agent calls enter_plan_mode tool,
|
|
261
|
+
requesting user consent to enter plan mode.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
reason: Why the agent wants to enter plan mode
|
|
265
|
+
"""
|
|
266
|
+
return self.emit(EventType.PLAN_MODE_REQUESTED, {
|
|
267
|
+
"reason": reason,
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
def emit_plan_submitted(self, plan: str) -> AgentEvent:
|
|
239
271
|
"""Convenience method to emit a plan submission event.
|
|
240
272
|
|
|
241
273
|
Args:
|
|
242
|
-
|
|
243
|
-
summary: Plan summary
|
|
244
|
-
files_to_modify: List of files with path, lines, changes
|
|
245
|
-
implementation_steps: Ordered implementation steps
|
|
246
|
-
risks: Potential risks or considerations
|
|
247
|
-
testing_strategy: How changes will be tested
|
|
274
|
+
plan: The implementation plan as markdown
|
|
248
275
|
"""
|
|
249
276
|
return self.emit(EventType.PLAN_SUBMITTED, {
|
|
250
|
-
"
|
|
251
|
-
"summary": summary,
|
|
252
|
-
"files_to_modify": files_to_modify or [],
|
|
253
|
-
"implementation_steps": implementation_steps or [],
|
|
254
|
-
"risks": risks or [],
|
|
255
|
-
"testing_strategy": testing_strategy or "",
|
|
277
|
+
"plan": plan,
|
|
256
278
|
})
|
|
257
279
|
|
|
258
280
|
def emit_error(self, message: str, details: str | None = None) -> AgentEvent:
|
|
@@ -62,6 +62,7 @@ class InProcessSubAgent:
|
|
|
62
62
|
model: Optional[str] = None,
|
|
63
63
|
max_turns: int = 10,
|
|
64
64
|
agent_id: Optional[str] = None,
|
|
65
|
+
thoroughness: str = "medium",
|
|
65
66
|
):
|
|
66
67
|
"""Initialize in-process sub-agent.
|
|
67
68
|
|
|
@@ -72,12 +73,14 @@ class InProcessSubAgent:
|
|
|
72
73
|
model: Model to use (defaults to fast model)
|
|
73
74
|
max_turns: Maximum iterations
|
|
74
75
|
agent_id: Optional agent ID (generated if not provided)
|
|
76
|
+
thoroughness: Search thoroughness level (quick, medium, thorough)
|
|
75
77
|
"""
|
|
76
78
|
self.subagent_type = subagent_type
|
|
77
79
|
self.repo_root = repo_root.resolve()
|
|
78
80
|
self.emitter = emitter
|
|
79
81
|
self.max_turns = max_turns
|
|
80
82
|
self.agent_id = agent_id or str(uuid.uuid4())[:8]
|
|
83
|
+
self.thoroughness = thoroughness
|
|
81
84
|
|
|
82
85
|
# Get toolkit for this agent type
|
|
83
86
|
self.toolkit = get_toolkit(subagent_type, repo_root)
|
|
@@ -86,13 +89,40 @@ class InProcessSubAgent:
|
|
|
86
89
|
model_name = model or DEFAULT_MODEL
|
|
87
90
|
self.provider = get_provider(model_name)
|
|
88
91
|
|
|
89
|
-
# Get system prompt
|
|
90
|
-
|
|
92
|
+
# Get system prompt and inject thoroughness level
|
|
93
|
+
base_prompt = get_subagent_prompt(subagent_type)
|
|
94
|
+
self.system_prompt = self._inject_thoroughness(base_prompt)
|
|
91
95
|
|
|
92
96
|
# Tracking
|
|
93
97
|
self.files_explored: set[str] = set()
|
|
94
98
|
self.tools_used: list[str] = []
|
|
95
99
|
|
|
100
|
+
def _inject_thoroughness(self, prompt: str) -> str:
|
|
101
|
+
"""Inject thoroughness level into the system prompt."""
|
|
102
|
+
thoroughness_guidance = {
|
|
103
|
+
"quick": """
|
|
104
|
+
## Thoroughness Level: QUICK
|
|
105
|
+
- Do basic searches only - find the most obvious matches first
|
|
106
|
+
- Stop after finding 2-3 relevant files
|
|
107
|
+
- Don't explore deeply - just locate the key files
|
|
108
|
+
- Prioritize speed over completeness""",
|
|
109
|
+
"medium": """
|
|
110
|
+
## Thoroughness Level: MEDIUM
|
|
111
|
+
- Do moderate exploration - check multiple locations
|
|
112
|
+
- Follow 1-2 levels of imports/references
|
|
113
|
+
- Balance speed with coverage
|
|
114
|
+
- Stop when you have reasonable confidence""",
|
|
115
|
+
"thorough": """
|
|
116
|
+
## Thoroughness Level: THOROUGH
|
|
117
|
+
- Do comprehensive analysis across the codebase
|
|
118
|
+
- Check multiple naming conventions and locations
|
|
119
|
+
- Follow import chains and cross-references deeply
|
|
120
|
+
- Explore edge cases and alternative implementations
|
|
121
|
+
- Only stop when you've exhausted relevant areas""",
|
|
122
|
+
}
|
|
123
|
+
guidance = thoroughness_guidance.get(self.thoroughness, thoroughness_guidance["medium"])
|
|
124
|
+
return prompt + "\n" + guidance
|
|
125
|
+
|
|
96
126
|
def _emit(self, event_type: str, **data) -> None:
|
|
97
127
|
"""Emit event with agent tagging.
|
|
98
128
|
|
|
@@ -115,6 +145,50 @@ class InProcessSubAgent:
|
|
|
115
145
|
if event_type in event_map:
|
|
116
146
|
self.emitter.emit(event_map[event_type], data)
|
|
117
147
|
|
|
148
|
+
def _get_project_context(self) -> str:
|
|
149
|
+
"""Get PROJECT.md and directory structure for context."""
|
|
150
|
+
context_parts = []
|
|
151
|
+
|
|
152
|
+
# Try to read PROJECT.md
|
|
153
|
+
project_md = self.repo_root / "PROJECT.md"
|
|
154
|
+
if project_md.exists():
|
|
155
|
+
try:
|
|
156
|
+
content = project_md.read_text()
|
|
157
|
+
# Truncate if too long
|
|
158
|
+
if len(content) > 8000:
|
|
159
|
+
content = content[:8000] + "\n...[truncated]"
|
|
160
|
+
context_parts.append(f"## PROJECT.md\n\n{content}")
|
|
161
|
+
except Exception as e:
|
|
162
|
+
log.debug(f"Could not read PROJECT.md: {e}")
|
|
163
|
+
|
|
164
|
+
# Get directory structure (top 2 levels)
|
|
165
|
+
try:
|
|
166
|
+
structure_lines = ["## Project Structure\n"]
|
|
167
|
+
for item in sorted(self.repo_root.iterdir()):
|
|
168
|
+
if item.name.startswith(".") and item.name not in (".emdash",):
|
|
169
|
+
continue
|
|
170
|
+
if item.name in ("node_modules", "__pycache__", ".git", "dist", "build", ".venv", "venv"):
|
|
171
|
+
continue
|
|
172
|
+
if item.is_dir():
|
|
173
|
+
structure_lines.append(f" {item.name}/")
|
|
174
|
+
# Show first level contents
|
|
175
|
+
try:
|
|
176
|
+
for subitem in sorted(item.iterdir())[:10]:
|
|
177
|
+
if not subitem.name.startswith("."):
|
|
178
|
+
suffix = "/" if subitem.is_dir() else ""
|
|
179
|
+
structure_lines.append(f" {subitem.name}{suffix}")
|
|
180
|
+
if len(list(item.iterdir())) > 10:
|
|
181
|
+
structure_lines.append(f" ...")
|
|
182
|
+
except PermissionError:
|
|
183
|
+
pass
|
|
184
|
+
else:
|
|
185
|
+
structure_lines.append(f" {item.name}")
|
|
186
|
+
context_parts.append("\n".join(structure_lines))
|
|
187
|
+
except Exception as e:
|
|
188
|
+
log.debug(f"Could not get directory structure: {e}")
|
|
189
|
+
|
|
190
|
+
return "\n\n".join(context_parts) if context_parts else ""
|
|
191
|
+
|
|
118
192
|
def run(self, prompt: str) -> SubAgentResult:
|
|
119
193
|
"""Execute the task and return results.
|
|
120
194
|
|
|
@@ -130,6 +204,19 @@ class InProcessSubAgent:
|
|
|
130
204
|
last_content = ""
|
|
131
205
|
error = None
|
|
132
206
|
|
|
207
|
+
# For Plan agents, inject project context
|
|
208
|
+
if self.subagent_type == "Plan":
|
|
209
|
+
context = self._get_project_context()
|
|
210
|
+
if context:
|
|
211
|
+
prompt = f"""Here is context about the project:
|
|
212
|
+
|
|
213
|
+
{context}
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
Now, your task:
|
|
218
|
+
{prompt}"""
|
|
219
|
+
|
|
133
220
|
# Add user message
|
|
134
221
|
messages.append({"role": "user", "content": prompt})
|
|
135
222
|
|
|
@@ -265,6 +352,7 @@ def run_subagent(
|
|
|
265
352
|
emitter=None,
|
|
266
353
|
model: Optional[str] = None,
|
|
267
354
|
max_turns: int = 10,
|
|
355
|
+
thoroughness: str = "medium",
|
|
268
356
|
) -> SubAgentResult:
|
|
269
357
|
"""Run a sub-agent synchronously.
|
|
270
358
|
|
|
@@ -275,18 +363,38 @@ def run_subagent(
|
|
|
275
363
|
emitter: Event emitter
|
|
276
364
|
model: Model to use
|
|
277
365
|
max_turns: Max iterations
|
|
366
|
+
thoroughness: Search thoroughness level (quick, medium, thorough)
|
|
278
367
|
|
|
279
368
|
Returns:
|
|
280
369
|
SubAgentResult
|
|
281
370
|
"""
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
371
|
+
try:
|
|
372
|
+
agent = InProcessSubAgent(
|
|
373
|
+
subagent_type=subagent_type,
|
|
374
|
+
repo_root=repo_root,
|
|
375
|
+
emitter=emitter,
|
|
376
|
+
model=model,
|
|
377
|
+
max_turns=max_turns,
|
|
378
|
+
thoroughness=thoroughness,
|
|
379
|
+
)
|
|
380
|
+
return agent.run(prompt)
|
|
381
|
+
except Exception as e:
|
|
382
|
+
# Return a proper error result instead of letting the exception propagate
|
|
383
|
+
# This prevents 0.0s "silent" failures and gives clear error messages
|
|
384
|
+
log.error(f"Failed to create sub-agent: {e}")
|
|
385
|
+
return SubAgentResult(
|
|
386
|
+
success=False,
|
|
387
|
+
agent_type=subagent_type,
|
|
388
|
+
agent_id="init-failed",
|
|
389
|
+
task=prompt,
|
|
390
|
+
summary="",
|
|
391
|
+
files_explored=[],
|
|
392
|
+
findings=[],
|
|
393
|
+
iterations=0,
|
|
394
|
+
tools_used=[],
|
|
395
|
+
execution_time=0.0,
|
|
396
|
+
error=f"Sub-agent initialization failed: {e}",
|
|
397
|
+
)
|
|
290
398
|
|
|
291
399
|
|
|
292
400
|
def run_subagent_async(
|
|
@@ -296,6 +404,7 @@ def run_subagent_async(
|
|
|
296
404
|
emitter=None,
|
|
297
405
|
model: Optional[str] = None,
|
|
298
406
|
max_turns: int = 10,
|
|
407
|
+
thoroughness: str = "medium",
|
|
299
408
|
) -> Future[SubAgentResult]:
|
|
300
409
|
"""Run a sub-agent asynchronously (returns Future).
|
|
301
410
|
|
|
@@ -306,6 +415,7 @@ def run_subagent_async(
|
|
|
306
415
|
emitter: Event emitter
|
|
307
416
|
model: Model to use
|
|
308
417
|
max_turns: Max iterations
|
|
418
|
+
thoroughness: Search thoroughness level (quick, medium, thorough)
|
|
309
419
|
|
|
310
420
|
Returns:
|
|
311
421
|
Future[SubAgentResult] - call .result() to get result
|
|
@@ -319,6 +429,7 @@ def run_subagent_async(
|
|
|
319
429
|
emitter=emitter,
|
|
320
430
|
model=model,
|
|
321
431
|
max_turns=max_turns,
|
|
432
|
+
thoroughness=thoroughness,
|
|
322
433
|
)
|
|
323
434
|
|
|
324
435
|
|
|
@@ -335,6 +446,7 @@ def run_subagents_parallel(
|
|
|
335
446
|
- prompt: str
|
|
336
447
|
- model: str (optional)
|
|
337
448
|
- max_turns: int (optional)
|
|
449
|
+
- thoroughness: str (optional, default "medium")
|
|
338
450
|
repo_root: Repository root
|
|
339
451
|
emitter: Shared event emitter
|
|
340
452
|
|
|
@@ -350,6 +462,7 @@ def run_subagents_parallel(
|
|
|
350
462
|
emitter=emitter,
|
|
351
463
|
model=task.get("model"),
|
|
352
464
|
max_turns=task.get("max_turns", 10),
|
|
465
|
+
thoroughness=task.get("thoroughness", "medium"),
|
|
353
466
|
)
|
|
354
467
|
futures.append(future)
|
|
355
468
|
|
|
@@ -15,11 +15,12 @@ from .workflow import (
|
|
|
15
15
|
)
|
|
16
16
|
from .main_agent import (
|
|
17
17
|
BASE_SYSTEM_PROMPT,
|
|
18
|
+
CODE_MODE_PROMPT,
|
|
19
|
+
PLAN_MODE_PROMPT,
|
|
18
20
|
build_system_prompt,
|
|
19
21
|
build_tools_section,
|
|
20
22
|
)
|
|
21
23
|
from .subagents import SUBAGENT_PROMPTS, get_subagent_prompt
|
|
22
|
-
from .plan_mode import PLAN_MODE_PROMPT
|
|
23
24
|
|
|
24
25
|
__all__ = [
|
|
25
26
|
# Workflow patterns
|
|
@@ -33,11 +34,11 @@ __all__ = [
|
|
|
33
34
|
"PARALLEL_EXECUTION",
|
|
34
35
|
# Main agent
|
|
35
36
|
"BASE_SYSTEM_PROMPT",
|
|
37
|
+
"CODE_MODE_PROMPT",
|
|
38
|
+
"PLAN_MODE_PROMPT",
|
|
36
39
|
"build_system_prompt",
|
|
37
40
|
"build_tools_section",
|
|
38
41
|
# Sub-agents
|
|
39
42
|
"SUBAGENT_PROMPTS",
|
|
40
43
|
"get_subagent_prompt",
|
|
41
|
-
# Plan mode
|
|
42
|
-
"PLAN_MODE_PROMPT",
|
|
43
44
|
]
|
|
@@ -9,13 +9,22 @@ from .workflow import (
|
|
|
9
9
|
EXPLORATION_STRATEGY,
|
|
10
10
|
OUTPUT_GUIDELINES,
|
|
11
11
|
PARALLEL_EXECUTION,
|
|
12
|
+
TODO_LIST_GUIDANCE,
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
# Base system prompt template with placeholder for tools
|
|
15
|
-
|
|
16
|
+
_BASE_PROMPT = """You are a code exploration and implementation assistant. You orchestrate focused sub-agents for exploration while maintaining the high-level view.
|
|
16
17
|
|
|
17
18
|
{tools_section}
|
|
18
|
-
"""
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Main agent system prompt - same for both code and plan modes
|
|
22
|
+
# Main agent is always an orchestrator that delegates to subagents
|
|
23
|
+
BASE_SYSTEM_PROMPT = _BASE_PROMPT + WORKFLOW_PATTERNS + PARALLEL_EXECUTION + EXPLORATION_STRATEGY + TODO_LIST_GUIDANCE + OUTPUT_GUIDELINES
|
|
24
|
+
|
|
25
|
+
# Legacy aliases
|
|
26
|
+
CODE_MODE_PROMPT = BASE_SYSTEM_PROMPT
|
|
27
|
+
PLAN_MODE_PROMPT = BASE_SYSTEM_PROMPT
|
|
19
28
|
|
|
20
29
|
|
|
21
30
|
def build_system_prompt(toolkit) -> str:
|
|
@@ -29,8 +38,15 @@ def build_system_prompt(toolkit) -> str:
|
|
|
29
38
|
"""
|
|
30
39
|
tools_section = build_tools_section(toolkit)
|
|
31
40
|
skills_section = build_skills_section()
|
|
41
|
+
rules_section = build_rules_section()
|
|
42
|
+
|
|
43
|
+
# Main agent always uses the same prompt - it orchestrates and delegates
|
|
32
44
|
prompt = BASE_SYSTEM_PROMPT.format(tools_section=tools_section)
|
|
33
45
|
|
|
46
|
+
# Add rules section if there are rules defined
|
|
47
|
+
if rules_section:
|
|
48
|
+
prompt += "\n" + rules_section
|
|
49
|
+
|
|
34
50
|
# Add skills section if there are skills available
|
|
35
51
|
if skills_section:
|
|
36
52
|
prompt += "\n" + skills_section
|
|
@@ -38,6 +54,20 @@ def build_system_prompt(toolkit) -> str:
|
|
|
38
54
|
return prompt
|
|
39
55
|
|
|
40
56
|
|
|
57
|
+
def build_rules_section() -> str:
|
|
58
|
+
"""Build the rules section of the system prompt.
|
|
59
|
+
|
|
60
|
+
Loads rules from .emdash/rules/*.md files.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Formatted string with project rules, or empty string if none
|
|
64
|
+
"""
|
|
65
|
+
from ..rules import load_rules, format_rules_for_prompt
|
|
66
|
+
|
|
67
|
+
rules = load_rules()
|
|
68
|
+
return format_rules_for_prompt(rules)
|
|
69
|
+
|
|
70
|
+
|
|
41
71
|
def build_skills_section() -> str:
|
|
42
72
|
"""Build the skills section of the system prompt.
|
|
43
73
|
|