Reactive agentic loop with MCP tool access and task management. The core while(True) loop: call the LLM with MCP + orchestrator tools, execute any tool calls (routing locally or to MCP), append results to conversation, and repeat until the LLM responds with no tool calls. Orchestra
| 119 | |
| 120 | |
| 121 | class AgenticLoop: |
| 122 | """Reactive agentic loop with MCP tool access and task management. |
| 123 | |
| 124 | The core while(True) loop: call the LLM with MCP + orchestrator tools, |
| 125 | execute any tool calls (routing locally or to MCP), append results to |
| 126 | conversation, and repeat until the LLM responds with no tool calls. |
| 127 | |
| 128 | Orchestrator tools (task_create, task_update, task_list, ask_user) run |
| 129 | in-process. MCP tools run in the sandbox VM. The LLM decides which to use. |
| 130 | |
| 131 | Protections: |
| 132 | - Tool results are truncated to max_tool_result_chars before appending |
| 133 | - Proactive context monitoring at 70% utilization injects a wrap-up message |
| 134 | - ContextWindowExceededError is caught and recovered by trimming history |
| 135 | - Empty LLM responses trigger a reminder (up to 3 times) |
| 136 | """ |
| 137 | |
| 138 | def __init__( |
| 139 | self, |
| 140 | mcp_client: MCPClient, |
| 141 | llm_client: LLMClient, |
| 142 | config: LoopConfig, |
| 143 | logger: Logger, |
| 144 | ): |
| 145 | self.mcp_client = mcp_client |
| 146 | self.llm_client = llm_client |
| 147 | self.config = config |
| 148 | self.logger = logger |
| 149 | self.session = Session(config) |
| 150 | self._mcp_tools: list[dict] = [] |
| 151 | self._all_tools: list[dict] = [] |
| 152 | self._orchestrator_tools: list[OrchestratorTool] = [] |
| 153 | self._task_board: Optional[TaskBoard] = None |
| 154 | self._plan_executor: Optional[PlanExecutor] = None |
| 155 | self._plan_generator: Optional[PlanGenerator] = None |
| 156 | self._verifier: Optional[PlanVerifier] = None |
| 157 | |
| 158 | def run(self, task: str, plan_mode: bool = False) -> str: |
| 159 | """Run a single task through the agentic loop. |
| 160 | |
| 161 | Args: |
| 162 | task: The user's task description. |
| 163 | plan_mode: If True, runs structured plan mode: |
| 164 | Phase 1: Decompose into subtasks (task tools only) |
| 165 | Phase 2: Generate YAML plan for each subtask |
| 166 | No execution — plans are generated and verified only. |
| 167 | """ |
| 168 | self._setup() |
| 169 | if plan_mode: |
| 170 | result = self._run_plan_mode(task) |
| 171 | else: |
| 172 | self.session.add_user_message(task) |
| 173 | result = self._inner_loop() |
| 174 | self.logger.event("loop_end", self.session.get_usage_summary()) |
| 175 | return result |
| 176 | |
| 177 | def run_interactive(self) -> None: |
| 178 | """Interactive REPL mode. Reads tasks from stdin. |