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>
350 lines
9.1 KiB
Markdown
350 lines
9.1 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)
|
|
│ │
|
|
│ ├── 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_<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
|