# Message Building Analysis

## Message Preparation Flow

### Overview
The message building process transforms user input into API-ready messages through a multi-stage pipeline that handles system prompts, conversation history, tool results, and MCP integration.

### Stage 1: User Input Reception
**Location:** `core/llm/llm_service.py::process_user_input()`

1. **Display user message** via MessageDisplayService
2. **Question Gate handling:** If question gate is active and pending tools exist:
   - Execute suspended tools (via `tool_executor.execute_all_tools()`)
   - Format results for conversation (via `tool_executor.format_result_for_conversation()`)
   - Add tool results as user message to history
   - Clear question gate state
3. **Log user message** to conversation logger (JSONL format)
4. **Add to processing queue** with overflow handling
5. **Start queue processing** if not already running

### Stage 2: Message Batch Processing
**Location:** `core/llm/llm_service.py::_process_message_batch()`

1. **Combine queued messages** into single input
2. **Add to conversation history** as user message:
   ```python
   self._add_conversation_message(ConversationMessage(
       role="user",
       content=combined_message
   ))
   ```
3. **Start thinking animation**
4. **Call LLM API** via `_call_llm()`

### Stage 3: API Message Preparation
**Location:** `core/llm/api_communication_service.py::_prepare_messages()`

1. **Apply history limit** (max_history parameter)
2. **Format messages for API:**
   ```python
   messages = []
   for msg in recent_messages:
       if hasattr(msg, 'role'):
           role, content = msg.role, msg.content
       else:
           role, content = msg["role"], msg["content"]
       messages.append({
           "role": role,
           "content": content
       })
   ```
3. **Return formatted messages** to API service

### Stage 4: Provider-Specific Formatting
**Location:** `core/llm/api_communication_service.py::call_llm()` → Adapter

1. **Select adapter** based on `tool_format` (openai/anthropic)
2. **Format request payload** using adapter:
   ```python
   payload = self._adapter.format_request(
       messages=messages,
       model=self.model,
       temperature=self.temperature,
       stream=self.enable_streaming,
       max_tokens=int(self.max_tokens) if self.max_tokens else None,
       tools=tools  # Native function calling
   )
   ```
3. **Execute HTTP request** with proper headers

### Stage 5: Response Processing
**Location:** `core/llm/api_communication_service.py::_execute_request_with_error_handling()`

1. **Send request** to LLM API endpoint
2. **Handle streaming or non-streaming response**
3. **Parse response** via adapter (unified format)
4. **Extract tool calls** (native function calling)
5. **Return content** to LLM service

## System Prompt Handling

### System Prompt Priority Order
**Location:** `core/llm/llm_service.py::_build_system_prompt()`

The system prompt is built in this priority order:

1. **Active agent's system prompt** (if agent manager has active agent)
2. **KOLLABOR_SYSTEM_PROMPT** environment variable (direct string)
3. **KOLLABOR_SYSTEM_PROMPT_FILE** environment variable (file path)
4. **Local .kollabor-cli/agents/default/system_prompt.md** (project-specific)
5. **Global ~/.kollabor-cli/agents/default/system_prompt.md** (default)
6. **Fallback to minimal default** (emergency fallback)

### System Prompt Initialization
**Location:** `core/utils/config_utils.py::get_system_prompt_content()`

1. **Check CLI override** (`--system-prompt` argument via `_cli_system_prompt_file`)
2. **Check environment variables** (direct string or file path)
3. **Resolve file path** (checks local then global)
4. **Read file content** (UTF-8 encoding)
5. **Return content or fallback**

### Dynamic System Prompt Rendering
**Location:** `core/utils/prompt_renderer.py::render_system_prompt()`

System prompts support `<trender>` tags for dynamic content:

**Supported Tags:**
```xml
<!-- Execute shell command -->
<trender>ls -la</trender>

<!-- Include file content -->
<trender type="include" path="sections/file.md" />
```

**Rendering Process:**
1. **Initialize PromptRenderer** with timeout and base_path
2. **Process includes first** (files can contain commands)
3. **Process commands** (shell execution with timeout)
4. **Cache results** (commands and files)
5. **Iterate until no tags remain** (max 100 iterations to prevent loops)
6. **Return rendered prompt**

### System Prompt Finalization
**Location:** `core/llm/llm_service.py::_finalize_system_prompt()`

After base prompt is loaded and rendered, additional sections are added:

1. **Project structure** (if enabled):
   ```python
   include_structure = self.config.get("core.llm.system_prompt.include_project_structure", True)
   ```
   - Runs `tree` command to show directory structure
   - Adds as `## Project Structure` section

2. **Attachment files** (from config):
   ```python
   attachment_files = self.config.get("core.llm.system_prompt.attachment_files", [])
   ```
   - Reads specified files
   - Appends to prompt

3. **Custom prompt files** (from config):
   ```python
   custom_files = self.config.get("core.llm.system_prompt.custom_prompt_files", [])
   ```
   - Reads custom markdown files
   - Appends to prompt

4. **Plugin additions**:
   ```python
   plugin_additions = self._get_plugin_system_prompt_additions()
   ```
   - Queries all plugins for `get_system_prompt_addition()`
   - Collects and appends plugin-specific content

5. **Join all parts** with `\n\n` separator

### System Prompt Storage
System prompts are NOT stored in `conversation_history` directly. Instead:
- System prompt is the **first message** in `conversation_history` (index 0)
- It's a `ConversationMessage` object with `role="system"`
- Can be rebuilt via `rebuild_system_prompt()` method
- Custom prompts can temporarily replace it (e.g., background tasks)

## Conversation History Management

### Message Storage Structure
**Location:** `core/llm/conversation_manager.py`

Messages are stored with rich metadata:

```python
message = {
    "uuid": str(uuid4()),
    "role": "user|assistant|system",
    "content": str,
    "timestamp": ISO format,
    "parent_uuid": str,  # For threading
    "metadata": dict,
    "session_id": str
}
```

### In-Memory Storage
**Primary structures:**
- `self.messages: List[Dict]` - All messages in order
- `self.message_index: Dict[uuid, Dict]` - UUID lookup
- `self.context_window: List[Dict]` - Recent messages for API (managed by max_history)
- `self.current_parent_uuid: str` - Tracks last message for threading

### History Truncation
**Location:** `core/llm/conversation_manager.py::_update_context_window()`

1. **Sliding window approach:**
   ```python
   self.context_window = self.messages[-self.max_history:]
   ```
2. **Preserve system message:**
   ```python
   system_messages = [m for m in self.messages if m["role"] == "system"]
   if system_messages and system_messages[0] not in self.context_window:
       self.context_window = [system_messages[0]] + self.context_window
   ```

### Context Window Handling
**Location:** `core/llm/api_communication_service.py::_prepare_messages()`

1. **Apply max_history limit:**
   ```python
   if max_history:
       recent_messages = conversation_history[-max_history:]
   ```
2. **Format for API** (role + content only)
3. **Preserve system message** (always included if exists)

### Message Serialization
**Location:** `core/llm/conversation_logger.py`

Messages are logged to JSONL format per-message:

```python
# User message format
{
    "type": "user",
    "message": {"role": "user", "content": "..."},
    "uuid": "...",
    "timestamp": "...",
    "sessionId": "...",
    "kollabor_intelligence": {
        "user_context": {...},
        "session_context": {...},
        "project_awareness": {...}
    }
}

# Assistant message format
{
    "type": "assistant",
    "message": {
        "role": "assistant",
        "content": [{"type": "text", "text": "..."}],
        "usage": {...}
    },
    "uuid": "...",
    "timestamp": "...",
    "requestId": "..."
}
```

### Session Management
**Session IDs:** Memorable names like `250114-1030-optimizing` (YYMMDD-HHMM-keyword)

**Storage locations:**
- Conversations: `~/.kollabor-cli/projects/<encoded-path>/conversations/`
  - `{session_id}.jsonl` - Live session log
  - `snapshots/{session_id}_snapshot.json` - JSON exports
- Memory: `conversations/memory/`
  - `user_patterns.json`
  - `project_context.json`
  - `solution_history.json`

**Session branching:**
- Can branch from any message in existing session
- Creates new session with renamed UUIDs
- Preserves parent UUID relationships
- Useful for exploring alternate conversation paths

## MCP Integration

### MCP Tool Discovery
**Location:** `core/llm/mcp_integration.py::discover_mcp_servers()`

1. **Load MCP config** 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 via subprocess
   - Send MCP `initialize` request (protocol version 2024-11-05)
   - Send `notifications/initialized` notification
   - Request tools via `tools/list`
   - Register tools in `tool_registry`

3. **Tool registration:**
   ```python
   self.tool_registry[tool_name] = {
       "server": server_name,
       "definition": {
           "name": tool_name,
           "description": ...,
           "parameters": inputSchema
       },
       "enabled": True
   }
   ```

### MCP Tool Schema Format
**Location:** `core/llm/mcp_integration.py::get_tool_definitions_for_api()`

MCP tools are returned in **generic format** that adapters auto-convert:

```python
{
    "name": "tool_name",
    "description": "Tool description",
    "parameters": {
        "type": "object",
        "properties": {...},
        "required": [...]
    }
}
```

**Adapter conversion:**
- **OpenAI adapter:** Wraps in `{type: "function", function: {...}}`
- **Anthropic adapter:** Uses as-is (matches Anthropic format)

### MCP Tool Execution
**Location:** `core/llm/mcp_integration.py::call_mcp_tool()`

1. **Get tool info** from registry
2. **Get active connection** to server
3. **Call via MCP protocol:**
   ```python
   await connection.call_tool(tool_name, params)
   ```
4. **Return result** (output or error)

### MCP Integration with Messages

**Tool calls are NOT added to conversation history automatically.**

Instead, tool execution flow is:
1. LLM requests tool (via native function calling or XML tags)
2. Tool executor executes tool (MCP or built-in)
3. Tool results are formatted as user message
4. Tool results are logged to conversation logger
5. Results are added to conversation history:
   ```python
   self._add_conversation_message(ConversationMessage(
       role="user",
       content="Tool result: ..."
   ))
   ```

### Built-in File Operation Tools
**Location:** `core/llm/mcp_integration.py::_get_file_operation_tools()`

The system provides built-in file tools alongside MCP tools:
- `file_read`, `file_create`, `file_edit`
- `file_append`, `file_insert_after`, `file_insert_before`
- `file_delete`, `file_move`, `file_copy`
- `file_mkdir`, `file_rmdir`, `file_grep`
- `terminal` (execute shell commands)

These are native Python functions, not MCP servers.

## Message Transformation Points

### ALL Message Transformation Locations

1. **User Input → Conversation History**
   - **Location:** `llm_service.py::_add_conversation_message()`
   - **Transformation:** String → `ConversationMessage(role, content, parent_uuid, metadata)`
   - **Storage:** Added to `conversation_history` list
   - **Logging:** Sent to `conversation_logger.log_user_message()`

2. **Conversation History → API Messages**
   - **Location:** `api_communication_service.py::_prepare_messages()`
   - **Transformation:** `ConversationMessage` objects → `{"role": ..., "content": ...}` dicts
   - **Truncation:** Applied `max_history` limit (sliding window)
   - **Output:** List of simple message dicts

3. **API Messages → Provider Format**
   - **Location:** `api_adapters/{openai,anthropic}_adapter.py::format_request()`
   - **Transformation:** Generic messages → Provider-specific format
   - **OpenAI:** `{"messages": [{"role": ..., "content": ...}]}`
   - **Anthropic:** `{"messages": [{"role": ..., "content": [...]}]}` (content blocks)
   - **Tools:** Converted to provider format

4. **Tool Results → Conversation History**
   - **Location:** `tool_executor.py::format_result_for_conversation()` (called from `llm_service.py`)
   - **Transformation:** Tool result objects → User message string
   - **Format:** `"Tool result: {tool_type} ({tool_id}): {output}"`
   - **Storage:** Added as user message to `conversation_history`

5. **System Prompt Building**
   - **Location:** `llm_service.py::_build_system_prompt()`
   - **Transformation:** File/agent → Rendered prompt
   - **Steps:**
     - Load base prompt (file/agent/env var)
     - Render `<trender>` tags (commands/includes)
     - Add project structure
     - Add attachment files
     - Add custom prompt files
     - Add plugin additions
     - Join with `\n\n`
   - **Storage:** First message in `conversation_history` (index 0)

6. **Response Processing**
   - **Location:** `api_communication_service.py` (streaming/non-streaming)
   - **Transformation:** Provider response → Unified format
   - **Adapter:** `parse_response()` returns `AdapterResponse`
   - **Fields:** content, tool_calls, usage, stop_reason, model
   - **Storage:** Assistant message added to `conversation_history`

7. **Message Serialization (Logging)**
   - **Location:** `conversation_logger.py::log_user_message()` / `log_assistant_message()`
   - **Transformation:** `ConversationMessage` → JSONL entry
   - **Fields:** type, message, uuid, timestamp, sessionId, kollabor_intelligence
   - **Storage:** Appended to `{session_id}.jsonl`

### Tool Schema Transformation

1. **MCP Tools → Generic Format**
   - **Location:** `mcp_integration.py::get_tool_definitions_for_api()`
   - **Input:** MCP tool definition (name, description, inputSchema)
   - **Output:** Generic format (name, description, parameters)
   - **Note:** Built-in file tools also added here

2. **Generic Format → Provider Format**
   - **Location:** `api_adapters/{openai,anthropic}_adapter.py::format_request()`
   - **OpenAI:** Wraps in `{type: "function", function: {...}}`
   - **Anthropic:** Uses as-is (matches Anthropic's tool format)

### Native Tool Call Transformation

1. **Response → Tool Calls**
   - **Location:** `api_adapters/{openai,anthropic}_adapter.py::parse_response()`
   - **OpenAI:** Extracts `tool_calls` from response
   - **Anthropic:** Extracts `tool_use` content blocks
   - **Output:** `List[ToolCallResult]` (unified format)

2. **Tool Results → API Messages**
   - **Location:** `api_communication_service.py::format_tool_result()`
   - **Delegates to:** `adapter.format_tool_result(tool_id, result, is_error)`
   - **OpenAI:** `{role: "tool", tool_call_id: ..., content: ...}`
   - **Anthropic:** `{role: "user", content: [{type: "tool_result", tool_use_id: ..., content: ...}]}`

## Integration Notes

### Provider-Specific Assumptions

**Current implementation assumes:**
1. **Message format:** `{"role": "...", "content": "..."}` (universal)
2. **Tool format:** OpenAI-style (adapted for Anthropic)
3. **Streaming:** Server-Sent Events (SSE)
4. **Auth:** Bearer token in headers

**What needs to change for multi-provider support:**

1. **Message content structures:**
   - OpenAI: Simple string content
   - Anthropic: Array of content blocks (text, image, tool_use, tool_result)
   - **Solution:** Adapters handle content transformation

2. **Tool result format:**
   - OpenAI: Separate message with `role: "tool"`
   - Anthropic: Content block in user message
   - **Solution:** `adapter.format_tool_result()` handles conversion

3. **System message placement:**
   - OpenAI: System message in messages array
   - Anthropic: Separate `system` parameter (not in messages)
   - **Solution:** Adapter extracts system message and places correctly

4. **Streaming response format:**
   - OpenAI: `data: {"choices": [{"delta": {"content": "..."}}]}`
   - Anthropic: `data: {"type": "content_block_delta", "delta": {"text": "..."}}`
   - **Solution:** Adapter-specific streaming handlers

### Tool Schema Injection Points

**Current flow:**
1. MCP tools discovered → `mcp_integration.get_tool_definitions_for_api()`
2. Built-in file tools added to MCP tools
3. Generic format passed to `api_service.call_llm(tools=...)`
4. Adapter converts generic tools to provider format in `format_request()`

**For OpenAI SDK integration:**
- Tool schemas already in generic format
- Need to convert to OpenAI SDK's `ChatCompletionTool` format
- Should handle in new `OpenAIClientAdapter`

### System Prompt Considerations

**Provider differences:**
- **OpenAI:** System message is part of messages array
- **Anthropic:** System is separate top-level parameter

**Current solution:**
- Adapters extract system message from messages array
- Anthropic adapter places it in `system` parameter
- OpenAI adapter leaves it in messages array

**For OpenAI SDK:**
- Will need to extract system message
- Pass to SDK as `system` parameter or separate messages array entry

### Conversation History Format

**Current format (provider-agnostic):**
```python
[
    ConversationMessage(role="system", content="..."),
    ConversationMessage(role="user", content="..."),
    ConversationMessage(role="assistant", content="..."),
    ...
]
```

**Transformation to API:**
```python
[
    {"role": "system", "content": "..."},
    {"role": "user", "content": "..."},
    {"role": "assistant", "content": "..."},
    ...
]
```

**For OpenAI SDK:**
- Need to convert to SDK's message format
- Likely similar to current API format
- May need to handle content blocks (images, etc.)

### MCP Context Injection

**Current approach:**
- MCP tools are registered and available as native function calling
- MCP servers can also provide resources (not yet implemented)
- No automatic MCP context injection into system prompt

**Future considerations:**
- MCP resources could be injected into system prompt
- MCP server capabilities could be listed
- Project context from MCP servers could be included

## Summary

The message building pipeline follows this flow:

```
User Input
    ↓
process_user_input() [Question Gate handling]
    ↓
_add_conversation_message() [Store in history]
    ↓
_process_message_batch() [Queue processing]
    ↓
_call_llm() [Prepare API call]
    ↓
api_service.call_llm()
    ↓
_prepare_messages() [Truncate history]
    ↓
adapter.format_request() [Provider formatting]
    ↓
HTTP Request to LLM API
    ↓
adapter.parse_response() [Parse response]
    ↓
Add assistant message to history
```

**Key integration points for OpenAI SDK:**
1. Replace HTTP client in `api_communication_service.py`
2. Use adapter pattern to convert message formats
3. Preserve conversation history management (no changes needed)
4. Preserve system prompt rendering (no changes needed)
5. Preserve MCP tool integration (no changes needed)
6. Transform tool schemas to SDK format in adapter
7. Handle streaming via SDK's streaming interface
