Files
ActiveBlue Build 430ab966b2 feat(service): add FastAPI agent service with 5 routers and Docker setup
- config.py: pydantic-settings with all env vars, privacy mode, per-agent overrides
- app_state.py: global singletons (pool, master agent, registry, llm_router, sweep)
- main.py: FastAPI lifespan startup — DB pool, LLM router, Odoo client, agents, master
- routers/dispatch.py: POST /dispatch with rate limiting and webhook secret auth
- routers/approval.py: GET /approval/pending, POST /approval/respond
- routers/registry.py: GET/POST /registry/agents, POST /registry/backend overrides
- routers/sweep.py: POST /sweep trigger, GET /sweep/status
- routers/health.py: GET /health, GET /health/detailed (DB/Odoo/Ollama checks)
- requirements.txt: pinned deps (fastapi, uvicorn, asyncpg, anthropic, alembic)
- Dockerfile: python:3.11-slim, single uvicorn worker
- docker-compose.yml: agent-service + postgres:15, bound to 192.168.2.47:8001

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 17:54:28 -04:00

56 lines
1.7 KiB
Python

from __future__ import annotations
import asyncio
import logging
from typing import Optional
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
logger = logging.getLogger(__name__)
router = APIRouter(prefix='/sweep', tags=['sweep'])
# Track running sweep task
_sweep_task: Optional[asyncio.Task] = None
_last_sweep_result: Optional[dict] = None
class SweepRequest(BaseModel):
agents: list[str] = [] # empty = all active agents
class SweepStatusResponse(BaseModel):
running: bool
last_result: Optional[dict] = None
@router.post('', status_code=status.HTTP_202_ACCEPTED)
async def trigger_sweep(req: SweepRequest):
global _sweep_task
if _sweep_task and not _sweep_task.done():
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail='Sweep already running',
)
from ..app_state import get_sweep_coordinator
coordinator = get_sweep_coordinator()
if coordinator is None:
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='Sweep coordinator not ready')
async def _run():
global _last_sweep_result
try:
result = await coordinator.run_sweep(agents=req.agents or None)
_last_sweep_result = result
except Exception as exc:
logger.error('sweep run error: %s', exc)
_last_sweep_result = {'error': str(exc)}
_sweep_task = asyncio.create_task(_run())
return {'status': 'accepted', 'agents': req.agents or 'all'}
@router.get('/status', response_model=SweepStatusResponse)
async def sweep_status():
running = _sweep_task is not None and not _sweep_task.done()
return SweepStatusResponse(running=running, last_result=_last_sweep_result)