Files
webber/webber-api/src/domains/agents/base.py
T
jpmschweitzerandClaude Opus 4.5 3b58fa4f8b
Build and Push API / release (push) Successful in 3s
Build and Push API / build (push) Successful in 2m27s
refactor: reorganize into monorepo with separate subprojects
Structure webber into three independent subprojects:
- webber-api/: FastAPI backend server with all agent code
- webber-cli/: Standalone CLI client (renamed from cli/ to webber_cli/)
- webber-sandbox/: Test project for functional testing

Key changes:
- Each subproject has its own .venv (Python 3.12+)
- Added sandbox.sh for managing test project templates
- Created sandbox-templates/ with calculator-cli and empty starter
- Updated CI/CD for prefixed tags (api/v*, cli/v*)
- Added comprehensive AGENTS.md with operational instructions
- Added gitignore filtering to glob and grep tools
- Created pyproject.toml for each subproject

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 10:37:47 +01:00

168 lines
3.8 KiB
Python

"""
Base classes and registry for agent implementations.
All agents are built on PydanticAI and registered in a central registry.
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Protocol, runtime_checkable
from pydantic_ai import Agent
from src.shared.logging import get_logger
logger = get_logger(__name__)
@dataclass
class AgentContext:
"""
Base context passed to all agent tools.
Subclass this for agent-specific context (e.g., ExploreContext).
"""
working_dir: str
allowed_paths: list[str] = field(default_factory=list)
timeout_seconds: int = 120
@runtime_checkable
class AgentProtocol(Protocol):
"""Protocol that all agents must implement."""
@property
def name(self) -> str:
"""Unique identifier for the agent."""
...
@property
def description(self) -> str:
"""Human-readable description of what the agent does."""
...
@property
def agent(self) -> Agent:
"""The underlying PydanticAI agent."""
...
async def run(self, prompt: str, **kwargs: Any) -> str:
"""
Execute the agent with a prompt.
Args:
prompt: User prompt/query
**kwargs: Additional arguments (working_dir, etc.)
Returns:
Agent response as string
"""
...
class BaseAgent(ABC):
"""
Abstract base class for agent implementations.
Provides common functionality and enforces interface.
Usage:
class ExploreAgent(BaseAgent):
name = "explore"
description = "Fast codebase exploration"
def _create_agent(self) -> Agent:
# Create and configure PydanticAI agent
...
async def run(self, prompt: str, **kwargs) -> str:
# Execute agent
...
"""
@property
@abstractmethod
def name(self) -> str:
"""Unique identifier for the agent."""
pass
@property
@abstractmethod
def description(self) -> str:
"""Human-readable description."""
pass
@property
def agent(self) -> Agent:
"""Lazy-loaded PydanticAI agent."""
if not hasattr(self, '_agent') or self._agent is None:
self._agent = self._create_agent()
return self._agent
@abstractmethod
def _create_agent(self) -> Agent:
"""
Create and configure the PydanticAI agent.
Override this to set up model, system prompt, and tools.
"""
pass
@abstractmethod
async def run(self, prompt: str, **kwargs: Any) -> str:
"""Execute the agent."""
pass
# === Agent Registry ===
_AGENT_REGISTRY: dict[str, BaseAgent] = {}
def register_agent(agent: BaseAgent) -> BaseAgent:
"""
Register an agent in the global registry.
Args:
agent: Agent instance to register
Returns:
The registered agent (for decorator chaining)
"""
if agent.name in _AGENT_REGISTRY:
logger.warning(f"Overwriting existing agent: {agent.name}")
_AGENT_REGISTRY[agent.name] = agent
logger.info(f"Registered agent: {agent.name}")
return agent
def get_agent(name: str) -> BaseAgent | None:
"""
Get an agent by name.
Args:
name: Agent name
Returns:
Agent instance or None if not found
"""
return _AGENT_REGISTRY.get(name)
def list_agents() -> list[dict[str, str]]:
"""
List all registered agents.
Returns:
List of agent info dicts with name and description
"""
return [
{"name": agent.name, "description": agent.description}
for agent in _AGENT_REGISTRY.values()
]
def get_registry() -> dict[str, BaseAgent]:
"""Get the full agent registry."""
return _AGENT_REGISTRY.copy()