""" Orchestrator — stateless LLM call that selects the next speaker or ends the session. Called fresh each turn when turn_order is ORCHESTRATED. Output is a tool call only; any text is discarded. """ import logging from dataclasses import dataclass from typing import Literal, Optional, TYPE_CHECKING if TYPE_CHECKING: from fellowship.core.session import Session logger = logging.getLogger(__name__) @dataclass class OrchestratorDecision: action: Literal["select_speaker", "hold", "end_session"] bot_name: Optional[str] = None # set when action == "select_speaker" reason: Optional[str] = None # set when action == "end_session" # Tool definitions sent to the LLM with every orchestrator call ORCHESTRATOR_TOOLS = [ { "type": "function", "function": { "name": "select_speaker", "description": "Choose which bot should speak next.", "parameters": { "type": "object", "properties": { "bot_name": {"type": "string", "description": "Name of the bot to speak next"}, }, "required": ["bot_name"], }, }, }, { "type": "function", "function": { "name": "hold", "description": ( "Do not prompt any bot this turn. " "Use when the conversation implies bots should stay silent." ), "parameters": {"type": "object", "properties": {}}, }, }, { "type": "function", "function": { "name": "end_session", "description": "End the session. Only use when the session goal has been reached.", "parameters": { "type": "object", "properties": { "reason": {"type": "string", "description": "Why the session is ending"}, }, "required": ["reason"], }, }, }, ] class Orchestrator: def __init__(self, session: "Session") -> None: self.session = session async def decide(self) -> OrchestratorDecision: """ Build the orchestrator prompt, call the LLM, parse the tool call response. Any text output from the LLM is ignored — only the tool call matters. """ raise NotImplementedError def _build_system_prompt(self) -> str: """ Build the orchestrator system prompt including: - Its role and instructions - Overview of how Fellowship works - Full bot roster (names, roles, system prompts) - Session goal (if set) - Instruction to always respond with a tool call """ raise NotImplementedError def _parse_tool_call(self, response: dict) -> OrchestratorDecision: """Parse the LLM tool call response into an OrchestratorDecision.""" raise NotImplementedError