feat: initial FastAPI boilerplate setup

Set up Webber - multi-agent AI development system with:

- Domain-based project structure (src/domains/, src/shared/)
- BaseController pattern with lazy router instantiation
- Pydantic Settings configuration with env file support
- Logger decorator with temporal benchmarking and trace IDs
- UserProvider singleton for request-scoped context
- Custom exception hierarchy
- Health endpoints (/, /health)

Dependencies (CVE checked 2026-01-09):
- FastAPI 0.128.0, Starlette 0.50.0, Uvicorn 0.40.0
- Pydantic 2.12.4, PydanticAI 1.40.0
- All packages at latest safe versions

Placeholder domains for future implementation:
- agents/ (explore, plan, task)
- tools/ (file, shell, search)
- auth/ (tatlock integration)

Port: 8086 (per CONTAINERS.md allocation)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-09 18:38:08 +01:00
co-authored by Claude Opus 4.5
parent 5f6b36f9c5
commit a61fbe5a88
38 changed files with 2970 additions and 0 deletions
View File
+53
View File
@@ -0,0 +1,53 @@
# Agents Domain
This domain contains PydanticAI agent definitions and orchestration.
## Structure
```
agents/
├── router.py # Agent routes (list, run)
├── controller.py # Agent orchestration logic
├── schemas.py # Request/response models
│
├── explore/ # Explore agent - codebase navigation
│ ├── agent.py # PydanticAI agent definition
│ └── prompts.py # System prompts
│
├── plan/ # Plan agent - implementation design
│ ├── agent.py
│ └── prompts.py
│
└── task/ # Task agent - execution
├── agent.py
└── prompts.py
```
## PydanticAI Pattern
```python
from pydantic_ai import Agent
from pydantic_ai.models.ollama import OllamaModel
from src.shared.config import get_settings
settings = get_settings()
explore_agent = Agent(
OllamaModel(settings.ollama_agent_model, base_url=settings.ollama_url),
system_prompt='You are a code exploration assistant...',
)
@explore_agent.tool
async def search_files(ctx, pattern: str) -> str:
"""Search for files matching pattern."""
# Implementation uses tools from src/domains/tools/
pass
```
## Adding a New Agent
1. Create a new directory under `agents/` (e.g., `agents/review/`)
2. Create `agent.py` with PydanticAI Agent definition
3. Create `prompts.py` with system prompts
4. Register in `controller.py`
5. Add tests in `tests/domains/test_agents/`
View File
View File
View File
+43
View File
@@ -0,0 +1,43 @@
# Auth Domain
This domain handles API key management and authentication.
## Structure
```
auth/
├── router.py # Auth routes
├── controller.py # Auth logic
└── schemas.py # Auth models
```
## Authentication Flow
1. Client sends `X-API-Key` header
2. Middleware validates key (via `shared/auth.py`)
3. User context set in `shared/context.py`
4. Routes use `Depends(require_auth)` for protected endpoints
## Integration with Tatlock
API keys are validated against the tatlock-ui/core-api user management system.
```python
# In shared/auth.py
async def validate_api_key(api_key: str) -> Optional[User]:
async with httpx.AsyncClient() as client:
response = await client.get(
f"{settings.tatlock_api_url}/auth/validate",
headers={"X-API-Key": api_key}
)
if response.status_code == 200:
return User(**response.json())
return None
```
## TODO
- [ ] Implement tatlock API key validation
- [ ] Add API key generation endpoint
- [ ] Add rate limiting per API key
- [ ] Add usage tracking
View File
View File
+71
View File
@@ -0,0 +1,71 @@
"""
Health check controller.
Provides service health and information endpoints.
"""
from fastapi import APIRouter
from pydantic import BaseModel
from src.shared.base import BaseController
from src.shared.config import get_settings
from src.shared.logging import get_logger, logged
logger = get_logger(__name__)
settings = get_settings()
class HealthResponse(BaseModel):
"""Health check response."""
status: str
version: str
service: str
class InfoResponse(BaseModel):
"""Service info response."""
service: str
version: str
status: str
docs: str
debug: bool
class HealthController(BaseController):
"""Controller for health and info endpoints."""
def __init__(self):
super().__init__(prefix="", tags=["Health"])
def create_router(self) -> APIRouter:
router = APIRouter(tags=self.tags)
@router.get("/", response_model=InfoResponse, summary="Service information")
@logged()
async def root():
"""Get service information."""
return InfoResponse(
service=settings.app_name,
version=settings.app_version,
status="healthy",
docs="/docs",
debug=settings.debug,
)
@router.get("/health", response_model=HealthResponse, summary="Health check")
@logged()
async def health_check():
"""
Health check endpoint.
Returns service health status for monitoring and load balancers.
"""
return HealthResponse(
status="healthy",
version=settings.app_version,
service=settings.app_name,
)
return router
health_controller = HealthController()
+8
View File
@@ -0,0 +1,8 @@
"""
Health check routes.
"""
from fastapi import APIRouter
from src.domains.health.controller import health_controller
router = health_controller.router
+26
View File
@@ -0,0 +1,26 @@
"""
Root router - composes all domain routers.
Import and include domain routers here.
main.py only includes this root_router.
"""
from fastapi import APIRouter
from src.domains.health.router import router as health_router
# from src.domains.auth.router import router as auth_router
# from src.domains.agents.router import router as agents_router
# from src.domains.tools.router import router as tools_router
root_router = APIRouter()
# Health (no prefix - root level)
root_router.include_router(health_router)
# Auth domain
# root_router.include_router(auth_router, prefix="/auth", tags=["Auth"])
# Agents domain
# root_router.include_router(agents_router, prefix="/agents", tags=["Agents"])
# Tools domain
# root_router.include_router(tools_router, prefix="/tools", tags=["Tools"])
+70
View File
@@ -0,0 +1,70 @@
# Tools Domain
This domain contains tool implementations for agent use.
## Structure
```
tools/
├── router.py # Tool routes (list, execute)
├── controller.py # Tool orchestration
├── schemas.py # Tool request/response models
│
├── file/ # File operation tools
│ ├── read.py # Read file contents
│ ├── write.py # Write file contents
│ └── glob.py # Find files by pattern
│
├── shell/ # Shell execution tools
│ └── bash.py # Execute bash commands
│
└── search/ # Search tools
├── grep.py # Search file contents
└── web.py # Web search
```
## Tool Pattern
Tools are registered with PydanticAI agents via the `@agent.tool` decorator.
Each tool should:
1. Have clear input/output types
2. Include a docstring (used by LLM)
3. Handle errors gracefully
4. Respect sandbox settings
```python
from src.shared.config import get_settings
from src.shared.logging import logged
settings = get_settings()
@logged()
async def read_file(file_path: str, limit: int = 2000) -> str:
"""
Read contents of a file.
Args:
file_path: Absolute path to the file
limit: Maximum lines to read
Returns:
File contents as string
"""
# Check path is allowed
if settings.sandbox_enabled:
# Validate against allowed_paths
pass
# Read and return
pass
```
## Adding a New Tool
1. Create a new file in appropriate category (file/, shell/, search/)
2. Implement the tool function with proper types and docstring
3. Add `@logged()` decorator for timing
4. Handle sandbox restrictions
5. Register with agents that need it
6. Add tests
View File
View File
View File