"""Two-agent orchestrator. Provides the per-turn coordination helpers. `app.py` calls these in order: 1. `run_manager(...)` — blocking ~2-4s, returns ManagerVerdict 2. `build_main_messages(...)` — pure, returns (messages, plan_control_text) 3. `main_agent.stream(messages)` — generator; the host threads it with its UI animation pattern The result of each turn (verdict + bot response + plan_control) is assembled by app.py into a per-turn record for the session log. """ from dataclasses import dataclass, asdict from core import manager_agent, main_agent from core.manager_agent import ManagerVerdict from core.main_agent import BotResponse @dataclass class TurnResult: """Per-turn record assembled by the host. Self-contained: holds the manager's verdict, the verbatim PLAN CONTROL text injected, the bot's response, and timing. Written into the session log as one turn row.""" verdict: ManagerVerdict plan_control_text: str bot_response: BotResponse def to_dict(self): return { "manager_verdict": self.verdict.to_dict(), "plan_control": self.plan_control_text, "bot": { "visible": self.bot_response.visible, "scratch": self.bot_response.scratch, "latency_ms": self.bot_response.latency_ms, "ttft_ms": self.bot_response.ttft_ms, }, } def run_manager(discussion_plan_subprobes, manager_history, history, current_msg, initial_argument="", client=None): """Blocking manager LLM call. Returns ManagerVerdict. `initial_argument` is the participant's full submitted essay; passed so the manager can always see the participant's overall stance regardless of how many turns have scrolled past in `history`. `client` is the per-session OpenAI client (from `config_loader.get_next_client()`). Pass `None` to fall back to the module-level default client. """ return manager_agent.classify( discussion_plan_subprobes=discussion_plan_subprobes, manager_history=manager_history, recent_turns=history, current_msg=current_msg, initial_argument=initial_argument, client=client, ) def build_main_messages(verdict, discussion_plan_subprobes, discussion_plan_text, history, case_text): """Compose the main bot's input messages from manager verdict + state. Returns (messages, plan_control_text).""" plan_control_text = main_agent.build_plan_control(verdict, discussion_plan_subprobes) messages = main_agent.build_messages( history=history, case_text=case_text, discussion_plan_text=discussion_plan_text, plan_control_text=plan_control_text, ) return messages, plan_control_text