# Tool System Analysis

**Phase 0 Analysis: Tool Executor and Question Gate Protocol**

**Date:** 2025-01-14
**Agent:** Phase0AnalyzeTools
**Focus:** Complete tool execution flow and Question Gate integration

---

## Executive Summary

The Kollabor CLI tool system is a sophisticated multi-format tool execution engine that supports:
- **3 tool types**: Terminal commands, MCP tools, and file operations
- **2 execution paths**: Native API function calling and XML-based tag parsing
- **Question Gate Protocol**: Suspends tool execution when agent asks clarifying questions
- **5 tool execution sites**: Located across `llm_service.py` and `tool_executor.py`

This analysis documents the complete tool flow, critical for Phase 6 (OpenAI SDK integration) where incremental tool accumulation will replace the current batch-retrieval approach.

---

## 1. Tool Executor Implementation

**File:** `/Users/malmazan/dev/kollabor-cli/core/llm/tool_executor.py`

### Architecture

The `ToolExecutor` class provides a unified interface for executing three tool types:

```python
class ToolExecutor:
    def __init__(self, mcp_integration: MCPIntegration, event_bus,
                 terminal_timeout: int = 90, mcp_timeout: int = 180, config=None)
```

**Key Components:**
- `mcp_integration`: MCP server connection manager
- `file_ops_executor`: File operations executor (delegated)
- `event_bus`: Hook emission for pre/post execution
- `stats`: Execution statistics tracking

### Tool Type Detection and Routing

**Entry Point:** `execute_tool(tool_data: Dict[str, Any])` (line 104)

```python
tool_type = tool_data.get("type", "unknown")

if tool_type == "terminal":
    result = await self._execute_terminal_command(tool_data)
elif tool_type == "mcp_tool":
    result = await self._execute_mcp_tool(tool_data)
elif tool_type.startswith("file_"):
    result = await self._execute_file_operation(tool_data)
```

**Tool Type Identification:**
- `terminal`: Shell command execution
- `mcp_tool`: MCP server tool calls
- `file_*`: 14 file operation types (read, create, edit, delete, move, copy, append, insert_after, insert_before, mkdir, rmdir, grep, create_overwrite, copy_overwrite)

### Tool Execution Flow

```
User Message
    ↓
LLM Response (with tools)
    ↓
ResponseParser extracts tools
    ↓
ToolExecutor.execute_tool() [single] or execute_all_tools() [batch]
    ↓
Type-specific handler (terminal/MCP/file)
    ↓
ToolExecutionResult (success, output, error, metadata)
    ↓
Result formatted for conversation history
```

### Hook Emission

**Pre-execution hook:** (line 120)
```python
await self.event_bus.emit_with_hooks(
    EventType.TOOL_CALL_PRE,
    {"tool_data": tool_data},
    "tool_executor"
)
```

**Post-execution hook:** (line 172)
```python
await self.event_bus.emit_with_hooks(
    EventType.TOOL_CALL_POST,
    {
        "tool_data": tool_data,
        "result": result.to_dict()
    },
    "tool_executor"
)
```

### Terminal Command Execution

**Method:** `_execute_terminal_command(tool_data)` (line 234)

**Process:**
1. Extract command from `tool_data["command"]`
2. Execute via `asyncio.create_subprocess_shell()`
3. Timeout: 90 seconds (configurable)
4. Capture stdout/stderr
5. Return success if exit code == 0

**Error Handling:**
- Timeout: Kill process, return error
- Cancellation: Clean up subprocess to avoid ResourceWarning
- Exception: Catch and return as error result

### MCP Tool Execution

**Method:** `_execute_mcp_tool(tool_data)` (line 312)

**Process:**
1. Extract tool name and arguments
2. Call `MCPIntegration.call_mcp_tool()`
3. Timeout: 180 seconds (configurable)
4. Format result using `_format_mcp_output()`

**MCP Result Formatting:** (line 376)
Handles multiple MCP response formats:
- `content` array (standard MCP format)
- `output` string (simple format)
- `result` object (JSON-RPC format)
- Fallback: stringify entire result

### File Operation Execution

**Method:** `_execute_file_operation(tool_data)` (line 416)

**Process:**
1. Delegates to `FileOperationsExecutor`
2. Uses `asyncio.to_thread()` to avoid blocking event loop
3. Preserves metadata (e.g., diff_info for edits)

**Supported Operations:** 14 types
- Read: `file_read`
- Create: `file_create`, `file_create_overwrite`
- Edit: `file_edit`, `file_append`, `file_insert_after`, `file_insert_before`
- Delete: `file_delete`
- Move/Copy: `file_move`, `file_copy`, `file_copy_overwrite`
- Search: `file_grep`
- Directories: `file_mkdir`, `file_rmdir`

### Tool Execution Result

**Class:** `ToolExecutionResult` (line 21)

**Fields:**
```python
tool_id: str           # Unique identifier
tool_type: str         # terminal, mcp_tool, file_*
success: bool          # Execution success
output: str            # Tool output/result
error: str             # Error message if failed
execution_time: float  # Seconds
metadata: Dict         # Additional data (diff_info, etc.)
timestamp: float       # Unix timestamp
```

### Batch Tool Execution

**Method:** `execute_all_tools(tools: List[Dict])` (line 201)

**Process:**
1. Execute tools sequentially (not parallel)
2. Continue on failure (don't stop batch if one tool fails)
3. Return all results in order
4. Log success/failure ratio

**Why Sequential?**
- Some tools may depend on previous tool outputs
- Maintains order for conversation history
- Prevents resource conflicts (file operations)

---

## 2. Tool Call Format

### Current Anthropic-Claude Format (XML Tags)

The system uses XML-based tool calls embedded in LLM responses:

**Terminal Commands:**
```xml
<terminal>ls -la</terminal>
```

**MCP Tool Calls (Attribute Format):**
```xml
<tool name="search_nodes" query="python" limit="10">
    Content goes here
</tool>
```

**MCP Tool Calls (Native Format):**
```xml
<tool_call>
search_nodes
</tool_call>
```

**MCP Tool Calls (JSON Format):**
```xml
<tool_call>
{"name": "search_nodes", "arguments": {"query": "python", "limit": 10}}
</tool_call>
```

**File Operations:**
```xml
<edit>
  <file>src/main.py</file>
  <find>old code</find>
  <replace>new code</replace>
</edit>

<create>
  <file>new_file.py</file>
  <content>
    def hello():
        print("world")
  </content>
</create>

<read>
  <file>README.md</file>
</read>
```

**Agent File Generation (@@@FILE blocks):**
```
@@@FILE path/to/file.md
Content here can contain <edit> examples without conflicts
@@@END
```

### Native API Function Calling

**When enabled**, the API returns structured function calls:

```python
# API response (e.g., OpenAI, Anthropic messages API)
{
    "role": "assistant",
    "tool_calls": [
        {
            "id": "call_abc123",
            "type": "function",
            "function": {
                "name": "file_read",
                "arguments": {"file": "README.md"}
            }
        }
    ]
}
```

**Conversion to ToolExecutor format:** (line 1138)
```python
# File operations
tool_data = {
    "type": "file_read",  # Tool name becomes type
    "id": "call_abc123",
    "file": "README.md"
}

# Terminal commands
tool_data = {
    "type": "terminal",
    "id": "call_def456",
    "command": "ls -la"
}

# MCP tools
tool_data = {
    "type": "mcp_tool",
    "id": "call_ghi789",
    "name": "search_nodes",
    "arguments": {"query": "python"}
}
```

### Tool Schema Format

**Generic tool schema** (used by `MCPIntegration.get_tool_definitions_for_api()`):

```python
{
    "name": "file_read",
    "description": "Read content from a file...",
    "parameters": {
        "type": "object",
        "properties": {
            "file": {"type": "string", "description": "Relative file path"},
            "offset": {"type": "integer", "description": "Line offset (0-indexed)"},
            "limit": {"type": "integer", "description": "Number of lines"}
        },
        "required": ["file"]
    }
}
```

**Adapter auto-conversion:**
- OpenAI adapter wraps in: `{type: "function", function: {...}}`
- Anthropic adapter uses: `{name, description, input_schema}`

---

## 3. Question Gate Protocol

**CRITICAL:** This is the most complex part of the tool system and must be preserved during OpenAI SDK integration.

### Purpose

Prevent "runaway investigation loops" where the agent executes tools without clarifying ambiguity with the user first.

**Example Scenario:**
```
User: "Fix the bug in the payment system"

Agent (WITHOUT Question Gate):
- <terminal>git log --oneline</terminal>
- <read>payment.py</read>
- <grep>pattern="error"</grep>
- ... executes 20 tools investigating ...

Agent (WITH Question Gate):
<question>Which payment system are you referring to - Stripe, PayPal, or the legacy checkout?</question>
[Tools suspended: terminal, read, grep]

User: "Stripe"
[Tools execute: terminal, read, grep with Stripe context]
```

### Question Gate Configuration

**Location:** `LLMService.__init__()` (line 201)

```python
self.question_gate_enabled = config.get("core.llm.question_gate_enabled", True)
self.pending_tools: List[Dict[str, Any]] = []
self.question_gate_active = False
```

**Enable/disable:** Set `core.llm.question_gate_enabled` in config

### Question Detection

**File:** `/Users/malmazan/dev/kollabor-cli/core/llm/response_parser.py`

**Pattern:** (line 512)
```python
self.question_pattern = re.compile(
    r'<question>(.*?)</question>',
    re.DOTALL | re.IGNORECASE
)
```

**Extraction:** (line 630)
```python
def _extract_question(self, content: str) -> Optional[str]:
    match = self.question_pattern.search(content)
    if match:
        return match.group(1).strip()
    return None
```

**Question Gate Detection:** (line 593)
```python
has_question = question_content is not None
question_gate_active = has_question and total_tools > 0  # Only if tools present
```

**Turn Completion Logic:** (line 587)
```python
# Turn is completed if: no tools OR question present (tools suspended)
turn_completed = (total_tools == 0) or has_question
```

### Question Gate State Management

**Two Flags:**
1. `question_gate_active`: True when question detected + tools pending
2. `pending_tools`: List of tools to execute after user responds

### Complete Question Gate Flow

**Phase 1: Question Asked (Tool Suspension)**

**Location:** `llm_service.py` lines 1706-1713

```
LLM Response Received
    ↓
ResponseParser.parse_response()
    ↓
Detects <question> tag + tools
    ↓
question_gate_active = True
pending_tools = all_tools
    ↓
Tools NOT executed (suspended)
    ↓
Question displayed to user
```

**Code:** (line 1709)
```python
if self.question_gate_enabled and parsed_response.get("question_gate_active"):
    # Store tools for later execution when user responds
    self.pending_tools = all_tools
    self.question_gate_active = True
    logger.info(f"Question gate: suspended {len(all_tools)} tool(s) pending user response")
```

**Phase 2: User Responds (Tool Execution)**

**Location:** `llm_service.py` lines 1188-1234

```
User provides input
    ↓
process_user_input() called
    ↓
Check: question_gate_active AND pending_tools?
    ↓
YES: Execute pending tools
    ↓
Display tool results
    ↓
Inject results into conversation history
    ↓
Clear question gate state
    ↓
Process user's new input normally
```

**Code:** (line 1191)
```python
if self.question_gate_enabled and self.question_gate_active and self.pending_tools:
    logger.info(f"Question gate: executing {len(self.pending_tools)} suspended tool(s)")

    # Show tool execution indicator
    self.renderer.update_thinking(True, f"Executing {tool_desc}...")

    # Execute suspended tools
    tool_injection_results = await self.tool_executor.execute_all_tools(self.pending_tools)

    # Stop tool execution indicator
    self.renderer.update_thinking(False)

    # Display tool results
    self.message_display.display_complete_response(
        thinking_duration=0,
        response="",
        tool_results=tool_injection_results,
        original_tools=self.pending_tools
    )

    # Add tool results to conversation history
    batched_tool_results = []
    for result in tool_injection_results:
        tool_context = self.tool_executor.format_result_for_conversation(result)
        batched_tool_results.append(f"Tool result: {tool_context}")

    if batched_tool_results:
        self._add_conversation_message(ConversationMessage(
            role="user",
            content="\n".join(batched_tool_results)
        ))

    # Clear question gate state
    self.pending_tools = []
    self.question_gate_active = False
```

### Question Gate Edge Cases

**Question without tools:**
- `question_gate_active = False` (only True if both question AND tools)
- No suspension, normal processing

**Tools without question:**
- Normal tool execution
- No Question Gate involvement

**Multiple questions before response:**
- Each new response replaces `pending_tools`
- Only latest question's tools are executed

**Question Gate disabled:**
- Tools execute immediately even with `<question>` tag
- Question content still displayed (just not functional)

### Integration Points for OpenAI SDK

**Critical:** Question Gate logic is NOT in the API layer - it's in the orchestration layer.

**When migrating to OpenAI SDK:**
1. Keep `ResponseParser._extract_question()` unchanged
2. Keep `question_gate_active` and `pending_tools` state in `LLMService`
3. Keep suspension logic (lines 1709-1713, 1948-1952)
4. Keep injection logic (lines 1191-1234)
5. Only change: How tools are extracted from API response (batch vs incremental)

---

## 4. Tool Call Sites

**All 5+ execution locations documented:**

### Site 1: Native Tool Call Execution (Batch)

**Location:** `llm_service.py` line 1166
**Method:** `_execute_native_tool_calls()`
**Context:** Processing native API function calls

```python
result = await self.tool_executor.execute_tool(tool_data)
```

**Flow:**
1. API returns native tool calls (e.g., OpenAI function calling)
2. Loop through each tool call
3. Convert to `ToolExecutor` format
4. Execute individually via `execute_tool()`
5. Return list of `ToolExecutionResult` objects

**Called from:**
- `_process_message_batch()` (line 1632)
- `_continue_conversation()` (line 1878)

### Site 2: Question Gate Tool Injection

**Location:** `llm_service.py` line 1199
**Method:** `process_user_input()`
**Context:** User responds to question, execute suspended tools

```python
tool_injection_results = await self.tool_executor.execute_all_tools(self.pending_tools)
```

**Flow:**
1. User sends message
2. Check if `question_gate_active` AND `pending_tools` exist
3. Execute all pending tools as batch
4. Display results
5. Inject into conversation history
6. Clear question gate state

### Site 3: XML Tool Execution (Message Batch)

**Location:** `llm_service.py` line 1721
**Method:** `_process_message_batch()`
**Context:** Processing LLM response with XML-based tools

```python
tool_results = await self.tool_executor.execute_all_tools(all_tools)
```

**Flow:**
1. Parse response for XML tool tags
2. Extract all tools via `ResponseParser.get_all_tools()`
3. Check Question Gate (suspend if active)
4. If not suspended, execute all tools as batch
5. Return results for display and logging

### Site 4: XML Tool Execution (Continue Conversation)

**Location:** `llm_service.py` line 1960
**Method:** `_continue_conversation()`
**Context:** Continuation turn with XML-based tools

```python
tool_results = await self.tool_executor.execute_all_tools(all_tools)
```

**Flow:**
- Identical to Site 3, but in `_continue_conversation()` path
- Used when agent needs to continue after tool results

### Site 5: Individual Tool Execution

**Location:** `tool_executor.py` line 219
**Method:** `execute_all_tools()`
**Context:** Batch execution loops over tools

```python
result = await self.execute_tool(tool_data)
```

**Flow:**
- Called by `execute_all_tools()` for each tool
- Routes to type-specific handler
- Emits hooks
- Updates statistics

### Summary Table

| Site | Location | Method | Batch/Single | Context |
|------|----------|--------|--------------|---------|
| 1 | llm_service.py:1166 | _execute_native_tool_calls | Single (loop) | Native API calls |
| 2 | llm_service.py:1199 | process_user_input | Batch | Question Gate injection |
| 3 | llm_service.py:1721 | _process_message_batch | Batch | XML tools (initial) |
| 4 | llm_service.py:1960 | _continue_conversation | Batch | XML tools (continue) |
| 5 | tool_executor.py:219 | execute_all_tools | Single (loop) | Batch implementation |

---

## 5. MCP Integration

**File:** `/Users/malmazan/dev/kollabor-cli/core/llm/mcp_integration.py`

### Architecture

**Two-level integration:**
1. **MCP Server Management**: Connect to stdio MCP servers
2. **Tool Registry**: Register discovered tools for LLM use

### MCP Server Discovery

**Method:** `discover_mcp_servers()` (line 250)

**Process:**
1. Load configuration from:
   - Global: `~/.kollabor-cli/mcp/mcp_settings.json`
   - Local: `.kollabor-cli/mcp/mcp_settings.json` (overrides global)
2. For each `stdio` server:
   - Start server process
   - Send `initialize` request (MCP protocol)
   - Send `tools/list` request
   - Register discovered tools
3. Keep connections open for tool calls

**Configuration Format:**
```json
{
  "servers": {
    "filesystem": {
      "type": "stdio",
      "command": "npx -y @modelcontextprotocol/server-filesystem /path/to/allowed",
      "enabled": true
    },
    "github": {
      "type": "stdio",
      "command": "npx -y @modelcontextprotocol/server-github",
      "enabled": false
    }
  }
}
```

### MCP Protocol Implementation

**Connection Class:** `MCPServerConnection` (line 25)

**Protocol Flow:**
```
1. Start subprocess (stdio)
    ↓
2. Send initialize request
{
  "jsonrpc": "2.0",
  "id": "uuid",
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {},
    "clientInfo": {...}
  }
}
    ↓
3. Receive initialize response
    ↓
4. Send initialized notification
{
  "jsonrpc": "2.0",
  "method": "notifications/initialized"
}
    ↓
5. Send tools/list request
{
  "jsonrpc": "2.0",
  "id": "uuid",
  "method": "tools/list",
  "params": {}
}
    ↓
6. Receive tool definitions
    ↓
7. Connection ready for tool calls
```

**Tool Call Request:**
```json
{
  "jsonrpc": "2.0",
  "id": "uuid",
  "method": "tools/call",
  "params": {
    "name": "tool_name",
    "arguments": {...}
  }
}
```

### Tool Registry

**Data Structure:** (line 210)
```python
self.tool_registry: Dict[str, Dict[str, Any]] = {
    "tool_name": {
        "server": "server_name",
        "definition": {
            "name": "tool_name",
            "description": "...",
            "parameters": {...}
        },
        "enabled": True
    }
}
```

**Registration:** (line 320)
```python
for tool in tools:
    tool_name = tool.get("name")
    self.tool_registry[tool_name] = {
        "server": server_name,
        "definition": {...},
        "enabled": True
    }
```

### MCP Tool Execution

**Method:** `call_mcp_tool(tool_name, params)` (line 436)

**Process:**
1. Look up tool in registry
2. Get active server connection
3. Reconnect if connection lost
4. Call `connection.call_tool()`
5. Return result or error

**Result Handling:**
- Success: Format output using `_format_mcp_output()`
- Error: Return `{"error": "message"}`
- Timeout: Return timeout error after 180s

### MCP + Built-in File Operations

**Method:** `get_tool_definitions_for_api()` (line 602)

**Returns:**
1. All MCP tools from registry
2. 14 built-in file operation tools
3. Terminal command tool

**File Operation Tools:** (line 636)
- `file_read`, `file_create`, `file_create_overwrite`
- `file_edit`, `file_append`
- `file_insert_after`, `file_insert_before`
- `file_delete`, `file_move`, `file_copy`, `file_copy_overwrite`
- `file_mkdir`, `file_rmdir`
- `file_grep`
- `terminal`

**Why Built-in Tools?**
- File operations work without MCP servers
- Always available for basic file manipulation
- Match XML tag functionality for native API compatibility

---

## 6. Integration Notes for OpenAI SDK

### What Works Well

**Strengths:**
1. Clean separation: ToolExecutor doesn't know about API format
2. Hook system: Pre/post execution hooks for monitoring
3. Type-based routing: Easy to add new tool types
4. Batch execution: Sequential but reliable
5. Error handling: Tools continue on failure
6. Statistics: Comprehensive execution tracking

### What Needs to Change for OpenAI SDK

**Current Flow (Batch Tool Retrieval):**
```python
# Current: Anthropic/Claude streaming
response = await api_service.call_llm(messages)

# Parse entire response for all tools at once
parsed_response = response_parser.parse_response(response)
all_tools = response_parser.get_all_tools(parsed_response)

# Execute all tools as batch
tool_results = await tool_executor.execute_all_tools(all_tools)
```

**New Flow (Incremental Tool Accumulation):**
```python
# New: OpenAI SDK streaming
async for chunk in openai_client.chat.completions.create(...):
    # Accumulate tool calls incrementally
    if chunk.delta.tool_calls:
        for tool_call in chunk.delta.tool_calls:
            accumulated_tools[tool_call.index] += tool_call

    # When complete tool is received
    if tool_is_complete:
        # Execute immediately (incremental)
        result = await tool_executor.execute_tool(tool_call)
```

**Critical Changes:**

1. **Tool Extraction:** (Phase 6 - Addendum Priority 2)
   - Current: Batch extraction from complete response
   - New: Incremental extraction from streaming chunks
   - Location: `llm_service.py` lines 1682-1684 (XML) and 1107 (native)

2. **Tool Execution Timing:**
   - Current: All tools execute after response complete
   - New: Each tool executes when fully received
   - Benefit: Faster user feedback, parallel execution potential

3. **Question Gate Compatibility:**
   - **NO CHANGES NEEDED** - Question Gate is orchestration-level
   - Still detects `<question>` tags in response content
   - Still suspends tool execution until user responds
   - Must preserve lines 1709-1713 and 1948-1952

4. **Tool Result Formatting:**
   - Current: Batch results processed together
   - New: Individual results processed as they complete
   - Display logic needs to handle incremental results

5. **Conversation History:**
   - Current: Batch results added as single message
   - New: May need incremental history updates
   - Consider batching for cleaner history

### Preserved Components

**These components remain unchanged:**

1. **ToolExecutor:** No API-specific logic
2. **Question Gate:** Orchestrations-level, not API-level
3. **File Operations:** API-agnostic
4. **MCP Integration:** Independent of API choice
5. **Tool Schema:** Generic format auto-converted by adapters

### Migration Strategy

**Phase 6 Implementation:**

1. **Create OpenAI Adapter** (Phase 1-5 complete)
   - Wraps OpenAI SDK
   - Implements `APICommunicationService` interface
   - Handles streaming chunks

2. **Incremental Tool Extraction** (Addendum Priority 2)
   - Accumulate tool calls across streaming chunks
   - Detect when tool is complete
   - Trigger execution immediately

3. **Preserve Question Gate**
   - Keep suspension logic intact
   - Keep injection logic intact
   - Only change tool retrieval mechanism

4. **Update Tool Call Sites**
   - Sites 3 and 4: Use incremental extraction
   - Site 1: Use OpenAI's native tool calls
   - Sites 2 and 5: No changes needed

---

## 7. Key Findings

### Tool System Strengths

1. **Unified Interface:** Single `ToolExecutor` for all tool types
2. **Extensible:** Easy to add new tool types
3. **Robust Error Handling:** Tools continue on failure
4. **Hook Integration:** Pre/post execution hooks
5. **Comprehensive Logging:** All tool executions logged

### Question Gate Excellence

1. **Prevents Runaway Loops:** Stops investigation before it starts
2. **Clean State Management:** Simple two-flag system
3. **User-Friendly:** Clear question display
4. **Conversation Context:** Tool results injected properly

### MCP Integration Quality

1. **Standard Protocol:** Proper MCP JSON-RPC implementation
2. **Server Management:** Auto-discovery and reconnection
3. **Tool Registry:** Clean separation of concerns
4. **Built-in Tools:** File operations without MCP dependency

### Critical Success Factors for OpenAI Integration

1. **Preserve Question Gate:** DO NOT change suspension/injection logic
2. **Maintain Tool Format:** Keep generic tool schema
3. **Support Both Paths:** Native + XML for compatibility
4. **Incremental Execution:** Implement streaming tool accumulation
5. **Hook Preservation:** Keep pre/post tool execution hooks

---

## 8. Testing Checklist

**After OpenAI SDK integration, verify:**

- [ ] Terminal commands execute correctly
- [ ] MCP tools work with OpenAI API
- [ ] File operations execute properly
- [ ] Question Gate suspends tools when `<question>` present
- [ ] Question Gate executes suspended tools on user response
- [ ] Tool results display correctly
- [ ] Tool results log to conversation history
- [ ] Hooks emit correctly (TOOL_CALL_PRE, TOOL_CALL_POST)
- [ ] Statistics track correctly
- [ ] Batch execution works (XML tools)
- [ ] Individual execution works (native tools)
- [ ] Error handling continues on tool failure

---

## 9. Code Locations Reference

**Tool Executor:**
- Main class: `/Users/malmazan/dev/kollabor-cli/core/llm/tool_executor.py:65`
- Execute single: `tool_executor.py:104`
- Execute batch: `tool_executor.py:201`
- Terminal execution: `tool_executor.py:234`
- MCP execution: `tool_executor.py:312`
- File ops execution: `tool_executor.py:416`

**Response Parser:**
- Question pattern: `response_parser.py:512`
- Question extraction: `response_parser.py:630`
- Tool extraction (XML): `response_parser.py:693`
- Tool extraction (native): `response_parser.py:725`
- Get all tools: `response_parser.py:954`

**LLM Service:**
- Question Gate state: `llm_service.py:199-201`
- Question Gate suspension: `llm_service.py:1709-1713`, `llm_service.py:1948-1952`
- Question Gate injection: `llm_service.py:1191-1234`
- Native tool execution: `llm_service.py:1092-1171`
- XML tool execution (batch): `llm_service.py:1721`, `llm_service.py:1960`

**MCP Integration:**
- Main class: `/Users/malmazan/dev/kollabor-cli/core/llm/mcp_integration.py:198`
- Server discovery: `mcp_integration.py:250`
- Tool registration: `mcp_integration.py:320`
- Tool execution: `mcp_integration.py:436`
- Tool definitions: `mcp_integration.py:602`

---

## 10. Conclusion

The Kollabor CLI tool system is well-architected for multi-API support. The clean separation between:
- **API layer** (how tools are retrieved)
- **Orchestration layer** (when/how tools are executed)
- **Execution layer** (actual tool operations)

...makes OpenAI SDK integration straightforward.

**Critical preservation:** Question Gate protocol must remain intact during Phase 6. The two-flag system (`question_gate_active`, `pending_tools`) is elegant and effective - do not refactor.

**Next phase:** Phase 1 (API Adapter Interface) can proceed with confidence that the tool system will integrate cleanly.

---

**End of Analysis**
