Scaffold all modules, route stubs, data models, and config. No logic implemented yet — all core methods raise NotImplementedError. Establishes the full directory layout matching the architecture in CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
93 lines
2.9 KiB
Python
93 lines
2.9 KiB
Python
"""
|
|
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
|