Add outbound agent response boundary

This commit is contained in:
2026-05-12 08:10:34 -05:00
parent bce93b39e0
commit 7286a241e4
2 changed files with 76 additions and 9 deletions

View File

@@ -13,10 +13,12 @@ from app.agent.modes.steward import StewardMode
from app.agent.modes.warden import WardenMode
from app.agent.modes.librarian import LibrarianMode
from app.agent.modes.scribe import ScribeMode
from app.config import settings
from app.llm.client import LLMClient
from app.memory.database import get_session
from app.memory.models import AgentActionType
from app.memory.repository import Repository
from app.twitch.chat import send_chat_message
logger = logging.getLogger(__name__)
@@ -212,6 +214,49 @@ class AgentOrchestrator:
"actions_taken": actions,
}
async def emit_agent_response(
self,
session_id: str,
message: str,
mode: str,
triggered_by_message_id: str | None = None,
) -> dict:
"""Send an agent response through the outbound chat boundary."""
session_info = self.active_sessions.get(session_id)
if not session_info:
logger.warning(f"Session {session_id} not found")
return {"sent": False, "reason": "session_not_found"}
channel_name = session_info["channel_name"]
sent = await send_chat_message(channel_name=channel_name, message=message)
bot_username = settings.TWITCH_BOT_USERNAME or "sanctum_chronicler"
async for db_session in get_session():
repo = Repository(db_session)
bot_message_id = await repo.add_chat_message(
session_id=session_id,
username=bot_username,
content=message,
is_bot=True,
)
action_id = await repo.record_action(
session_id=session_id,
action_type=AgentActionType.RESPONSE,
mode=mode,
description=message,
triggered_by_message_id=triggered_by_message_id,
)
logger.info(
f"Agent response emitted. Session: {session_id}, Mode: {mode}, Sent: {sent}"
)
return {
"sent": sent,
"channel": channel_name,
"message_id": bot_message_id,
"action_id": action_id,
}
def set_loop_interval(self, interval_seconds: float) -> None:
"""Update how frequently the background agent loop runs."""
if interval_seconds < 1:
@@ -269,20 +314,17 @@ class AgentOrchestrator:
theme=session_info.get("theme")
)
session_info["last_hearthkeeper_prompt_at"] = datetime.utcnow()
async for db_session in get_session():
repo = Repository(db_session)
await repo.record_action(
session_id=session_id,
action_type=AgentActionType.RESPONSE,
mode="hearthkeeper",
description=agent_response,
)
delivery = await self.emit_agent_response(
session_id=session_id,
message=agent_response,
mode="hearthkeeper",
)
return {
"session_id": session_id,
"actions_taken": ["HEARTHKEEPER_PROMPT"],
"agent_response": agent_response,
"delivery": delivery,
"reason": "inactive_chat",
}
except Exception as e:

View File

@@ -128,6 +128,31 @@ async def test_message(session_id: str = Form(...), message: str = Form(...), us
}
@app.post("/admin/test-agent-response")
async def test_agent_response(
session_id: str = Form(...),
message: str = Form(...),
mode: str = Form("admin"),
) -> dict:
"""Send a test agent response through the outbound boundary."""
if not orchestrator:
raise HTTPException(status_code=503, detail="Orchestrator not initialized")
delivery = await orchestrator.emit_agent_response(
session_id=session_id,
message=message,
mode=mode,
)
if not delivery.get("sent"):
raise HTTPException(status_code=404, detail=delivery.get("reason", "send_failed"))
return {
"status": "agent_response_emitted",
"delivery": delivery,
"timestamp": datetime.utcnow().isoformat(),
}
@app.get("/admin/ledger")
async def get_ledger(session_id: str) -> dict:
"""Get the markdown ledger for a session."""