feat: implement Phase 2 two-tier architecture with Steward

Add comprehensive two-tier architecture where Steward analyzes requests
and Tatlock executes with scoped tools. Includes full infrastructure for
request preprocessing, tool tracking, benchmarking, and streaming.

**Added:**
- Steward agent for request analysis and capability recommendation
- Household Registry for centralized capability management
- Request preprocessing pipeline (Steward → Tatlock flow)
- Tool usage tracking and benchmarking system
- Streaming transparency (Steward reasoning visible in streams)
- Structured logging with operation timing
- Redis benchmark storage with 30-day expiry
- Benchmark analysis CLI tools

**Infrastructure:**
- src/agents/steward/ - Steward agent implementation
- src/agents/tatlock_core/ - Tatlock capability domain
- src/core/preprocessing.py - Request preprocessing pipeline
- src/core/tool_tracking.py - Tool call tracking
- src/core/benchmarks.py - Benchmark recording system
- src/core/household_registry.py - Capability registry
- src/core/startup.py - Application startup coordination
- src/core/logging_config.py - Structured logging setup

**Integration:**
- Responses API uses Steward for Tatlock requests
- Chat Completions wraps Responses API for OpenAI compatibility
- Streaming coordinator supports Steward + Tatlock flow
- Tool scoping per request based on Steward recommendations

**Testing:**
- Integration tests for Steward-Tatlock flow
- Benchmark and registry unit tests
- Steward streaming tests

See PHASE2_PLAN.md and PHASE2_COMPLETE.md for detailed documentation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-07 15:39:20 +01:00
co-authored by Claude Sonnet 4.5
parent 2577730546
commit 6eed5f4d13
34 changed files with 6362 additions and 133 deletions
+43 -24
View File
@@ -9,7 +9,6 @@ Main responsibilities:
- Router registration
- Lifecycle management
"""
import logging
from contextlib import asynccontextmanager
from typing import AsyncGenerator
@@ -21,35 +20,42 @@ from fastapi.responses import JSONResponse
from src.chat.router import router as chat_router
from src.core.config import config
from src.core.exceptions import AppException
from src.core.logging_config import get_logger
from src.core.router import router as core_router
from src.core.startup import initialize_application
from src.models.router import router as models_router
from src.responses.router import router as responses_router
# Configure logging
logging.basicConfig(
level=config.LOG_LEVEL,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
# Get structured logger
logger = get_logger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
"""
Application lifespan manager.
Handles startup and shutdown logic.
"""
# Startup
logger.info(f"Starting {config.APP_NAME} v{config.APP_VERSION}")
logger.info(f"Environment: {config.ENVIRONMENT.value}")
logger.info(f"Ollama host: {config.OLLAMA_HOST}")
logger.info(f"Default model: {config.OLLAMA_DEFAULT_MODEL}")
logger.info(
"application_starting",
app_name=config.APP_NAME,
version=config.APP_VERSION,
environment=config.ENVIRONMENT.value,
ollama_host=str(config.OLLAMA_HOST),
ollama_model=config.OLLAMA_DEFAULT_MODEL,
redis_url=config.redis_url,
log_format=config.log_format,
)
# Initialize application (register household members, etc.)
initialize_application()
yield
# Shutdown
logger.info("Shutting down application")
logger.info("application_shutdown")
def create_application() -> FastAPI:
@@ -102,10 +108,14 @@ def register_exception_handlers(application: FastAPI) -> None:
) -> JSONResponse:
"""Handle custom application exceptions."""
logger.error(
f"Application error: {exc.message}",
extra={"details": exc.details}
"application_exception",
error_message=exc.message,
error_type=exc.__class__.__name__,
status_code=exc.status_code,
details=exc.details,
path=request.url.path,
)
return JSONResponse(
status_code=exc.status_code,
content={
@@ -116,15 +126,19 @@ def register_exception_handlers(application: FastAPI) -> None:
}
},
)
@application.exception_handler(RequestValidationError)
async def validation_exception_handler(
request: Request,
exc: RequestValidationError,
) -> JSONResponse:
"""Handle Pydantic validation errors."""
logger.error(f"Validation error: {exc.errors()}")
logger.error(
"validation_error",
errors=exc.errors(),
path=request.url.path,
)
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content={
@@ -135,15 +149,20 @@ def register_exception_handlers(application: FastAPI) -> None:
}
},
)
@application.exception_handler(Exception)
async def general_exception_handler(
request: Request,
exc: Exception,
) -> JSONResponse:
"""Handle unexpected exceptions."""
logger.exception("Unexpected error")
logger.exception(
"unexpected_error",
error_type=type(exc).__name__,
error_message=str(exc),
path=request.url.path,
)
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content={