# Lazy Skill Loading System

The oclawma skill system provides on-demand loading of skills to minimize startup time and memory usage.

## Features

- **Lazy Loading**: Skills are only loaded when their tools are actually invoked
- **YAML Manifests**: Skills are defined via human-readable YAML manifests
- **Token Count Caching**: Token estimates are cached to avoid recomputation
- **Filesystem Discovery**: Skills are automatically discovered from directories
- **Budget Tracking**: Token budgets can be tracked across skills

## Quick Start

### Creating a Skill

1. Create a directory for your skill:
```bash
mkdir -p ~/.oclawma/skills/my_skill
```

2. Create a `skill.yaml` manifest:
```yaml
name: my_skill
version: 1.0.0
description: My custom skill
author: Your Name
category: utilities
tags: [example, demo]
entry_point: my_skill:MySkill
tools:
  - name: hello
    description: Say hello
    parameters:
      - name: name
        type: string
        description: Name to greet
        required: false
    returns: string
    token_count: 50
```

3. Create the skill implementation (`my_skill.py`):
```python
from oclawma.skills import LazySkill, SkillMetadata

class MySkill(LazySkill):
    def _load(self) -> None:
        self._tools = {"hello": self._hello}
        self._loaded = True
    
    async def _hello(self, name: str = "World") -> dict:
        return {
            "success": True,
            "output": f"Hello, {name}!"
        }
```

### Using the Registry

```python
from oclawma.skills import SkillRegistry

# Create registry
registry = SkillRegistry()

# Discover skills (but don't load them)
registry.discover_skills("~/.oclawma/skills")

# List available skills
print(registry.list_available())  # ['my_skill', ...]

# Skill is loaded only when tool is executed
result = await registry.execute_tool("my_skill", "hello", name="Alice")
```

## Architecture

```
┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   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         │
└─────────────────┘
```

## Manifest Format

The skill manifest (`skill.yaml`) defines:

| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Skill identifier (lowercase, no spaces) |
| `version` | Yes | Semantic version (e.g., "1.0.0") |
| `description` | No | Human-readable description |
| `author` | No | Author name |
| `category` | No | Category for grouping |
| `tags` | No | List of tags |
| `requirements` | No | Python package dependencies |
| `entry_point` | Yes | Module path (e.g., "module:ClassName") |
| `tools` | No | List of tool definitions |

### Tool Definition

```yaml
tools:
  - name: tool_name          # Required: tool identifier
    description: "..."       # Required: what it does
    parameters:              # Optional: input parameters
      - name: param1
        type: string
        description: "..."
        required: true
    returns: string          # Optional: return type
    token_count: 100         # Optional: estimated tokens
```

## API Reference

### SkillRegistry

```python
# Discovery
registry.discover_skills("/path/to/skills", recursive=True)

# Query
registry.list_available()   # All discovered skills
registry.list_loaded()      # Currently loaded skills
registry.has_skill("name")  # Check if skill exists

# Execution
await registry.execute_tool("skill", "tool", **params)

# Loading control
registry.load_skill("name")    # Explicitly load
registry.unload_skill("name")  # Unload to free memory
registry.preload_skills([...]) # Preload multiple
```

### LazySkill

```python
class MySkill(LazySkill):
    def _load(self) -> None:
        """Called when tools are first accessed."""
        self._tools = {
            "tool1": self._tool1,
            "tool2": self._tool2,
        }
        self._loaded = True
    
    def _on_unload(self) -> None:
        """Optional: cleanup when unloaded."""
        pass
```

### TokenCache

```python
cache = TokenCache()

# Cache a token count
cache.set("skill_name", 1500, source_hash="abc123")

# Retrieve (returns None if expired or hash mismatch)
count = cache.get("skill_name", source_hash="abc123")

# Estimate from text
estimate = cache.estimate_tokens("some text")
```

## Examples

See the `examples/skills/` directory for complete skill examples.

### Docker Skill

The included Docker skill demonstrates:
- Multiple tools (ps, run, stop, logs, images, build)
- Parameter definitions
- Lazy loading with subprocess-based implementation

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `OCLAWMA_SKILLS_PATH` | `~/.oclawma/skills` | Default skills directory |
| `OCLAWMA_CACHE_DIR` | `~/.cache/oclawma` | Cache directory for tokens |

## Testing

Run the skill tests:

```bash
pytest tests/test_skills.py -v
```
