# Webber Architecture Multi-Agent AI Development System - similar to Claude Code but running locally with configurable models. ## Overview Webber is a FastAPI-based agent orchestration service that provides: - Multi-agent execution (Explore, Plan, Task) - Tool capabilities (file operations, shell, search) - Multi-tenant authentication via Tatlock integration - PydanticAI framework for LLM orchestration **Port:** 8086 **Runtime:** Python 3.12, FastAPI, Uvicorn **Agent Framework:** PydanticAI **Default LLM:** Ollama with mistral-nemo-large:latest --- ## Directory Structure ``` webber/ ├── src/ │ ├── main.py # App entry point (NO routes) │ │ │ ├── shared/ # Cross-cutting concerns │ │ ├── base.py # BaseController, BaseSchema │ │ ├── config.py # Pydantic Settings │ │ ├── logging.py # @logged decorator, trace_span │ │ ├── exceptions.py # Custom exception hierarchy │ │ ├── auth.py # API key validation │ │ └── context.py # UserProvider singleton │ │ │ └── domains/ # Feature domains │ ├── router.py # Root router (composes all) │ ├── health/ # Health endpoints │ ├── auth/ # Authentication │ ├── agents/ # Agent orchestration │ │ ├── explore/ # Codebase navigation │ │ ├── plan/ # Implementation design │ │ └── task/ # Execution │ └── tools/ # Tool execution │ ├── file/ # Read, write, glob │ ├── shell/ # Bash execution │ └── search/ # Grep, web search │ ├── tests/ ├── docs/ └── logs/ ``` --- ## Core Patterns ### 1. Clean main.py The entry point contains ONLY: - FastAPI app creation with lifespan - CORS middleware - Auth middleware (sets UserProvider) - Exception handlers - Single router include (`root_router`) All routes live in domain routers. This keeps main.py focused on app initialization. ### 2. Domain-Based Structure Each feature domain has its own directory: ``` domains/ ├── router.py # Root router composing all domains ├── health/ │ ├── router.py # Domain routes │ └── controller.py # Business logic ├── agents/ │ ├── router.py # Agent routes │ ├── controller.py # Orchestration logic │ ├── schemas.py # Request/response models │ └── explore/ # Agent implementation │ ├── agent.py # PydanticAI agent │ └── prompts.py # System prompts ``` ### 3. BaseController Pattern Controllers use lazy router instantiation: ```python from src.shared.base import BaseController class MyController(BaseController): def __init__(self): super().__init__(prefix="/my", tags=["My"]) def create_router(self) -> APIRouter: router = APIRouter(prefix=self.prefix, tags=self.tags) @router.get("/") async def list_items(): return [] return router my_controller = MyController() # Use: my_controller.router ``` ### 4. UserProvider Singleton Request-scoped user context without parameter passing: ```python # In middleware (main.py): user = await validate_api_key(api_key) user_provider.set_user(user) try: response = await call_next(request) finally: user_provider.clear_user() # Anywhere in code: from src.shared.context import get_current_user, require_user user = get_current_user() # Returns None if not authenticated user = require_user() # Raises if not authenticated ``` Uses Python's `contextvars` for async-safe request isolation. ### 5. Logger with Temporal Benchmarking The `@logged()` decorator automatically tracks execution time: ```python from src.shared.logging import logged, trace_span, get_logger logger = get_logger(__name__) @logged() async def my_function(): # Automatically logs entry, exit, and duration pass @logged(slow_threshold_ms=50, warn_threshold_ms=200) def critical_path(): # Custom thresholds for performance-critical code pass async def complex_operation(): async with trace_span("llm_call"): # Manual span for specific sections result = await agent.run(prompt) ``` Features: - Trace ID correlation across nested calls - Configurable slow/warn thresholds - DEBUG: all calls logged with timing - INFO: slow calls (>100ms default) - WARNING: very slow calls (>500ms default) - ERROR: failed calls with stack trace ### 6. Exception Hierarchy ```python from src.shared.exceptions import ( AppException, NotFoundError, ValidationError, AuthenticationError, AuthorizationError, ) # Raise with context: raise NotFoundError("User", user_id) raise ValidationError("email", "Invalid format") # Automatic JSON response via exception handlers in main.py ``` --- ## Configuration All settings via environment variables or `.env`: | Variable | Default | Description | |----------|---------|-------------| | DEBUG | false | Enable debug mode | | LOG_LEVEL | INFO | Logging level | | HOST | 0.0.0.0 | Server host | | PORT | 8086 | Server port | | OLLAMA_URL | http://192.168.86.149:11434 | Ollama API URL | | OLLAMA_AGENT_MODEL | mistral-nemo-large:latest | Agent reasoning model | | OLLAMA_EMBED_MODEL | nomic-embed-text:latest | Embedding model | | TATLOCK_API_URL | http://192.168.86.149:8000 | Tatlock auth service | | TOOL_TIMEOUT_SECONDS | 120 | Tool execution timeout | | SANDBOX_ENABLED | true | Enable sandboxed execution | | ALLOWED_PATHS | [] | Paths accessible to tools | | SESSION_TTL_HOURS | 24 | Session expiry | | MAX_CONTEXT_TOKENS | 128000 | Max context window | --- ## Agent Architecture Webber uses PydanticAI for agent orchestration. Each agent type is purpose-built: ### Explore Agent Fast codebase exploration for: - Finding files by pattern - Searching code for keywords - Answering questions about structure ### Plan Agent Implementation design for: - Analyzing requirements - Creating step-by-step plans - Identifying files to modify - Considering trade-offs ### Task Agent Autonomous execution for: - Multi-step implementations - Tool orchestration - Code generation and modification --- ## Tool Architecture Tools are sandboxed operations agents can invoke: ### File Tools - **Read**: Read file contents with line limits - **Write**: Create or overwrite files - **Edit**: String replacement in files - **Glob**: Pattern-based file search ### Shell Tools - **Bash**: Command execution with timeout - Sandboxed to allowed paths - Captures stdout/stderr ### Search Tools - **Grep**: Regex content search via ripgrep - **WebSearch**: Web search integration (optional) --- ## Authentication Flow 1. Client sends `X-API-Key` header 2. Auth middleware calls `validate_api_key()` 3. Tatlock validates key and returns user info 4. UserProvider stores user in request context 5. Routes access via `get_current_user()` or `require_user()` 6. Middleware clears user in `finally` block --- ## Deployment ### Docker ```dockerfile FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY pyproject.toml . COPY src/ ./src/ ENV PYTHONPATH=/app EXPOSE 8086 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8086/health || exit 1 CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8086"] ``` ### CI/CD Gitea Actions workflow: 1. Push tag `v*` triggers build 2. Creates Gitea release 3. Builds and pushes Docker image to registry 4. Watchtower auto-deploys to production ### Production Stack Deployed in Portainer `agents` stack alongside Tatlock: - Network: `docker-dataplane` - Registry: `git.schweitz.internal/jpmschweitzer/webber` - Auto-update: Watchtower with label `com.centurylinklabs.watchtower.enable=true` --- ## Adding New Domains 1. Create domain directory under `src/domains/` 2. Add `router.py` with routes 3. Add `controller.py` with business logic 4. Add `schemas.py` for request/response models 5. Import and include router in `src/domains/router.py` 6. Add tests in `tests/test_.py` --- ## Adding New Agents 1. Create agent directory under `src/domains/agents/` 2. Add `agent.py` with PydanticAI agent definition 3. Add `prompts.py` with system prompts 4. Register in agents controller 5. Document in `src/domains/agents/README.md` --- ## Adding New Tools 1. Create tool file under appropriate `src/domains/tools/` subdir 2. Implement tool function with type hints 3. Register as PydanticAI tool 4. Document in `src/domains/tools/README.md` --- ## Testing ```bash # Run all tests ./wakeup.sh # Start server first pytest tests/ -v # With coverage pytest tests/ --cov=src --cov-report=html ``` --- ## Security Considerations - All tool execution is sandboxed when `SANDBOX_ENABLED=true` - File operations restricted to `ALLOWED_PATHS` - No secrets in prompts - Input validation via Pydantic - Output parsing expects malformed LLM responses - Timeouts on all tool execution