Files
webber/webber-api/docs/architecture.md
T

418 lines
12 KiB
Markdown

# 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)
│ │
│ ├── db/ # Database layer
│ │ ├── __init__.py # Exports: Database, get_database, get_session
│ │ ├── database.py # SQLAlchemy async engine, session factory
│ │ └── models.py # Base declarative model
│ │
│ ├── 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
│ │ └── tokens.py # Token counting utilities (litellm)
│ │
│ └── domains/ # Feature domains
│ ├── router.py # Root router (composes all)
│ ├── health/ # Health endpoints
│ ├── auth/ # Authentication
│ ├── conversations/ # Multi-turn conversation memory
│ │ ├── models.py # Conversation, Message SQLAlchemy models
│ │ ├── schemas.py # Pydantic request/response models
│ │ ├── service.py # ConversationService business logic
│ │ ├── router.py # REST API endpoints
│ │ └── summarize.py # Context summarization logic
│ ├── 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://tatlock:8000 | Tatlock auth service |
| SEARXNG_URL | http://searxng:8080 | SearXNG web search instance |
| SEARXNG_TIMEOUT | 10 | SearXNG request timeout (seconds) |
| 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 |
| DATABASE_URL | sqlite+aiosqlite:///./webber.db | Database connection URL |
| SUMMARIZATION_THRESHOLD | 0.8 | Summarize at N% of max tokens |
| SUMMARIZATION_TARGET_TOKENS | 500 | Target summary size |
| KEEP_RECENT_MESSAGES | 6 | Messages to keep unsummarized |
| RETRY_MAX_ATTEMPTS | 3 | Max retry attempts for transient failures |
| RETRY_BASE_DELAY | 1.0 | Base delay between retries (seconds) |
| RETRY_MAX_DELAY | 30.0 | Maximum delay between retries (seconds) |
---
## 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)
---
## Database Layer
SQLAlchemy 2.0 async with lazy initialization pattern.
### Supported Databases
- **Development**: SQLite via `aiosqlite`
- **Production**: PostgreSQL via `asyncpg`
### Pattern
```python
from src.db import get_session
from sqlalchemy.ext.asyncio import AsyncSession
async def my_endpoint(session: AsyncSession = Depends(get_session)):
# Session auto-commits on success, rollbacks on exception
result = await session.execute(query)
```
Tables are created lazily on first `get_session()` call.
---
## Conversation API
Multi-turn conversation memory with automatic context summarization.
### Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/conversations/` | POST | Create new conversation |
| `/conversations/` | GET | List user's conversations |
| `/conversations/{id}` | GET | Get conversation with history |
| `/conversations/{id}/messages` | POST | Add message, triggers agent |
| `/conversations/{id}` | DELETE | Delete conversation |
### Models
- **Conversation**: User session with agent type, working directory
- **Message**: Individual messages with role, content, token count
### Context Summarization
When total tokens exceed 80% of `MAX_CONTEXT_TOKENS`:
1. Keep last 6 messages intact
2. Summarize older messages into a single summary message
3. Mark old messages as summarized (soft delete)
---
## 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.net/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_<domain>.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