""" Records the engine's model decisions in the backend's decision log. Until Phase 5 of the plan (krow_talent_app/docs/agent-platform-plan.md) the stall and assignment-failure decisions existed only as log lines, so the console's Insights tab could not show them. Each decision is now sent to ``POST /api/v1/internal/agent-decisions`` — the same log routemate's assignment decisions go to. Fire-and-forget: the post runs as a background task, so a slow or down backend never delays an agent's reaction to a stall. A failure is logged by the HTTP client and otherwise ignored — the decision was still made and acted on; only its record is missing. """ import asyncio from typing import Any, Dict, Optional, Set from config.system_config import GO_API_BASE_URL, INTERNAL_API_KEY from core.http_client import api_post _pending: Set[asyncio.Task] = set() # keep references so tasks are not garbage-collected def _as_booking_id(value: Any) -> Optional[int]: """The backend's booking_id is an unsigned integer; anything else is omitted.""" try: n = int(value) except (TypeError, ValueError): return None return n if n > 0 else None def build_payload(decision_type: str, booking_id: Any, facts: Optional[Dict[str, Any]], action: str, confidence: float, reasoning: str, model: str) -> Dict[str, Any]: """The request body. Pure, so its shape is tested without a network.""" return { "decision_type": decision_type, "booking_id": _as_booking_id(booking_id), "context": {"facts": facts or {}, "model": model}, "decision": {"action": action, "confidence": round(float(confidence), 3)}, "reasoning": reasoning or "", } async def _post(payload: Dict[str, Any]) -> None: await api_post( f"{GO_API_BASE_URL}/api/v1/internal/agent-decisions", json=payload, headers={"X-Internal-Key": INTERNAL_API_KEY}, ) def record_decision(decision_type: str, booking_id: Any, facts: Optional[Dict[str, Any]], action: str, confidence: float, reasoning: str, model: str) -> Optional[asyncio.Task]: """Schedule the record and return at once. Returns the task (for tests), or None when there is no running loop or no key to authenticate with.""" if not INTERNAL_API_KEY: return None try: loop = asyncio.get_running_loop() except RuntimeError: return None task = loop.create_task(_post(build_payload(decision_type, booking_id, facts, action, confidence, reasoning, model))) _pending.add(task) task.add_done_callback(_pending.discard) return task