# Victor Architecture Deep Dive & Analysis

**For Newbies: A Comprehensive Guide to Understanding Victor's Design**

> Created: 2025-11-26
> Updated: 2025-12-06
> Author: Architecture Analysis
> Audience: New developers, contributors, and system designers

---

> Status: This document reflects the current architecture as of 2025-11-30. Most roadmap items from Phases 1-3 are now implemented. See CODEBASE_ANALYSIS_REPORT.md for remaining work.

## Table of Contents

1. [System Overview](#system-overview)
2. [Current Architecture](#current-architecture)
3. [Tool Calling System](#tool-calling-system)
4. [MCP Integration](#mcp-integration)
5. [Missing Capabilities](#missing-capabilities)
6. [Design Issues](#design-issues)
7. [Optimization Recommendations](#optimization-recommendations)
8. [Implementation Roadmap](#implementation-roadmap)

---

## 1. System Overview

### What is Victor?

Victor is an **enterprise-ready, terminal-based AI coding assistant** that acts as a universal interface for multiple LLM providers. Think of it as a "Swiss Army knife" for AI coding - it works with both expensive cloud models (Claude, GPT-4, Gemini) and free local models (Ollama, vLLM).

### The Core Problem Victor Solves

**Problem**: Different AI providers (Anthropic, OpenAI, Google, Ollama) all have different APIs, tool calling formats, and capabilities.

**Solution**: Victor provides a unified abstraction layer that:
- Normalizes all provider differences into a single interface
- Provides 45 enterprise-grade tools (git, testing, security, docker, etc.)
- Enables air-gapped (offline) operation for compliance
- Uses intelligent semantic tool selection instead of broadcasting all tools

---

## 2. Current Architecture

### 2.1 High-Level Layered Design

```mermaid
flowchart TB
    subgraph UI["USER INTERFACES"]
        CLI["CLI<br/>(victor/ui/cli.py)"]
        MCP_UI["MCP Server<br/>(Claude Desktop, VS Code)"]
    end

    subgraph ORCH["AGENT ORCHESTRATOR (Brain)"]
        direction TB
        ORC["victor/agent/orchestrator.py"]
        RESP["• Manage conversation history<br/>• Select relevant tools intelligently<br/>• Execute tool calls from LLM<br/>• Handle streaming responses<br/>• Enforce tool budget limits"]
    end

    subgraph SYSTEMS[""]
        direction LR
        PROV["PROVIDER SYSTEM<br/>━━━━━━━━━━━━<br/>Normalize different<br/>LLM APIs"]
        TOOLS["TOOLS SYSTEM<br/>━━━━━━━━━━━━<br/>45 tools<br/>registered in registry"]
        MCP_B["MCP BRIDGE<br/>━━━━━━━━━━━━<br/>Client +<br/>Server"]
    end

    UI --> ORCH
    ORCH --> PROV
    ORCH --> TOOLS
    ORCH --> MCP_B

    style UI fill:#e0e7ff,stroke:#4f46e5
    style ORCH fill:#d1fae5,stroke:#10b981
    style PROV fill:#fef3c7,stroke:#f59e0b
    style TOOLS fill:#fef3c7,stroke:#f59e0b
    style MCP_B fill:#fef3c7,stroke:#f59e0b
```

### 2.2 Key Components Explained

#### A. AgentOrchestrator (The Brain)
**Location**: `victor/agent/orchestrator.py:65`

**Think of it as**: The conductor of an orchestra - it coordinates everything.

**What it does**:
1. **Manages Conversation**: Keeps track of all messages (user, assistant, tool results)
2. **Selects Tools**: Intelligently chooses which tools to send to the LLM (not all 45!)
3. **Executes Tools**: When LLM requests a tool, orchestrator runs it
4. **Streams Responses**: Shows results in real-time
5. **Enforces Budget**: Limits tool calls to prevent infinite loops

**Key Data**:
- `self.messages`: Full conversation history
- `self.tools`: ToolRegistry with all 45 tools
- `self.provider`: Current LLM provider (Claude, Ollama, etc.)
- `self.semantic_selector`: Optional embedding-based tool selector

**Decomposed Components** (as of December 2025):

The orchestrator follows the **facade pattern**, delegating to specialized components:

```mermaid
flowchart TB
    subgraph FACADE["AgentOrchestrator (Facade)"]
        direction TB

        subgraph ROW1[" "]
            direction LR
            CC["**ConversationController**<br/>• Message history<br/>• Context tracking<br/>• Stage management"]
            TP["**ToolPipeline**<br/>• Tool validation<br/>• Execution coord<br/>• Budget enforcement"]
            SC["**StreamingController**<br/>• Session lifecycle<br/>• Cancellation<br/>• Metrics"]
        end

        subgraph ROW2[" "]
            direction LR
            MC["**MetricsCollector**<br/>• Stream metrics<br/>• Tool selection<br/>• Cost tracking"]
            TA["**TaskAnalyzer**<br/>• Complexity class.<br/>• Task/intent class.<br/>• Unified facade"]
            MS["**ModelSwitcher**<br/>• Provider swap<br/>• Fallback chains<br/>• Hot-swap support"]
        end

        UTC["**UnifiedTaskClassifier**<br/>• Negation detection<br/>• Context boosting<br/>• LRU cache (TTL)"]

        TA --> UTC
    end

    style FACADE fill:#f0f9ff,stroke:#0284c7
    style CC fill:#d1fae5,stroke:#10b981
    style TP fill:#d1fae5,stroke:#10b981
    style SC fill:#d1fae5,stroke:#10b981
    style MC fill:#fef3c7,stroke:#f59e0b
    style TA fill:#fef3c7,stroke:#f59e0b
    style MS fill:#fef3c7,stroke:#f59e0b
    style UTC fill:#e0e7ff,stroke:#4f46e5
```

| Component | Location | Responsibility |
|-----------|----------|----------------|
| `ConversationController` | `victor/agent/conversation_controller.py` | Message history, context size tracking, conversation stage management |
| `ToolPipeline` | `victor/agent/tool_pipeline.py` | Tool validation, execution coordination, budget enforcement |
| `StreamingController` | `victor/agent/streaming_controller.py` | Session lifecycle, cancellation handling |
| `MetricsCollector` | `victor/agent/metrics_collector.py` | Stream metrics (TTFT, throughput), tool selection stats, cost tracking |
| `TaskAnalyzer` | `victor/agent/task_analyzer.py` | Unified facade for complexity/task/intent classification |
| `UnifiedTaskClassifier` | `victor/agent/unified_classifier.py` | Keyword + semantic classification with negation detection |
| `ToolRegistrar` | `victor/agent/tool_registrar.py` | Dynamic tool discovery, plugin loading, MCP integration, tool dependency graph |
| `ProviderManager` | `victor/agent/provider_manager.py` | Unified provider/model management, switching, health monitoring, fallback chains |
| `ToolSequenceTracker` | `victor/agent/tool_sequence_tracker.py` | Workflow pattern detection, transition probability tracking, 15-20% tool selection boost |
| `UsageAnalytics` | `victor/agent/usage_analytics.py` | Tool/provider performance tracking, Prometheus/JSON export, optimization recommendations |
| `ContextCompactor` | `victor/agent/context_compactor.py` | Proactive context compaction (70% threshold), smart tool result truncation |

**Benefits of Decomposition**:
1. **Testability**: Each component can be unit tested in isolation
2. **Maintainability**: Changes are localized to specific modules
3. **Clarity**: Single responsibility per component
4. **Scalability**: New functionality via new components, not god-class expansion

**UnifiedTaskClassifier Architecture** (as of December 2025):

The `UnifiedTaskClassifier` provides robust task classification with negation detection:

```mermaid
flowchart TB
    subgraph UTC["UnifiedTaskClassifier"]
        INPUT["📥 **Input**<br/>\"Don't analyze this, just run the tests\""]

        subgraph STEP1["1. Keyword Detection"]
            KW1["\"analyze\" → ANALYSIS, weight: 1.0"]
            KW2["\"run\" → ACTION, weight: 0.9"]
        end

        subgraph STEP2["2. Negation Detection"]
            NEG1["\"Don't\" before \"analyze\" → ❌ NEGATED"]
            NEG2["\", just\" before \"run\" → ✅ NOT NEGATED"]
        end

        subgraph STEP3["3. Score Calculation"]
            SCORE1["ACTION score: 0.9 (run)"]
            SCORE2["ANALYSIS score: -0.5 (negated)"]
        end

        OUTPUT["📤 **Output**<br/>ClassificationResult(<br/>  task_type=ACTION,<br/>  confidence=0.78,<br/>  negated_keywords=[\"analyze\"]<br/>)"]

        INPUT --> STEP1
        STEP1 --> STEP2
        STEP2 --> STEP3
        STEP3 --> OUTPUT
    end

    style UTC fill:#f0f9ff,stroke:#0284c7
    style INPUT fill:#e0e7ff,stroke:#4f46e5
    style OUTPUT fill:#d1fae5,stroke:#10b981
    style STEP1 fill:#fef3c7,stroke:#f59e0b
    style STEP2 fill:#fee2e2,stroke:#ef4444
    style STEP3 fill:#dbeafe,stroke:#3b82f6
```

**Key Features**:

| Feature | Description |
|---------|-------------|
| **Negation Patterns** | Detects "don't", "do not", "skip", "without", "avoid", "never" within 30-char window |
| **Positive Overrides** | ", just", ", instead", "but do" cancel earlier negation for subsequent keywords |
| **Weighted Keywords** | Keywords have weights (0.5-1.0) based on signal strength |
| **LRU Cache with TTL** | 256-entry cache, 5-minute TTL, MD5-based keys |
| **Context Boosting** | Conversation history boosts confidence for consistent task types |
| **Ensemble Voting** | Combines keyword, semantic, and context signals |

**Classification Types** (`TaskType` enum):
- `ACTION`: Execute commands, deploy, build, install
- `ANALYSIS`: Analyze, review, audit, explain
- `GENERATION`: Create, generate, write, implement
- `SEARCH`: Find, search, locate, grep
- `EDIT`: Refactor, modify, update, rename
- `DEFAULT`: Ambiguous or conversational messages

**Integration with TaskAnalyzer**:
```python
# TaskAnalyzer uses UnifiedTaskClassifier for robust classification
analyzer = TaskAnalyzer()
result = analyzer.analyze("Don't analyze, just run tests")

print(result.unified_task_type)      # TaskType.ACTION
print(result.is_action_task)         # True
print(result.is_analysis_task)       # False
print(result.negated_keywords)       # ["analyze"]
print(result.needs_execution)        # True
```

**Caching Behavior**:
```python
classifier = UnifiedTaskClassifier()

# First call - cache miss (computed)
result1 = classifier.classify("Analyze the code")

# Second call - cache hit (instant)
result2 = classifier.classify("Analyze the code")

# Stats
stats = classifier.get_cache_stats()
# {"cache_size": 1, "cache_hits": 1, "cache_misses": 1, "hit_rate": 0.5}
```

#### B. Provider System
**Location**: `victor/providers/`

**Think of it as**: Universal translators for different languages (LLM APIs).

**The Problem**: Each LLM provider has different formats:
```python
# Anthropic format
{
  "role": "assistant",
  "content": [{"type": "tool_use", "name": "read_file", ...}]
}

# OpenAI format
{
  "role": "assistant",
  "tool_calls": [{"function": {"name": "read_file", ...}}]
}

# Ollama format (varies by model!)
{
  "message": {"content": "```json\n{\"name\":\"read_file\",...}\n```"}
}
```

**The Solution**: `BaseProvider` (victor/providers/base.py:70) defines:
- Standard `Message` format
- Standard `CompletionResponse` format
- Standard `ToolDefinition` format

Each provider (AnthropicProvider, OllamaProvider, etc.) translates to/from its native format.

**Key Methods**:
- `chat(messages, tools, **kwargs) → CompletionResponse`: Synchronous completion
- `stream(messages, tools, **kwargs) → AsyncIterator[StreamChunk]`: Streaming
- `supports_tools() → bool`: Does this provider support tool calling?

#### C. Tool System
**Location**: `victor/tools/`

**Think of it as**: A toolbox where each tool is a self-contained function.

**How Tools Work**:

1. **Definition**: Each tool is a Python function decorated with `@tool`:
```python
@tool
async def read_file(path: str) -> Dict[str, Any]:
    """Read contents of a file.

    Args:
        path: File path to read

    Returns:
        Dictionary with file contents
    """
    # Implementation here
    return {"success": True, "content": "..."}
```

2. **Registration**: Tools are registered in `ToolRegistry`:
```python
# In orchestrator.__init__
self.tools.register(read_file)
self.tools.register(write_file)
self.tools.register(execute_bash)
# ... 29 more tools
```

3. **Execution**: When LLM calls a tool:
```python
result = await self.tools.execute(
    name="read_file",
    context={"code_manager": self.code_manager, ...},
    path="/path/to/file.py"
)
```

**Tool Categories** (46 total):
- **File Operations**: read_file, write_file, edit_files, list_directory
- **Execution**: execute_bash, execute_python_in_sandbox
- **Git**: git, git_suggest_commit, git_create_pr
- **Code Quality**: code_review, security_scan, analyze_metrics
- **Testing**: run_tests
- **Refactoring**: refactor_extract_function, refactor_inline_variable, refactor_organize_imports
- **Code Intelligence**: find_symbol, find_references, rename_symbol
- **Documentation**: generate_docs, analyze_docs
- **Web**: web_search, web_fetch, web_summarize (disabled in air-gapped mode)
- **Batch**: batch (process multiple files)
- **CI/CD**: cicd
- **Docker**: docker
- **Scaffolding**: scaffold
- **Planning**: plan_files
- **Search**: code_search
- **Workflows**: run_workflow
- **MCP**: mcp_call (bridge to external MCP servers)

#### D. MCP Integration
**Location**: `victor/mcp/`

**Think of it as**: Victor can both BE a tool provider AND USE external tools.

**Two Modes**:

1. **MCP Server** (victor/mcp/server.py): Exposes Victor's tools to external clients
   - Claude Desktop can connect to Victor as an MCP server
   - VS Code can use Victor's tools via MCP
   - Other MCP clients can discover and call Victor's 42 tools

2. **MCP Client** (victor/mcp/client.py): Connects to external MCP servers
   - Victor can connect to filesystem servers, database servers, etc.
   - External tools are bridged via `mcp_bridge_tool.py`
   - Tools are prefixed (e.g., `mcp_database_query`) to avoid naming conflicts

**Communication**: Both use stdio (standard input/output) following MCP spec.

---

## 3. Tool Calling System

### 3.1 The Current Design

**Question**: Does Victor broadcast all 42 tools to the LLM every time?

**Answer**: **No, but there's a fallback that does!**

Here's what actually happens (from orchestrator.py):

```python
# In stream_chat() and chat() methods:

if self.provider.supports_tools():
    if self.use_semantic_selection:
        # SEMANTIC SELECTION: Use embeddings to find relevant tools
        tools = await self._select_relevant_tools_semantic(user_message)
    else:
        # KEYWORD SELECTION: Use keyword matching
        tools = self._select_relevant_tools_keywords(user_message)

    # STAGE-BASED PRUNING: Further reduce based on conversation stage
    tools = self._prioritize_tools_stage(user_message, tools, stage="initial")
```

### 3.2 Intelligent Tool Selection Methods

#### Method 1: Semantic Selection (Embedding-Based)
**Location**: `orchestrator.py:268-335`

**How it works**:
1. Generate embedding for user message: "write a Python function to validate emails"
2. Compare to pre-computed embeddings of all 42 tools
3. Select top 8 most similar tools using cosine similarity
4. Threshold: 0.10 (tools with similarity < 0.10 are filtered)

**Example**:
```
User: "write a Python function to validate emails"

Semantic Similarities:
- write_file: 0.78 ✓
- execute_python_in_sandbox: 0.65 ✓
- read_file: 0.45 ✓
- edit_files: 0.43 ✓
- code_review: 0.32 ✓
- execute_bash: 0.28 ✓
- security_scan: 0.15 ✓
- generate_docs: 0.12 ✓
- git: 0.08 ✗ (below threshold)
- docker: 0.05 ✗

Selected: 8 tools
```

**Pros**:
- Handles synonyms ("test" → "verify", "validate", "check")
- No hardcoded keyword lists
- Context-aware (understands intent)

**Cons**:
- Requires embedding model (120MB, ~8ms per query)
- Can miss obvious tools if description isn't semantic-rich

#### Method 2: Keyword Selection (Rule-Based)
**Location**: `orchestrator.py:337-442`

**How it works**:
1. Always include "core tools": read_file, write_file, list_directory, execute_bash, edit_files
2. Match keywords to tool categories:
   - "git" → add git tools
   - "test" → add testing tools
   - "refactor" → add refactoring tools
   - etc.

**Example**:
```
User: "run the tests and commit changes"

Matches:
- "test" → testing tools
- "commit" → git tools

Selected tools:
- Core: read_file, write_file, list_directory, execute_bash, edit_files
- Testing: run_tests
- Git: git, git_suggest_commit, git_create_pr

Total: 8 tools
```

**Pros**:
- Fast (no embeddings needed)
- Predictable
- Works offline

**Cons**:
- Misses synonyms
- Requires maintaining keyword lists
- Brittle to phrasing changes

#### Method 3: Stage-Based Pruning
**Location**: `orchestrator.py:444-467`

**Purpose**: Further reduce tools based on conversation stage.

**Stages**:
1. **Initial**: User just sent first message
   - Keep: planning tools (plan_files, code_search, list_directory)
   - Keep: web tools (if query mentions web)
   - Keep: core tools (write_file, edit_files)

2. **Post-plan**: After planning, before reading files
   - Keep: reading tools (read_file, analyze_docs, code_review)
   - Keep: planning tools (still useful)

3. **Post-read**: After reading files
   - Keep: reading tools
   - Keep: core editing tools
   - Drop: planning tools (already planned)

**Example**:
```
Initial query: "fix the authentication bug"
Stage: initial
Tools before pruning: [read_file, write_file, plan_files, code_search, ...]
Tools after pruning: [plan_files, code_search, list_directory, write_file, edit_files]

After planning:
Stage: post_plan
Tools: [read_file, analyze_docs, code_review, plan_files, write_file, edit_files]

After reading:
Stage: post_read
Tools: [read_file, code_review, write_file, edit_files]
```

### 3.3 The Problematic Fallback

**Location**: `orchestrator.py:318-333`

```python
# Fallback: If 0 tools selected, provide ALL tools to the model
if not tools:
    logger.info(
        "Semantic selection returned 0 tools. "
        f"Falling back to ALL {len(self.tools.list_tools())} tools."
    )
    tools = [
        ToolDefinition(
            name=tool.name,
            description=tool.description,
            parameters=tool.parameters
        )
        for tool in self.tools.list_tools()
    ]
```

**The Problem**:
- If semantic selection returns 0 tools (low similarity), it sends ALL 42 tools
- This defeats the purpose of intelligent selection
- Wastes context window on small models
- Confuses smaller LLMs with too many choices

**When this happens**:
- User query is very vague: "help me"
- User query is domain-specific without good tool descriptions
- Embedding model doesn't understand the query well

---

## 4. MCP Integration

### 4.1 Current Status

**✅ What's Implemented**:
1. **MCP Server** (victor/mcp/server.py): Fully functional
   - Exposes Victor's 42 tools via MCP protocol
   - Supports tool calling
   - Supports resource access (file:// URIs)
   - Uses stdio transport

2. **MCP Client** (victor/mcp/client.py): Fully functional
   - Connects to external MCP servers via stdio
   - Discovers tools and resources
   - Calls external tools
   - Returns results to Victor

3. **MCP Bridge Tool** (victor/tools/mcp_bridge_tool.py): Partially functional
   - Registers external MCP tools with Victor
   - Prefixes tool names to avoid conflicts
   - Calls external tools on demand

**❌ What's NOT Exposed**:

The MCP server is implemented but **NOT automatically exposed to LLMs via Ollama or other providers**. Here's the issue:

```python
# In orchestrator.py:248-261
if getattr(settings, "use_mcp_tools", False):  # ← Requires manual config!
    if getattr(settings, "mcp_command", None):
        try:
            from victor.mcp.client import MCPClient
            mcp_client = MCPClient()
            cmd_parts = settings.mcp_command.split()
            asyncio.create_task(mcp_client.connect(cmd_parts))
            configure_mcp_client(mcp_client, prefix=getattr(settings, "mcp_prefix", "mcp"))
        except Exception as exc:
            logger.warning(f"Failed to start MCP client: {exc}")

    for mcp_tool in get_mcp_tool_definitions():
        self.tools.register_dict(mcp_tool)
```

**Problems**:
1. MCP tools only registered if `use_mcp_tools=True` in settings
2. Requires manual `mcp_command` configuration
3. No automatic discovery of MCP servers
4. Victor's MCP server is not exposed as a tool to the LLM
5. No bidirectional MCP bridge (Victor as server → LLM as client)

### 4.2 How MCP SHOULD Work with Ollama

**Vision**: Victor should act as an MCP server that Ollama (or any LLM) can use as a client.

**Current Reality**: This is NOT implemented.

**Why it matters**:
- Desktop AI clients can use MCP servers
- VS Code extensions can use MCP servers
- But when you use Victor with Ollama, the MCP server functionality is unused
- The LLM sees Victor's tools as native tool calls, not via MCP

**Desired Architecture**:
```
┌──────────────────┐
│  User Query      │
└────────┬─────────┘
         │
         ▼
┌────────────────────────────────────┐
│  Victor AgentOrchestrator          │
│  - Manages conversation            │
│  - Calls Ollama LLM                │
└────────┬───────────────────────────┘
         │
         ▼
┌────────────────────────────────────┐
│  Ollama LLM (qwen2.5-coder:7b)    │
│  - Generates response              │
│  - Requests tool calls             │
└────────┬───────────────────────────┘
         │
         ▼
┌────────────────────────────────────┐
│  Victor MCP Server                 │
│  - Receives tool call request      │
│  - Executes tool                   │
│  - Returns result                  │
└────────────────────────────────────┘
```

**Currently Missing**:
- MCP client integration in provider layer
- MCP server auto-start for Ollama
- Tool call translation: Ollama format → MCP format

---

## 5. Missing Capabilities

Based on deep analysis, here are critical missing features:

### 5.1 Contextual Tool Selection

**Current**: Tools selected based on initial user message only.

**Missing**:
- **Conversation history awareness**: "Now run the tests" (which tests? mentioned 5 messages ago)
- **Tool dependency chains**: If user calls `write_file`, likely needs `execute_bash` next
- **Success/failure adaptation**: If `read_file` fails, don't suggest `edit_files`

**Impact**: LLM gets confused when context from previous messages is needed.

### 5.2 Tool Call Validation & Sandboxing

**Current**: Tools execute with minimal validation.

**Missing**:
- **Pre-execution validation**: Check if file exists before `edit_files`
- **Dry-run mode**: Preview changes before applying
- **Rollback on failure**: If tool fails, undo changes
- **Sandboxed execution**: Limit file access to project directory only

**Impact**: Risky operations can damage codebase.

### 5.3 Multi-Step Tool Planning

**Current**: LLM calls tools one at a time.

**Missing**:
- **Tool orchestration**: Chain tools automatically (read → analyze → edit → test)
- **Parallel execution**: Run independent tools in parallel
- **Conditional execution**: If test fails, call debug tool

**Impact**: Inefficient tool usage, many back-and-forth LLM calls.

### 5.4 Tool Result Caching

**Current**: Every tool call executes from scratch.

**Missing**:
- **Result caching**: Cache `read_file` results within conversation
- **Incremental updates**: Only re-read changed files
- **Smart invalidation**: Clear cache when files modified

**Impact**: Wasted execution time, redundant file reads.

### 5.5 MCP Bidirectional Bridge

**Current**: MCP server exists but not exposed to LLMs.

**Missing**:
- **Auto-expose to Ollama**: Make Victor's tools available via MCP
- **Provider-aware MCP**: Different providers need different MCP formats
- **Tool discovery**: Auto-discover and register external MCP servers

**Impact**: MCP capabilities unused in most configurations.

### 5.6 Tool Usage Analytics

**Current**: No metrics on tool usage.

**Missing**:
- **Usage stats**: Which tools used most often?
- **Success rates**: Which tools fail frequently?
- **Performance metrics**: Tool execution time distribution
- **Context efficiency**: How many tools actually needed vs. sent?

**Impact**: No data-driven optimization possible.

### 5.7 Dynamic Tool Loading

**Current**: All 42 tools loaded at startup. Plugin system now implemented.

**Implemented**:
- **Plugin system**: `victor/tools/plugin.py` - ToolPlugin base class and FunctionToolPlugin
- **Plugin metadata**: PluginMetadata dataclass for plugin info

**Still Missing**:
- **Lazy loading**: Load tools on demand
- **Tool versioning**: Multiple versions of same tool
- **Hot reload**: Update tools without restarting

**Impact**: Plugin system enables extensibility; lazy loading would reduce memory.

### 5.8 Cost-Aware Tool Selection

**Current**: No consideration of API costs.

**Missing**:
- **Cost estimation**: Estimate tokens for each tool's parameters
- **Budget-aware selection**: Prefer cheaper tools when possible
- **Cost tracking**: Track costs per conversation

**Impact**: Unexpected API bills for cloud providers.

---

## 6. Design Issues

### 6.1 Tool Broadcasting Fallback

**Issue**: When semantic selection returns 0 tools, ALL 42 tools are sent.

**Why it's bad**:
- Defeats the purpose of intelligent selection
- Overwhelms small models (< 7B parameters)
- Wastes context window
- Increases API costs (for cloud providers)

**Recommendation**:
```python
# Instead of broadcasting all tools, use a smart fallback
if not tools:
    # Option 1: Return core tools only
    core_tool_names = ["read_file", "write_file", "execute_bash", "list_directory"]
    tools = [t for t in all_tools if t.name in core_tool_names]

    # Option 2: Ask user to clarify
    yield StreamChunk(content="I'm not sure which tools you need. Could you be more specific?")

    # Option 3: Use keyword fallback
    tools = self._select_relevant_tools_keywords(user_message)
```

### 6.2 Semantic Selection Threshold

**Issue**: Threshold of 0.10 is very low.

**Why it's bad**:
- Includes marginally relevant tools
- Noise in tool selection
- Can still overwhelm smaller models

**Current**:
```python
similarity_threshold=0.10  # Very permissive
```

**Recommendation**:
```python
# Dynamic threshold based on model size
if model_size == "small":  # <7B parameters
    threshold = 0.30  # Strict
    max_tools = 5
elif model_size == "medium":  # 7B-30B
    threshold = 0.20  # Moderate
    max_tools = 8
else:  # >30B parameters
    threshold = 0.10  # Permissive
    max_tools = 12
```

### 6.3 Stage-Based Pruning Logic

**Issue**: Stage detection is fragile.

**Current**:
```python
if stage == "initial":
    keep = planning_tools | (web_tools if needs_web else set()) | core
elif stage == "post_plan":
    keep = reading_tools | planning_tools | core | (web_tools if needs_web else set())
```

**Why it's bad**:
- Stage transitions not clearly defined
- No way to detect when planning is "done"
- Manual stage management required

**Recommendation**:
```python
# Use conversation state machine
class ConversationStage(Enum):
    PLANNING = "planning"
    READING = "reading"
    EDITING = "editing"
    TESTING = "testing"
    COMMITTING = "committing"

# Auto-detect stage from executed tools
def detect_stage(executed_tools: List[str]) -> ConversationStage:
    if any(t in ["plan_files", "code_search"] for t in executed_tools[-3:]):
        return ConversationStage.PLANNING
    elif any(t in ["read_file", "analyze_docs"] for t in executed_tools[-3:]):
        return ConversationStage.READING
    # ... etc
```

### 6.4 MCP Integration Complexity

**Issue**: MCP client requires manual configuration.

**Current**:
```python
# Settings.yaml
use_mcp_tools: true
mcp_command: "python external_mcp_server.py"
mcp_prefix: "mcp"
```

**Why it's bad**:
- No auto-discovery
- Requires knowing exact command
- No validation of MCP server health
- No reconnection on failure

**Recommendation**:
```python
# MCP Server Registry
class MCPRegistry:
    def discover_servers(self) -> List[MCPServerConfig]:
        """Auto-discover MCP servers in common locations."""
        # Check ~/.config/mcp/servers/
        # Check environment variables
        # Check known server paths

    async def health_check(self, server: MCPServerConfig) -> bool:
        """Verify server is responsive."""

    async def auto_connect(self):
        """Connect to all discovered servers automatically."""
```

### 6.5 Tool Call Budget Enforcement

**Issue**: Tool budget is per-conversation, not per-query.

**Current**:
```python
self.tool_budget = 6  # Total for entire conversation
self.tool_calls_used = 0  # Incremented each tool call
```

**Why it's bad**:
- Once budget exhausted, no more tools can be called
- No way to reset within conversation
- Doesn't account for tool importance

**Recommendation**:
```python
# Hierarchical budget
class ToolBudget:
    def __init__(self):
        self.total_budget = 50  # Per conversation
        self.per_query_budget = 10  # Per user query
        self.per_tool_priority = {
            "critical": 15,  # Can always call critical tools
            "normal": 10,
            "optional": 5,
        }

    def can_execute(self, tool_name: str, priority: str) -> bool:
        """Check if tool can be executed within budget."""
        # Implement complex budget logic
```

### 6.6 Embedding Model Loading

**Issue**: Embedding model loaded on first tool selection, blocking the request.

**Current**:
```python
if not self._embeddings_initialized:
    logger.info("Initializing tool embeddings (one-time operation)...")
    await self.semantic_selector.initialize_tool_embeddings(self.tools)
    self._embeddings_initialized = True
```

**Why it's bad**:
- First query has high latency (~5-10 seconds)
- Blocks event loop during loading
- No progress indication to user

**Recommendation**:
```python
# Preload during orchestrator initialization
async def __init__(self, ...):
    # ... existing init

    if self.use_semantic_selection:
        # Start background task to load embeddings
        asyncio.create_task(self._preload_embeddings())

async def _preload_embeddings(self):
    """Preload embeddings in background."""
    try:
        await self.semantic_selector.initialize_tool_embeddings(self.tools)
        self._embeddings_initialized = True
        logger.info("✓ Tool embeddings preloaded")
    except Exception as e:
        logger.warning(f"Failed to preload embeddings: {e}")
```

---

## 7. Optimization Recommendations

### 7.1 Immediate Wins (1-2 days)

#### 1. Fix Fallback Broadcast
**Location**: `orchestrator.py:318-333`

**Change**:
```python
# Before: Broadcast all tools
if not tools:
    tools = [all tools]  # BAD

# After: Smart fallback
if not tools:
    # Use core tools + keyword fallback
    core_tools = self._get_core_tools()
    keyword_tools = self._select_relevant_tools_keywords(user_message)
    tools = core_tools + keyword_tools
    logger.warning(f"Semantic selection failed, using fallback ({len(tools)} tools)")
```

**Impact**: Prevents overwhelming small models, reduces context waste.

#### 2. Preload Embeddings
**Location**: `orchestrator.py:__init__`

**Change**:
```python
# Start background embedding loading
if self.use_semantic_selection:
    asyncio.create_task(self._preload_embeddings())
```

**Impact**: Eliminates first-query latency spike.

#### 3. Add Usage Logging
**Location**: `orchestrator.py:_handle_tool_calls`

**Change**:
```python
# Log tool usage for analytics
logger.info(f"Tool usage: {tool_name} (args: {list(tool_args.keys())})")
self._usage_stats[tool_name] = self._usage_stats.get(tool_name, 0) + 1
```

**Impact**: Enables data-driven optimization.

### 7.2 Medium-Term Improvements (1 week)

#### 1. Implement Tool Result Caching

**Approach**:
```python
class ToolResultCache:
    def __init__(self, ttl_seconds=300):
        self._cache = {}  # {(tool_name, args_hash): (result, timestamp)}
        self._ttl = ttl_seconds

    def get(self, tool_name: str, args: Dict) -> Optional[ToolResult]:
        """Get cached result if valid."""
        key = (tool_name, self._hash_args(args))
        if key in self._cache:
            result, timestamp = self._cache[key]
            if time.time() - timestamp < self._ttl:
                return result
        return None

    def set(self, tool_name: str, args: Dict, result: ToolResult):
        """Cache tool result."""
        key = (tool_name, self._hash_args(args))
        self._cache[key] = (result, time.time())
```

**Integration**:
```python
# In orchestrator
self.tool_cache = ToolResultCache()

async def _execute_tool_call(self, tool_call):
    # Check cache first
    cached = self.tool_cache.get(tool_name, tool_args)
    if cached:
        logger.info(f"✓ Using cached result for {tool_name}")
        return cached

    # Execute and cache
    result = await self.tools.execute(tool_name, context, **tool_args)
    self.tool_cache.set(tool_name, tool_args, result)
    return result
```

**Impact**: 40-60% reduction in redundant tool calls.

#### 2. Dynamic Tool Selection Threshold

**Approach**:
```python
def _get_adaptive_threshold(self, user_message: str, model: str) -> float:
    """Calculate adaptive similarity threshold."""

    # Factor 1: Model size
    if "0.5b" in model or "1b" in model:
        base_threshold = 0.35  # Very strict for tiny models
    elif "7b" in model:
        base_threshold = 0.25  # Strict for small models
    elif "30b" in model or "70b" in model:
        base_threshold = 0.15  # Moderate for large models
    else:
        base_threshold = 0.20  # Default

    # Factor 2: Query specificity
    word_count = len(user_message.split())
    if word_count < 5:
        # Vague query → stricter threshold
        base_threshold += 0.10
    elif word_count > 20:
        # Detailed query → looser threshold
        base_threshold -= 0.05

    # Factor 3: Conversation depth
    if len(self.messages) > 10:
        # Deep conversation → looser (more context)
        base_threshold -= 0.05

    return max(0.10, min(0.40, base_threshold))
```

**Impact**: Better tool selection accuracy across different models and scenarios.

#### 3. Tool Dependency Graph

**Approach**:
```python
class ToolDependencyGraph:
    """Models relationships between tools."""

    def __init__(self):
        self.dependencies = {
            "edit_files": ["read_file"],  # Edit requires read first
            "git_suggest_commit": ["git"],  # Commit suggestion needs git
            "code_review": ["read_file", "analyze_metrics"],
            "run_tests": ["execute_bash"],
        }

        self.common_sequences = [
            ["plan_files", "read_file", "edit_files", "run_tests"],
            ["code_search", "read_file", "code_review"],
            ["read_file", "security_scan", "edit_files"],
        ]

    def suggest_next_tools(self, executed_tools: List[str]) -> List[str]:
        """Suggest tools likely needed next."""
        last_tool = executed_tools[-1] if executed_tools else None

        # Check if last tool has common follow-ups
        for sequence in self.common_sequences:
            if last_tool in sequence:
                idx = sequence.index(last_tool)
                if idx < len(sequence) - 1:
                    return [sequence[idx + 1]]

        return []
```

**Integration**:
```python
# Boost suggested tools in semantic selection
suggested = self.dependency_graph.suggest_next_tools(self.executed_tools)
for tool in tools:
    if tool.name in suggested:
        # Boost similarity score
        tool.similarity_score += 0.15
```

**Impact**: More intelligent tool suggestions based on workflow patterns.

### 7.3 Long-Term Enhancements (2-4 weeks)

#### 1. Full MCP Bidirectional Bridge

**Architecture**:
```
┌─────────────────────────────────────────────┐
│  Victor AgentOrchestrator                   │
│                                              │
│  ┌─────────────┐         ┌─────────────┐   │
│  │ MCP Server  │◄───────►│ Provider    │   │
│  │ (Expose     │         │ (Ollama,    │   │
│  │  Victor     │         │  Claude,    │   │
│  │  tools)     │         │  GPT)       │   │
│  └─────────────┘         └─────────────┘   │
│         │                        │          │
│         │                        ▼          │
│         │                 ┌─────────────┐   │
│         │                 │ MCP Client  │   │
│         │                 │ (Use ext.   │   │
│         │                 │  tools)     │   │
│         │                 └─────────────┘   │
│         │                        │          │
└─────────┼────────────────────────┼──────────┘
          │                        │
          ▼                        ▼
    Claude Desktop          External MCP
    VS Code                 Servers
```

**Implementation**:
```python
class MCPProviderBridge:
    """Bridge between provider and MCP."""

    async def expose_as_mcp_server(self, provider: BaseProvider):
        """Expose provider's tools via MCP."""
        mcp_server = MCPServer(tool_registry=self.tools)

        # Start stdio server
        await mcp_server.start_stdio_server()

    async def use_external_mcp(self, server_command: List[str]):
        """Connect to external MCP server."""
        mcp_client = MCPClient()
        await mcp_client.connect(server_command)

        # Register external tools
        for tool in mcp_client.tools:
            self.tools.register_mcp_tool(tool)
```

#### 2. Plugin System for Tools

**Architecture**:
```python
class ToolPlugin:
    """Base class for tool plugins."""

    def __init__(self, config: Dict[str, Any]):
        self.config = config

    @abstractmethod
    def get_tools(self) -> List[BaseTool]:
        """Return tools provided by this plugin."""
        pass

    @abstractmethod
    def initialize(self):
        """Initialize plugin (load models, connect to services, etc.)."""
        pass

# Example plugin
class DatabaseToolPlugin(ToolPlugin):
    def get_tools(self):
        return [
            SQLQueryTool(),
            SchemaInspectorTool(),
            MigrationTool(),
        ]

    def initialize(self):
        # Connect to databases from config
        self.connections = setup_db_connections(self.config)

# Plugin loader
class ToolPluginManager:
    def load_plugin(self, plugin_path: str):
        """Load plugin from path."""
        spec = importlib.util.spec_from_file_location("plugin", plugin_path)
        module = importlib.util.module_from_spec(spec)
        spec.loader.exec_module(module)

        # Instantiate and register
        plugin = module.Plugin(config=self.config)
        plugin.initialize()

        for tool in plugin.get_tools():
            self.tool_registry.register(tool)
```

#### 3. Conversational State Machine

**Approach**:
```python
class ConversationState:
    """Manages conversation state and tool selection strategy."""

    class Stage(Enum):
        INITIAL = "initial"
        PLANNING = "planning"
        READING = "reading"
        ANALYZING = "analyzing"
        EDITING = "editing"
        TESTING = "testing"
        FINALIZING = "finalizing"

    def __init__(self):
        self.current_stage = self.Stage.INITIAL
        self.executed_tools = []
        self.files_accessed = set()
        self.modifications_made = []

    def update_stage(self, tool_name: str):
        """Auto-update stage based on tool executed."""
        if tool_name in ["plan_files", "code_search"]:
            self.current_stage = self.Stage.PLANNING
        elif tool_name in ["read_file", "list_directory"]:
            self.current_stage = self.Stage.READING
        elif tool_name in ["code_review", "analyze_metrics", "security_scan"]:
            self.current_stage = self.Stage.ANALYZING
        elif tool_name in ["edit_files", "write_file", "refactor_*"]:
            self.current_stage = self.Stage.EDITING
        elif tool_name in ["run_tests", "execute_python_in_sandbox"]:
            self.current_stage = self.Stage.TESTING
        elif tool_name in ["git", "git_suggest_commit"]:
            self.current_stage = self.Stage.FINALIZING

    def get_relevant_tools_for_stage(self) -> Set[str]:
        """Get tools relevant to current stage."""
        stage_tools = {
            self.Stage.INITIAL: {"plan_files", "code_search", "list_directory"},
            self.Stage.PLANNING: {"plan_files", "code_search", "read_file"},
            self.Stage.READING: {"read_file", "list_directory", "find_symbol"},
            self.Stage.ANALYZING: {"code_review", "analyze_metrics", "security_scan", "read_file"},
            self.Stage.EDITING: {"edit_files", "write_file", "refactor_*", "read_file"},
            self.Stage.TESTING: {"run_tests", "execute_bash", "execute_python_in_sandbox"},
            self.Stage.FINALIZING: {"git", "git_suggest_commit", "git_create_pr"},
        }
        return stage_tools.get(self.current_stage, set())
```

**Integration**:
```python
# In orchestrator
self.conversation_state = ConversationState()

async def _select_relevant_tools_semantic(self, user_message: str):
    # ... existing semantic selection

    # Filter by conversation stage
    stage_tools = self.conversation_state.get_relevant_tools_for_stage()
    tools = [t for t in tools if t.name in stage_tools or t.is_core_tool]

    return tools
```

**Impact**: More intelligent, context-aware tool selection.

---

## 8. Implementation Roadmap

### Phase 1: Quick Wins (Week 1)

**Objectives**: Fix critical issues with minimal code changes.

**Tasks**:
1. ✅ Fix broadcast fallback (orchestrator.py:318-333)
2. ✅ Preload embeddings in background (orchestrator.py:__init__)
3. ✅ Add tool usage logging (orchestrator.py:_handle_tool_calls)
4. ✅ Implement dynamic similarity threshold (semantic_selector.py:215)
5. ✅ Document current architecture (this file!)

**Testing**:
- Run with small model (qwen2.5-coder:1.5b)
- Verify no more broadcast fallback
- Check first-query latency improved
- Verify usage logs generated

**Expected Impact**:
- 50% reduction in wasted context on small models
- 80% reduction in first-query latency
- Data for future optimizations

### Phase 2: Medium-Term Improvements (Week 2-3)

**Objectives**: Add intelligent caching and dependency tracking.

**Tasks**:
1. ✅ Implement ToolResultCache (new file: victor/cache/tool_cache.py)
2. ✅ Add ToolDependencyGraph (new file: victor/tools/dependency_graph.py)
3. ✅ Integrate cache into orchestrator (orchestrator.py:_execute_tool_call)
4. ✅ Add dependency-based tool suggestions (orchestrator.py:_select_relevant_tools_semantic)
5. ✅ Implement ConversationState state machine (new file: victor/agent/conversation_state.py)

**Testing**:
- Multi-turn conversations with file reading
- Verify caching works (check logs)
- Test tool suggestions (read_file → edit_files)
- Verify state transitions (planning → reading → editing)

**Expected Impact**:
- 40-60% reduction in redundant tool calls
- 30% better tool selection accuracy
- More natural conversation flow

### Phase 3: MCP Enhancements (Week 4)

**Objectives**: Full bidirectional MCP integration.

**Tasks**:
1. ✅ Implement MCPProviderBridge (new file: victor/mcp/bridge.py)
2. ✅ Auto-expose Victor as MCP server to Ollama (mcp/bridge.py)
3. ✅ Implement MCPRegistry for auto-discovery (mcp/registry.py)
4. ✅ Add health checks and reconnection (mcp/client.py)
5. ✅ Document MCP integration (docs/MCP_GUIDE.md)

**Testing**:
- Start Victor with Ollama
- Verify MCP server auto-starts
- Connect Claude Desktop to Victor
- Test external MCP server integration
- Verify tool prefixing works

**Expected Impact**:
- Full MCP compatibility
- Easy integration with Claude Desktop, VS Code
- Extensibility via external MCP servers

### Phase 4: Plugin System (Week 5-6)

**Objectives**: Enable extensibility via plugins.

**Tasks**:
1. ✅ Design plugin API (victor/tools/plugin.py)
2. ✅ Implement ToolPluginManager (victor/tools/plugin_manager.py)
3. ✅ Create example plugin (plugins/example_database_plugin/)
4. ✅ Add plugin loading to orchestrator (orchestrator.py:__init__)
5. ✅ Document plugin development (docs/PLUGIN_GUIDE.md)

**Testing**:
- Create test plugin
- Load at runtime
- Verify tools registered
- Test plugin hot reload

**Expected Impact**:
- Community-contributed tools
- Domain-specific tool collections
- Easier experimentation

---

## Conclusion

Victor is a well-architected system with strong foundations. The key areas for improvement are:

1. **Smarter tool selection**: Eliminate broadcast fallback, use adaptive thresholds
2. **Better MCP integration**: Expose to LLMs, auto-discovery, bidirectional
3. **Caching & optimization**: Reduce redundant calls, faster responses
4. **Extensibility**: Plugin system for community tools

The recommended roadmap is designed for incremental improvement with immediate value at each phase.

---

## For Newbies: Summary

**Think of Victor as**:
- A **universal translator** between different AI providers
- A **toolbox** of 46 enterprise-grade coding tools
- A **smart assistant** that chooses the right tools for each task

**Key Design Principles**:
1. **Abstraction**: Hide provider differences behind unified interfaces
2. **Intelligence**: Select tools semantically, not just by keywords
3. **Safety**: Sandbox execution, budget limits, validation
4. **Extensibility**: MCP bridge, plugin system

**The Main Flow**:
```
User Query
    ↓
AgentOrchestrator
    ↓
Select Relevant Tools (semantic/keyword)
    ↓
Send to LLM (Claude, Ollama, GPT)
    ↓
LLM Responds with Tool Calls
    ↓
Execute Tools via ToolRegistry
    ↓
Return Results to LLM
    ↓
LLM Generates Final Response
    ↓
Display to User
```

**What Makes Victor Special**:
- Works with BOTH expensive cloud models AND free local models
- Air-gapped mode for compliance (100% offline)
- Intelligent tool selection (not broadcasting all 42 tools)
- Enterprise-grade tools (security scanning, CI/CD, testing, etc.)

**Current Limitations** (as of 2025-12-06):
- Tool selection fallback improved but still needs refinement
- MCP registry implemented (victor/mcp/registry.py) but auto-discovery pending
- Tool result caching implemented (victor/cache/tool_cache.py)
- Dependency graph implemented (victor/tools/dependency_graph.py)
- Conversation state machine implemented (victor/agent/conversation_state.py)
- Orchestrator decomposition complete (ConversationController, ToolPipeline, StreamingController, MetricsCollector, ToolRegistrar, ProviderManager, ToolSequenceTracker extracted)
- UnifiedTaskClassifier with negation detection implemented (victor/agent/unified_classifier.py)
- ToolSequenceTracker integrated into SemanticToolSelector for 15-20% tool selection boost
- Classification caching with LRU + TTL implemented
- Provider health check system implemented (victor/providers/health.py)
- Resilience metrics export implemented (victor/providers/metrics_export.py)
- Classification-aware tool selection implemented in SemanticToolSelector
- UsageAnalytics implemented (victor/agent/usage_analytics.py) with Prometheus/JSON export and optimization recommendations
- ContextCompactor implemented (victor/agent/context_compactor.py) with proactive compaction and smart truncation

**Future Vision**:
- Perfect tool selection (no wasted context)
- Full MCP bidirectional bridge
- Plugin ecosystem for community tools
- Intelligent caching and dependency tracking

---

**Last Updated**: 2025-12-06
**Next Review**: After usage analytics implementation
