Files
Fellowship/fellowship/core/orchestrator.py
Jaroslav Benes 083cbb1fa7 Initial project structure
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>
2026-04-08 14:48:48 +02:00

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