Implement runtime agent loop and container hygiene

This commit is contained in:
2026-05-12 07:56:37 -05:00
parent 412d7caec3
commit a09197e85a
13 changed files with 524 additions and 70 deletions

View File

@@ -1,7 +1,8 @@
"""FastAPI main application."""
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
import asyncio
from contextlib import suppress
from fastapi import FastAPI, HTTPException, Form
from datetime import datetime
import logging
@@ -20,15 +21,37 @@ app = FastAPI(
# Global orchestrator instance
orchestrator: AgentOrchestrator | None = None
agent_loop_task: asyncio.Task | None = None
async def agent_loop() -> None:
"""Run periodic time-based agent behavior for active sessions."""
if not orchestrator:
return
while True:
try:
results = await orchestrator.tick()
if results:
logger.info(f"Agent loop actions: {results}")
except asyncio.CancelledError:
raise
except Exception as e:
logger.error(f"Agent loop tick failed: {e}")
await asyncio.sleep(orchestrator.loop_interval_seconds)
@app.on_event("startup")
async def startup_event():
"""Initialize database and services on startup."""
global orchestrator
global orchestrator, agent_loop_task
try:
await init_db()
orchestrator = AgentOrchestrator()
orchestrator = AgentOrchestrator(
loop_interval_seconds=settings.AGENT_LOOP_INTERVAL_SECONDS
)
agent_loop_task = asyncio.create_task(agent_loop())
logger.info("Application started successfully")
except Exception as e:
logger.error(f"Failed to start application: {e}")
@@ -38,6 +61,10 @@ async def startup_event():
@app.on_event("shutdown")
async def shutdown_event():
"""Clean up resources on shutdown."""
if agent_loop_task:
agent_loop_task.cancel()
with suppress(asyncio.CancelledError):
await agent_loop_task
logger.info("Application shutting down")
@@ -53,7 +80,7 @@ async def health_check() -> dict:
@app.post("/admin/session/start")
async def start_session(channel_name: str) -> dict:
async def start_session(channel_name: str = Form(...)) -> dict:
"""Start a new stream session."""
if not orchestrator:
raise HTTPException(status_code=503, detail="Orchestrator not initialized")
@@ -68,7 +95,7 @@ async def start_session(channel_name: str) -> dict:
@app.post("/admin/session/end")
async def end_session(session_id: str) -> dict:
async def end_session(session_id: str = Form(...)) -> dict:
"""End the current stream session."""
if not orchestrator:
raise HTTPException(status_code=503, detail="Orchestrator not initialized")
@@ -82,7 +109,7 @@ async def end_session(session_id: str) -> dict:
@app.post("/admin/test-message")
async def test_message(session_id: str, message: str, username: str = "test_user") -> dict:
async def test_message(session_id: str = Form(...), message: str = Form(...), username: str = Form("test_user")) -> dict:
"""Send a test message to the orchestrator."""
if not orchestrator:
raise HTTPException(status_code=503, detail="Orchestrator not initialized")
@@ -116,6 +143,37 @@ async def get_ledger(session_id: str) -> dict:
}
@app.get("/admin/loop/status")
async def get_loop_status() -> dict:
"""Get the background agent loop runtime configuration."""
if not orchestrator:
raise HTTPException(status_code=503, detail="Orchestrator not initialized")
return {
"status": "running" if agent_loop_task and not agent_loop_task.done() else "stopped",
**orchestrator.get_loop_status(),
"timestamp": datetime.utcnow().isoformat(),
}
@app.post("/admin/loop/frequency")
async def set_loop_frequency(interval_seconds: float = Form(...)) -> dict:
"""Set how frequently the background agent loop runs."""
if not orchestrator:
raise HTTPException(status_code=503, detail="Orchestrator not initialized")
try:
orchestrator.set_loop_interval(interval_seconds)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
return {
"status": "loop_frequency_updated",
"interval_seconds": orchestrator.loop_interval_seconds,
"timestamp": datetime.utcnow().isoformat(),
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(