# Architecture Overview

This document provides a comprehensive overview of OCLAWMA's architecture and design principles.

## Table of Contents

- [High-Level Architecture](#high-level-architecture)
- [Core Components](#core-components)
- [Data Flow](#data-flow)
- [Provider System](#provider-system)
- [Skill System](#skill-system)
- [Context Management](#context-management)
- [Session Management](#session-management)
- [Safety System](#safety-system)

## High-Level Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                         CLI Layer                                │
│                    (Click-based commands)                        │
└─────────────────────────────────────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
        ▼                     ▼                     ▼
┌──────────────┐    ┌─────────────────┐    ┌──────────────┐
│   Session    │◄──►│    Providers    │    │    Skills    │
│   Runner     │    │  (LLM backends) │    │   Registry   │
└──────┬───────┘    └─────────────────┘    └──────┬───────┘
       │                                           │
       ▼                                           ▼
┌──────────────┐                         ┌─────────────────┐
│   Context    │                         │  Lazy Loading   │
│   Budget     │                         │   System        │
└──────────────┘                         └─────────────────┘
       │
       ▼
┌──────────────┐
│  Summarizer  │
│  (Rolling)   │
└──────────────┘
```

## Core Components

### 1. CLI Layer (`oclawma.cli`)

The entry point for all user interactions:

```python
# src/oclawma/cli.py
import click

@click.group()
def main():
    """OCLAWMA CLI."""
    pass

@main.command()
@click.option("--model", default="qwen2.5:3b")
@click.option("--provider", default="ollama")
def run(model, provider):
    """Start interactive session."""
    # Start session...
```

**Responsibilities:**
- Parse command-line arguments
- Route to appropriate handlers
- Display help and version info

### 2. Session Runner (`oclawma.session.runner`)

Manages the interactive REPL session:

```
┌─────────────────────────────────────┐
│         InteractiveSession          │
├─────────────────────────────────────┤
│ - provider: BaseProvider            │
│ - history: ConversationHistory      │
│ - budget: ContextBudget             │
│ - tool_registry: ToolRegistry       │
│ - summarizer: RollingSummarizer     │
├─────────────────────────────────────┤
│ + run()                             │
│ + process_message()                 │
│ + execute_tool_call()               │
└─────────────────────────────────────┘
```

**Responsibilities:**
- Manage conversation loop
- Handle user input and commands
- Coordinate tool execution
- Track session statistics

### 3. Provider System (`oclawma.providers`)

Abstracts different LLM backends:

```
┌─────────────────┐
│  BaseProvider   │ (Abstract)
├─────────────────┤
│ + complete()    │
│ + stream()      │
│ + list_models() │
└────────┬────────┘
         │
    ┌────┴────┬──────────┐
    ▼         ▼          ▼
┌────────┐ ┌────────┐ ┌──────────┐
│ Ollama │ │  Kimi  │ │ Fallback │
└────────┘ └────────┘ └──────────┘
```

**Responsibilities:**
- Generate completions
- Stream responses
- Count tokens
- Handle provider-specific errors

### 4. Skill System (`oclawma.skills`)

Manages lazy-loaded skills:

```
┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   SkillRegistry │────►│  SkillManifest   │────►│   LazySkill     │
│                 │     │   (YAML file)    │     │                 │
│  - Discovers    │     │                  │     │  - Loads on     │
│    skills       │     │  - Metadata      │     │    demand       │
│  - Loads on     │     │  - Tool list     │     │  - Executes     │
│    demand       │     │  - Entry point   │     │    tools        │
└─────────────────┘     └──────────────────┘     └─────────────────┘
         │
         ▼
┌─────────────────┐
│  TokenCache     │
│                 │
│  - Caches       │
│    token counts │
│  - Persists to  │
│    disk         │
└─────────────────┘
```

**Responsibilities:**
- Discover skills from filesystem/entry points
- Lazy-load skill implementations
- Cache token counts
- Route tool calls to skills

### 5. Context Budget (`oclawma.context.budget`)

Tracks and manages token usage:

```python
class ContextBudget:
    """Token budget management."""
    
    def __init__(self, limit: int = 8192):
        self.limit = limit
        self.used = 0
        self.warning_threshold = 0.75
        self.critical_threshold = 0.87
    
    @property
    def usage_percent(self) -> float:
        return self.used / self.limit
    
    def can_allocate(self, tokens: int) -> bool:
        return (self.used + tokens) <= self.limit
    
    def allocate(self, tokens: int, strict: bool = False):
        if strict and not self.can_allocate(tokens):
            raise BudgetExceededError()
        self.used += tokens
```

**Responsibilities:**
- Track token usage
- Enforce limits
- Trigger warnings
- Enable compression decisions

### 6. Tool System (`oclawma.tools`)

Built-in tool implementations:

```
┌─────────────────┐
│   BaseTool      │
├─────────────────┤
│ + name          │
│ + description   │
│ + execute()     │
└────────┬────────┘
         │
    ┌────┼────┐
    ▼    ▼    ▼
┌─────┐┌─────┐┌─────┐
│read ││write││exec │
└─────┘└─────┘└─────┘
```

**Responsibilities:**
- Read files
- Write files
- Execute commands
- Return structured results

## Data Flow

### Session Flow

```
User Input
    │
    ▼
┌─────────────────┐
│ Parse Command   │
└────────┬────────┘
         │
    ┌────┴────┐
    ▼         ▼
┌────────┐ ┌──────────┐
│Command │ │ Message  │
│Handler │ │ Processing│
└────────┘ └────┬─────┘
                │
                ▼
        ┌─────────────────┐
        │ Add to History  │
        └────────┬────────┘
                 │
                 ▼
        ┌─────────────────┐
        │ Check Budget    │
        └────────┬────────┘
                 │
            ┌────┴────┐
            ▼         ▼
        ┌────────┐ ┌──────────┐
        │  OK    │ │ Compress │
        └────┬───┘ └────┬─────┘
             │          │
             └────┬─────┘
                  ▼
        ┌─────────────────┐
        │ LLM Completion  │
        └────────┬────────┘
                 │
                 ▼
        ┌─────────────────┐
        │ Extract Tools   │
        └────────┬────────┘
                 │
                 ▼
        ┌─────────────────┐
        │ Execute Tools   │
        └────────┬────────┘
                 │
                 ▼
        ┌─────────────────┐
        │ Return Results  │
        └────────┬────────┘
                 │
                 ▼
        ┌─────────────────┐
        │ Show Response   │
        └─────────────────┘
```

### Tool Execution Flow

```
LLM Response
    │
    ▼
┌─────────────────┐
│ Parse TOOL_CALL │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ Validate Tool   │
└────────┬────────┘
         │
    ┌────┴────┐
    ▼         ▼
┌────────┐ ┌──────────┐
│ Builtin│ │ Skill    │
│  Tool  │ │  Tool    │
└────┬───┘ └────┬─────┘
     │          │
     └────┬─────┘
          ▼
┌─────────────────┐
│ Execute         │
└────────┬────────┘
         │
    ┌────┴────┐
    ▼         ▼
┌────────┐ ┌──────────┐
│Success │ │  Error   │
└────┬───┘ └────┬─────┘
     │          │
     └────┬─────┘
          ▼
┌─────────────────┐
│ Return Result   │
│ (to LLM)        │
└─────────────────┘
```

## Provider System

### Provider Interface

```python
class BaseProvider(ABC):
    """Abstract base for LLM providers."""
    
    @abstractmethod
    async def complete(self, request: CompletionRequest) -> CompletionResponse:
        """Generate a chat completion."""
        ...
    
    @abstractmethod
    async def stream_complete(self, request: CompletionRequest) -> AsyncIterator[CompletionResponse]:
        """Generate a streaming completion."""
        ...
    
    @abstractmethod
    async def list_models(self) -> list[str]:
        """List available models."""
        ...
    
    @abstractmethod
    def count_tokens(self, text: str, model: str | None = None) -> int:
        """Estimate token count."""
        ...
```

### Provider Comparison

| Feature | Ollama | Kimi | Auto |
|---------|--------|------|------|
| Local | ✅ | ❌ | ✅ |
| Cloud | ❌ | ✅ | ✅ |
| No API Key | ✅ | ❌ | Partial |
| Fallback | ❌ | ❌ | ✅ |
| Best For | Privacy, speed | Power, availability | Reliability |

### Fallback Provider

The fallback provider automatically switches from local to cloud:

```python
class FallbackProvider(BaseProvider):
    """Provider with automatic fallback."""
    
    def __init__(self, primary: BaseProvider, fallback: BaseProvider):
        self.primary = primary
        self.fallback = fallback
        self.fallback_threshold = 0.9
    
    async def complete(self, request: CompletionRequest) -> CompletionResponse:
        try:
            return await self.primary.complete(request)
        except ContextOverflowError:
            # Switch to fallback for large contexts
            return await self.fallback.complete(request)
```

## Skill System

### Manifest Format

```yaml
# skill.yaml
name: my_skill
version: 1.0.0
description: My skill
entry_point: my_skill:MySkillClass
tools:
  - name: tool1
    description: Does something
    parameters:
      - name: param1
        type: string
        required: true
    returns: string
    token_count: 100
```

### Lazy Loading

Skills are only loaded when their tools are invoked:

```python
class LazySkill(Skill):
    """Base class for lazy-loaded skills."""
    
    def __init__(self, metadata: SkillMetadata):
        self.metadata = metadata
        self._loaded = False
        self._tools = {}
    
    @property
    def tools(self) -> dict[str, BaseTool]:
        if not self._loaded:
            self._load()
        return self._tools
    
    @abstractmethod
    def _load(self) -> None:
        """Override to initialize tools."""
        ...
```

### Discovery

Skills are discovered from multiple sources:

```python
class SkillRegistry:
    """Registry for managing skills."""
    
    def discover_skills(self, paths: list[str]):
        """Discover skills from filesystem."""
        for path in paths:
            for manifest in Path(path).glob("*/skill.yaml"):
                self._register_from_manifest(manifest)
    
    def discover_entry_points(self):
        """Discover pip-installed skills."""
        eps = entry_points()
        if 'oclawma.skills' in eps:
            for ep in eps['oclawma.skills']:
                self._register_from_entry_point(ep)
```

## Context Management

### Context Budget

```
┌─────────────────────────────────────────┐
│           Context Budget                │
│                                         │
│  Limit: 8192 tokens                     │
│                                         │
│  [████████░░░░░░░░░░░░] 40% (3277)      │
│                                         │
│  Warnings:                              │
│  - Yellow at 75% (6144)                 │
│  - Red at 87% (7127)                    │
└─────────────────────────────────────────┘
```

### Rolling Summarization

When context approaches the limit:

```
Before Summarization:
┌─────────────────────────────────────────────────┐
│ System Prompt (500 tokens)                      │
│ User: Hello (10 tokens)                         │
│ Assistant: Hi! (5 tokens)                       │
│ User: Tell me about... (20 tokens)              │
│ Assistant: [Long response] (2000 tokens)        │
│ User: What about... (15 tokens)                 │
│ Assistant: [Another long response] (2500 tokens)│
│ User: Can you explain... (25 tokens)            │
│ Assistant: [Response] (2000 tokens)             │
│                                                 │
│ Total: ~7075 tokens (86% - approaching limit!)  │
└─────────────────────────────────────────────────┘

After Summarization:
┌─────────────────────────────────────────────────┐
│ System Prompt (500 tokens)                      │
│ Summary: Previous conversation covered...       │
│   (200 tokens)                                  │
│ User: Can you explain... (25 tokens)            │
│ Assistant: [Response] (2000 tokens)             │
│                                                 │
│ Total: ~2725 tokens (33% - much better!)        │
└─────────────────────────────────────────────────┘
```

### Summarizer Algorithm

```python
class RollingHistorySummarizer:
    """Summarizes conversation history to save tokens."""
    
    def should_summarize(self, history: ConversationHistory) -> bool:
        """Check if summarization is needed."""
        tokens = history.estimate_tokens()
        return tokens > self.config.trigger_threshold
    
    async def summarize(self, history: ConversationHistory) -> SummaryResult:
        """Summarize older messages."""
        # Identify messages to summarize
        to_summarize = self._select_messages(history)
        
        # Generate summary with LLM
        summary = await self._generate_summary(to_summarize)
        
        # Extract key facts
        facts = await self._extract_facts(to_summarize)
        
        # Replace messages with summary
        history.replace_with_summary(to_summarize, summary, facts)
        
        return SummaryResult(
            summary=summary,
            facts=facts,
            tokens_saved=len(to_summarize) * 200,
        )
```

## Session Management

### Session State

```python
@dataclass
class SessionState:
    """Current session state."""
    status: SessionStatus  # IDLE, RUNNING, PAUSED, ERROR
    provider: str
    model: str
    budget_used: int
    budget_limit: int
    message_count: int
    tool_call_count: int
    start_time: datetime
    last_activity: datetime
```

### Conversation History

```python
class ConversationHistory:
    """Manages conversation messages."""
    
    def __init__(self):
        self.messages: list[SessionMessage] = []
        self.system_message: str = ""
    
    def add_message(self, role: str, content: str, metadata: dict = None):
        self.messages.append(SessionMessage(
            role=role,
            content=content,
            timestamp=datetime.now(),
            metadata=metadata
        ))
    
    def estimate_tokens(self) -> int:
        """Estimate total tokens in history."""
        total = 0
        for msg in self.messages:
            # Rough estimate: 4 chars per token
            total += len(msg.content) // 4
        return total
```

## Safety System

### Safety Levels

```python
class SafetyLevel(Enum):
    STRICT = "strict"       # No exec, read-only
    NORMAL = "normal"       # Safe exec, confirm dangerous
    PERMISSIVE = "permissive"  # Most commands allowed

class SafetyChecker:
    """Checks if operations are safe."""
    
    BLOCKED_COMMANDS = {
        "strict": ["rm", "sudo", "chmod", "chown"],
        "normal": ["sudo", "rm -rf /"],
        "permissive": [],
    }
    
    def check_command(self, command: str, level: SafetyLevel) -> bool:
        """Check if a command is allowed."""
        blocked = self.BLOCKED_COMMANDS[level.value]
        for pattern in blocked:
            if pattern in command:
                return False
        return True
```

### Tool Safety

```python
class ExecTool(BaseTool):
    """Execute shell commands with safety checks."""
    
    async def execute(self, command: str, safety_level: SafetyLevel) -> ToolResult:
        # Check safety
        if not self.safety_checker.check_command(command, safety_level):
            return ToolResult(
                success=False,
                error="Command blocked by safety policy"
            )
        
        # Confirm dangerous operations
        if self._is_dangerous(command) and safety_level != SafetyLevel.PERMISSIVE:
            if not self._confirm(command):
                return ToolResult(success=False, error="User cancelled")
        
        # Execute
        return await self._run_command(command)
```

## Design Principles

### 1. Lazy Loading

- Skills load only when needed
- Minimizes startup time
- Reduces memory usage

### 2. Plugin Architecture

- Skills are self-contained
- Easy to add/remove
- Standardized interfaces

### 3. Context Awareness

- Track token usage
- Proactive compression
- Budget enforcement

### 4. Multi-Provider Support

- Pluggable providers
- Automatic fallback
- Consistent interface

### 5. Safety First

- Configurable safety levels
- Block dangerous operations
- Confirm destructive actions

## Performance Considerations

| Component | Optimization |
|-----------|-------------|
| Skill Loading | Lazy load on first use |
| Token Counting | Cache estimates |
| Context Compression | Summarize when >75% |
| Provider Calls | Stream responses |
| Tool Execution | Async I/O |

## Extension Points

1. **New Provider**: Extend `BaseProvider`
2. **New Skill**: Extend `LazySkill`
3. **New Tool**: Extend `BaseTool`
4. **New Command**: Add Click command in `cli.py`
5. **New Safety Rule**: Extend `SafetyChecker`
