Core architecture approach

How the library is structured internally — affects testability, extensibility, and TDD workflow

A

Protocol-first (Recommended)

Define typing.Protocol interfaces for every boundary. Implementations plug in. No inheritance coupling. Maximum TDD ergonomics.

class LLMProvider(Protocol):
    async def complete(self, messages, **kw) -> str: ...

class Agent(Protocol):
    async def run(self, task, ctx) -> AgentResult: ...

class Pattern(Protocol):
    async def execute(self, agents, task, ctx) -> SwarmResult: ...

# Test with simple mock — no mocking framework needed
class FakeProvider:
    async def complete(self, messages, **kw): return "ok"

Pros

  • TDD-native: mock = any object with right methods
  • Zero coupling between layers
  • Add providers/patterns without touching core

Cons

  • No IDE autocomplete on Protocol attrs (minor)
  • Slightly less familiar to beginners
B

ABC-first

Abstract base classes with template method pattern. Familiar Python OOP. Subclass to extend.

class Agent(ABC):
    @abstractmethod
    async def run(self, task, ctx) -> AgentResult: ...

    def before_run(self, task): pass   # hook
    def after_run(self, result): pass  # hook

class MyAgent(Agent):
    async def run(self, task, ctx):
        return AgentResult(content="done")

Pros

  • Familiar to most Python devs
  • Template hooks baked in
  • Good IDE support

Cons

  • Subclassing couples consumers to base class
  • Harder to mock in tests
  • Diamond inheritance issues in complex hierarchies
C

Dataclass + Functions (Functional)

Agents are dataclasses. Patterns are pure async functions. No classes to subclass.

@dataclass
class Agent:
    model: str
    role: str
    tools: list[Tool] = field(default_factory=list)
    run: Callable | None = None  # override fn

async def parallel_pattern(
    agents: list[Agent], task: str, ctx: SwarmContext
) -> SwarmResult:
    results = await asyncio.gather(*[a.run(task, ctx) for a in agents])

Pros

  • Simple, no inheritance
  • Trivial to serialize/deserialize
  • Functions easy to unit test

Cons

  • Less extensible for complex custom agents
  • Loses OOP ergonomics users expect from "Agent" class