fableforge-agent-swarm 0.1.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.
@@ -0,0 +1,41 @@
1
+ """AgentSwarm — Orchestrate micro-agent swarms using Markov transition matrices."""
2
+
3
+ from agent_swarm.orchestrator import SwarmOrchestrator, SwarmResult, SwarmStatus
4
+ from agent_swarm.transition_matrix import TransitionMatrix, ToolCall, HandoffPattern
5
+ from agent_swarm.agents import (
6
+ ReaderAgent,
7
+ EditorAgent,
8
+ BashAgent,
9
+ VerifierAgent,
10
+ PlannerAgent,
11
+ BaseAgent,
12
+ AgentRole,
13
+ create_agent,
14
+ )
15
+ from agent_swarm.models import (
16
+ AgentConfig,
17
+ SwarmResult as SwarmResultPydantic,
18
+ HandoffEvent,
19
+ AgentMessage as AgentMessagePydantic,
20
+ )
21
+
22
+ __version__ = "0.1.0"
23
+ __all__ = [
24
+ "SwarmOrchestrator",
25
+ "SwarmResult",
26
+ "SwarmStatus",
27
+ "TransitionMatrix",
28
+ "ToolCall",
29
+ "HandoffPattern",
30
+ "ReaderAgent",
31
+ "EditorAgent",
32
+ "BashAgent",
33
+ "VerifierAgent",
34
+ "PlannerAgent",
35
+ "BaseAgent",
36
+ "AgentRole",
37
+ "create_agent",
38
+ "AgentConfig",
39
+ "HandoffEvent",
40
+ "AgentMessagePydantic",
41
+ ]
agent_swarm/agents.py ADDED
@@ -0,0 +1,424 @@
1
+ """Specialized micro-agents for the agent swarm."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from enum import Enum
7
+ from typing import Any
8
+
9
+ from agent_swarm.models import AgentConfig, AgentRole as PydanticAgentRole
10
+
11
+
12
+ class AgentRole(str, Enum):
13
+ """Roles that micro-agents can assume."""
14
+
15
+ READER = "reader"
16
+ EDITOR = "editor"
17
+ BASH = "bash"
18
+ VERIFIER = "verifier"
19
+ PLANNER = "planner"
20
+
21
+
22
+ @dataclass
23
+ class ToolDef:
24
+ """Definition of a tool available to an agent."""
25
+
26
+ name: str
27
+ description: str
28
+ parameters: dict[str, Any] = field(default_factory=dict)
29
+
30
+
31
+ @dataclass
32
+ class AgentMessage:
33
+ """A message in the agent swarm conversation."""
34
+
35
+ role: str
36
+ content: str
37
+ tool_calls: list[dict[str, Any]] = field(default_factory=list)
38
+ metadata: dict[str, Any] = field(default_factory=dict)
39
+
40
+
41
+ READER_TOOLS = [
42
+ ToolDef(name="read", description="Read file contents at a given path", parameters={"path": {"type": "string"}}),
43
+ ToolDef(name="grep", description="Search file contents for a pattern", parameters={"pattern": {"type": "string"}, "path": {"type": "string"}}),
44
+ ToolDef(name="glob", description="Find files matching a pattern", parameters={"pattern": {"type": "string"}}),
45
+ ]
46
+
47
+ EDITOR_TOOLS = [
48
+ ToolDef(name="edit", description="Edit a file by replacing a string", parameters={"file_path": {"type": "string"}, "old_string": {"type": "string"}, "new_string": {"type": "string"}}),
49
+ ToolDef(name="write", description="Write content to a file", parameters={"file_path": {"type": "string"}, "content": {"type": "string"}}),
50
+ ]
51
+
52
+ BASH_TOOLS = [
53
+ ToolDef(name="bash", description="Execute a shell command", parameters={"command": {"type": "string"}, "timeout": {"type": "integer"}}),
54
+ ]
55
+
56
+ VERIFIER_TOOLS = [
57
+ ToolDef(name="bash", description="Execute shell commands for testing", parameters={"command": {"type": "string"}}),
58
+ ToolDef(name="read", description="Read files to verify changes", parameters={"path": {"type": "string"}}),
59
+ ToolDef(name="grep", description="Search for patterns in files", parameters={"pattern": {"type": "string"}, "path": {"type": "string"}}),
60
+ ]
61
+
62
+ PLANNER_TOOLS = [
63
+ ToolDef(name="question", description="Ask the user a clarifying question", parameters={"question": {"type": "string"}}),
64
+ ToolDef(name="glob", description="Find files to understand project structure", parameters={"pattern": {"type": "string"}}),
65
+ ToolDef(name="read", description="Read files for context", parameters={"path": {"type": "string"}}),
66
+ ]
67
+
68
+ # System prompts for each agent role
69
+ SYSTEM_PROMPTS: dict[str, str] = {
70
+ "reader": (
71
+ "You are a Reader agent. Your job is to explore the codebase, "
72
+ "read files, search for patterns, and gather context. You do NOT "
73
+ "edit files or run commands. When you have enough context, hand off "
74
+ "to the Editor, Bash, or Verifier agent as appropriate.\n\n"
75
+ "Guidelines:\n"
76
+ "- Start by understanding the project structure\n"
77
+ "- Read relevant files thoroughly before handing off\n"
78
+ "- Use grep/glob to find relevant code\n"
79
+ "- Summarize your findings in the handoff context"
80
+ ),
81
+ "editor": (
82
+ "You are an Editor agent. Your job is to write and modify code. "
83
+ "You make precise, targeted edits. After editing, hand off to the "
84
+ "Verifier to check your work.\n\n"
85
+ "Guidelines:\n"
86
+ "- Make minimal, focused changes\n"
87
+ "- Preserve existing code style and conventions\n"
88
+ "- Add comments only when asked\n"
89
+ "- After editing, always hand off to the Verifier"
90
+ ),
91
+ "bash": (
92
+ "You are a Bash agent. Your job is to execute shell commands to "
93
+ "install dependencies, run tests, start servers, and perform "
94
+ "system operations. You do NOT edit files directly.\n\n"
95
+ "Guidelines:\n"
96
+ "- Always use absolute paths\n"
97
+ "- Set reasonable timeouts (60s default)\n"
98
+ "- Handle errors gracefully\n"
99
+ "- Report results clearly for the next agent"
100
+ ),
101
+ "verifier": (
102
+ "You are a Verifier agent. Your job is to validate that changes "
103
+ "work correctly by running tests, checking linting, and verifying "
104
+ "the code does what was intended.\n\n"
105
+ "Guidelines:\n"
106
+ "- Run the project's test suite first\n"
107
+ "- Check for type errors if a typechecker is available\n"
108
+ "- Run linters to catch style issues\n"
109
+ "- If tests fail, hand off to the Editor with specific details\n"
110
+ "- If all checks pass, report success"
111
+ ),
112
+ "planner": (
113
+ "You are a Planner agent. Your job is to break down tasks, define "
114
+ "the approach, and coordinate which agents should handle which steps. "
115
+ "You ask clarifying questions when requirements are ambiguous.\n\n"
116
+ "Guidelines:\n"
117
+ "- Break complex tasks into subtasks\n"
118
+ "- Assign subtasks to the most appropriate agent role\n"
119
+ "- Ask questions when requirements are unclear\n"
120
+ "- Track progress across agent handoffs"
121
+ ),
122
+ }
123
+
124
+
125
+ @dataclass
126
+ class BaseAgent:
127
+ """Base class for all micro-agents in the swarm.
128
+
129
+ Each agent has:
130
+ - A role defining its specialization
131
+ - A set of tools it can use
132
+ - A system prompt for LLM interactions
133
+ - A list of roles it can hand off to
134
+ - A context history of messages
135
+
136
+ Attributes:
137
+ role: The agent's specialization role.
138
+ tools: List of tool definitions available to this agent.
139
+ system_prompt: System prompt for LLM interactions.
140
+ can_handoff_to: List of roles this agent can transfer control to.
141
+ _context: Internal message history.
142
+ """
143
+
144
+ role: AgentRole
145
+ tools: list[ToolDef]
146
+ system_prompt: str
147
+ can_handoff_to: list[AgentRole]
148
+ _context: list[AgentMessage] = field(default_factory=list, repr=False)
149
+
150
+ def add_message(self, message: AgentMessage) -> None:
151
+ """Add a message to the agent's context history.
152
+
153
+ Args:
154
+ message: The message to add.
155
+ """
156
+ self._context.append(message)
157
+
158
+ def get_context(self) -> list[AgentMessage]:
159
+ """Return a copy of the agent's context history."""
160
+ return list(self._context)
161
+
162
+ def clear_context(self) -> None:
163
+ """Clear the agent's context history."""
164
+ self._context.clear()
165
+
166
+ def tool_names(self) -> list[str]:
167
+ """Return the names of available tools."""
168
+ return [t.name for t in self.tools]
169
+
170
+ def can_handle(self, tool_name: str) -> bool:
171
+ """Check if this agent can use the given tool.
172
+
173
+ Args:
174
+ tool_name: Name of the tool to check.
175
+
176
+ Returns:
177
+ True if the agent has access to this tool.
178
+ """
179
+ return tool_name in self.tool_names()
180
+
181
+ def to_dict(self) -> dict[str, Any]:
182
+ """Serialize the agent to a dictionary."""
183
+ return {
184
+ "role": self.role.value,
185
+ "tools": [t.name for t in self.tools],
186
+ "system_prompt": self.system_prompt,
187
+ "can_handoff_to": [r.value for r in self.can_handoff_to],
188
+ "context": [
189
+ {"role": m.role, "content": m.content, "metadata": m.metadata}
190
+ for m in self._context
191
+ ],
192
+ }
193
+
194
+ def get_config(self) -> AgentConfig:
195
+ """Get a Pydantic AgentConfig for this agent."""
196
+ return AgentConfig(
197
+ role=PydanticAgentRole(self.role.value),
198
+ system_prompt=self.system_prompt,
199
+ tools=[
200
+ AgentConfig.model_fields["tools"].annotation.__args__[0]( # type: ignore[attr-defined]
201
+ name=t.name,
202
+ description=t.description,
203
+ parameters=t.parameters,
204
+ )
205
+ ] if False else [], # Handled by for_role
206
+ can_handoff_to=[PydanticAgentRole(r.value) for r in self.can_handoff_to],
207
+ )
208
+
209
+ def execute(self, task: str, context: dict[str, Any] | None = None) -> dict[str, Any]:
210
+ """Execute a task within this agent's specialization.
211
+
212
+ This is a local execution method that processes the task based on
213
+ the agent's role and available tools. It does not call an LLM; instead
214
+ it records the execution plan and returns structured output.
215
+
216
+ For LLM-based execution, use execute_with_llm() which integrates
217
+ with litellm for actual model calls.
218
+
219
+ Args:
220
+ task: The task description to execute.
221
+ context: Optional context from previous agent handoffs.
222
+
223
+ Returns:
224
+ Dictionary with execution results including:
225
+ - role: The agent's role
226
+ - task: The task description
227
+ - plan: List of planned tool calls
228
+ - context: Enriched context for handoff
229
+ - status: Execution status
230
+ """
231
+ context = context or {}
232
+
233
+ # Determine primary tool based on role
234
+ role_primary_tools: dict[str, str] = {
235
+ "reader": "read",
236
+ "editor": "edit",
237
+ "bash": "bash",
238
+ "verifier": "bash",
239
+ "planner": "question",
240
+ }
241
+
242
+ primary_tool = role_primary_tools.get(self.role.value, "read")
243
+
244
+ # Build execution plan
245
+ plan = [ToolCall(name=primary_tool, confidence=1.0, args={"task": task})]
246
+
247
+ # Add secondary tools based on role
248
+ if self.role == AgentRole.READER:
249
+ plan.append(ToolCall(name="grep", confidence=0.8, args={"pattern": task}))
250
+ plan.append(ToolCall(name="glob", confidence=0.6, args={"pattern": "**/*"}))
251
+ elif self.role == AgentRole.EDITOR:
252
+ plan.append(ToolCall(name="write", confidence=0.7, args={"task": task}))
253
+ elif self.role == AgentRole.VERIFIER:
254
+ plan.append(ToolCall(name="read", confidence=0.8, args={"task": "verify output"}))
255
+
256
+ # Record execution in context
257
+ self.add_message(AgentMessage(
258
+ role="assistant",
259
+ content=f"[{self.role.value}] Executing: {task}",
260
+ metadata={"plan": [tc.to_dict() for tc in plan], **context},
261
+ ))
262
+
263
+ # Build handoff recommendations
264
+ recommended_handoff = None
265
+ if self.can_handoff_to:
266
+ # Pick the most likely handoff target based on role logic
267
+ handoff_priority: dict[str, list[str]] = {
268
+ "reader": ["editor", "verifier", "bash", "planner"],
269
+ "editor": ["verifier", "reader", "bash", "planner"],
270
+ "bash": ["reader", "editor", "verifier", "planner"],
271
+ "verifier": ["editor", "reader", "bash", "planner"],
272
+ "planner": ["reader", "editor", "bash", "verifier"],
273
+ }
274
+ priority_list = handoff_priority.get(self.role.value, [])
275
+ for target in priority_list:
276
+ target_role = AgentRole(target)
277
+ if target_role in self.can_handoff_to:
278
+ recommended_handoff = target_role.value
279
+ break
280
+
281
+ return {
282
+ "role": self.role.value,
283
+ "task": task,
284
+ "plan": [tc.to_dict() for tc in plan],
285
+ "context": {
286
+ **context,
287
+ "executed_by": self.role.value,
288
+ "primary_tool": primary_tool,
289
+ },
290
+ "recommended_handoff": recommended_handoff,
291
+ "status": "completed",
292
+ }
293
+
294
+
295
+ @dataclass
296
+ class ToolCall:
297
+ """A tool call planned by an agent."""
298
+
299
+ name: str
300
+ confidence: float = 1.0
301
+ args: dict[str, Any] = field(default_factory=dict)
302
+
303
+ def to_dict(self) -> dict[str, Any]:
304
+ return {
305
+ "name": self.name,
306
+ "confidence": self.confidence,
307
+ "args": self.args,
308
+ }
309
+
310
+
311
+ class ReaderAgent(BaseAgent):
312
+ """Agent specialized in reading and searching code.
313
+
314
+ The Reader explores the codebase, reads files, searches for patterns,
315
+ and gathers context. It hands off to Editor (to make changes),
316
+ Verifier (to validate), or Bash (to execute commands).
317
+
318
+ Transition data: Read→Edit=0.22, Read→Bash=0.37
319
+ """
320
+
321
+ def __init__(self) -> None:
322
+ super().__init__(
323
+ role=AgentRole.READER,
324
+ tools=READER_TOOLS,
325
+ system_prompt=SYSTEM_PROMPTS["reader"],
326
+ can_handoff_to=[AgentRole.EDITOR, AgentRole.BASH, AgentRole.VERIFIER, AgentRole.PLANNER],
327
+ )
328
+
329
+
330
+ class EditorAgent(BaseAgent):
331
+ """Agent specialized in writing and editing code.
332
+
333
+ The Editor makes precise, targeted edits based on context from
334
+ other agents. After editing, it typically hands off to the Verifier.
335
+
336
+ Transition data: Edit→Bash=0.34, Edit→Read=0.28
337
+ """
338
+
339
+ def __init__(self) -> None:
340
+ super().__init__(
341
+ role=AgentRole.EDITOR,
342
+ tools=EDITOR_TOOLS,
343
+ system_prompt=SYSTEM_PROMPTS["editor"],
344
+ can_handoff_to=[AgentRole.READER, AgentRole.BASH, AgentRole.VERIFIER, AgentRole.PLANNER],
345
+ )
346
+
347
+
348
+ class BashAgent(BaseAgent):
349
+ """Agent specialized in running shell commands.
350
+
351
+ The Bash agent executes system commands, runs tests, installs
352
+ dependencies, and reports results.
353
+
354
+ Transition data: Bash→Bash=0.59, Bash→Edit=0.18
355
+ """
356
+
357
+ def __init__(self) -> None:
358
+ super().__init__(
359
+ role=AgentRole.BASH,
360
+ tools=BASH_TOOLS,
361
+ system_prompt=SYSTEM_PROMPTS["bash"],
362
+ can_handoff_to=[AgentRole.READER, AgentRole.EDITOR, AgentRole.VERIFIER, AgentRole.PLANNER],
363
+ )
364
+
365
+
366
+ class VerifierAgent(BaseAgent):
367
+ """Agent specialized in verifying code changes and running tests.
368
+
369
+ The Verifier runs tests, linters, and type checkers to validate
370
+ changes made by the Editor.
371
+ """
372
+
373
+ def __init__(self) -> None:
374
+ super().__init__(
375
+ role=AgentRole.VERIFIER,
376
+ tools=VERIFIER_TOOLS,
377
+ system_prompt=SYSTEM_PROMPTS["verifier"],
378
+ can_handoff_to=[AgentRole.READER, AgentRole.EDITOR, AgentRole.BASH, AgentRole.PLANNER],
379
+ )
380
+
381
+
382
+ class PlannerAgent(BaseAgent):
383
+ """Agent specialized in planning and coordinating work.
384
+
385
+ The Planner breaks down tasks, defines approaches, and coordinates
386
+ which agents should handle which steps.
387
+ """
388
+
389
+ def __init__(self) -> None:
390
+ super().__init__(
391
+ role=AgentRole.PLANNER,
392
+ tools=PLANNER_TOOLS,
393
+ system_prompt=SYSTEM_PROMPTS["planner"],
394
+ can_handoff_to=[AgentRole.READER, AgentRole.EDITOR, AgentRole.BASH, AgentRole.VERIFIER],
395
+ )
396
+
397
+
398
+ AGENT_CLASSES: dict[AgentRole, type[BaseAgent]] = {
399
+ AgentRole.READER: ReaderAgent,
400
+ AgentRole.EDITOR: EditorAgent,
401
+ AgentRole.BASH: BashAgent,
402
+ AgentRole.VERIFIER: VerifierAgent,
403
+ AgentRole.PLANNER: PlannerAgent,
404
+ }
405
+
406
+
407
+ def create_agent(role: AgentRole | str) -> BaseAgent:
408
+ """Factory function to create an agent by role.
409
+
410
+ Args:
411
+ role: The agent role to create, either as an AgentRole enum or string.
412
+
413
+ Returns:
414
+ A new agent instance of the specified role.
415
+
416
+ Raises:
417
+ ValueError: If the role is not recognized.
418
+ """
419
+ if isinstance(role, str):
420
+ role = AgentRole(role)
421
+ agent_class = AGENT_CLASSES.get(role)
422
+ if agent_class is None:
423
+ raise ValueError(f"Unknown agent role: {role}. Available: {list(AGENT_CLASSES.keys())}")
424
+ return agent_class()