24. OpenCLaw Implementation Project

Chapter 24 of 24 · 25 min

This chapter synthesizes the preceding material into a complete OpenCLaw implementation. The project builds incrementally, demonstrating how components integrate into a functional personal AI agent.

Project Structure

openclaw/
├── src/
│   ├── __init__.py
│   ├── core.py              # Main agent implementation
│   ├── llm_client.py        # LLM interface
│   ├── storage.py           # Persistence layer
│   ├── context.py           # Context management
│   ├── feedback.py          # Learning system
│   ├── preferences.py       # User preferences
│   ├── notifications.py     # Notification system
│   ├── security.py          # Authentication/authorization
│   ├── privacy.py           # Privacy controls
│   ├── web.py               # Web interface
│   ├── plugins/             # Plugin system
│   │   ├── __init__.py
│   │   ├── manager.py
│   │   └── sdk.py
│   └── utils/
│       ├── profiler.py
│       └── power.py
├── tests/
├── config/
├── plugins/
├── web_static/
└── main.py

Core Implementation

The main entry point initializes all components and starts the agent.

# main.py
import asyncio
from src.core import OpenCLawCore
from src.config import load_config
from src.storage import create_storage
from src.llm_client import create_llm_client
from src.web import WebInterface
from src.plugins.manager import PluginManager

async def main():
    config = load_config('config/openclaw.yaml')
    
    storage = create_storage(config.storage)
    llm_client = create_llm_client(config.llm)
    
    core = OpenCLawCore(
        config=config.core,
        storage=storage,
        llm_client=llm_client
    )
    
    plugin_manager = PluginManager(
        config.plugins.directory,
        core.api
    )
    plugin_manager.load_all()
    
    web_interface = WebInterface(core, config.web)
    
    await core.start()
    await web_interface.start()
    
    print(f"OpenCLaw running on {config.web.host}:{config.web.port}")
    
    try:
        await asyncio.Event().wait()
    except KeyboardInterrupt:
        print("\nShutting down...")
        await core.stop()
        await web_interface.stop()

if __name__ == '__main__':
    asyncio.run(main())

Complete Agent Class

The core agent class orchestrates all components.

# src/core.py
from dataclasses import dataclass
from typing import Optional, Dict, Any
from datetime import datetime
import asyncio

from .llm_client import LLMClient
from .storage import StorageBackend
from .context import ContextManager
from .feedback import FeedbackCapture
from .preferences import PreferenceLearner, PreferenceMixer
from .notifications import NotificationManager
from .security import AuthService, AuthzService
from .privacy import PrivacyManager

@dataclass
class CoreConfig:
    max_context_tokens: int = 8192
    feedback_threshold: int = 10
    retention_days: int = 90

class OpenCLawCore:
    def __init__(
        self,
        config: CoreConfig,
        storage: StorageBackend,
        llm_client: LLMClient
    ):
        self.config = config
        self.storage = storage
        self.llm = llm_client
        
        self.context_manager = ContextManager(config.max_context_tokens)
        self.feedback_capture = FeedbackCapture(storage)
        self.preference_learner = PreferenceLearner(storage)
        self.notification_manager = NotificationManager()
        self.privacy_manager = PrivacyManager(storage)
        self.auth = AuthService()
        self.authz = AuthzService(self.auth)
        
        self.running = False
    
    @property
    def api(self):
        return CoreAPI(self)
    
    async def start(self):
        self.running = True
        await self.privacy_manager.apply_retention_policies()
    
    async def stop(self):
        self.running = False
    
    async def process_message(
        self,
        message: str,
        user_id: str = 'default',
        context: Optional[Dict] = None
    ) -> Response:
        user_context = self.context_manager.get_context(user_id)
        
        preference_mixer = self.preference_learner.get_mixer(user_id)
        response_config = preference_mixer.mix_response_config(
            ResponseConfig(), 
            task_complexity=0.5
        )
        
        prompt = self.build_prompt(
            message,
            user_context,
            response_config
        )
        
        llm_response = await self.llm.generate(
            prompt,
            max_tokens=response_config.max_tokens,
            temperature=response_config.temperature
        )
        
        self.context_manager.add_message(
            user_id,
            Message(role="user", content=message, timestamp=datetime.utcnow())
        )
        self.context_manager.add_message(
            user_id,
            Message(role="assistant", content=llm_response.content, timestamp=datetime.utcnow())
        )
        
        self.preference_learner.observe_interaction(
            user_id,
            Interaction(message, llm_response.content)
        )
        
        return Response(
            content=llm_response.content,
            message_id=llm_response.message_id,
            confidence=llm_response.confidence
        )
    
    def build_prompt(self, message: str, context: Any, config: ResponseConfig) -> str:
        system_prompt = self.get_system_prompt(config)
        
        context_text = context.to_prompt_string() if context else ""
        
        return f"{system_prompt}\n\n{context_text}\nUser: {message}\nAssistant:"

class ResponseConfig:
    verbosity: float = 0.5
    format_hint: str = "auto"
    max_tokens: int = 1000
    temperature: float = 0.7

class Response:
    content: str
    message_id: str
    confidence: float

Running the Project

# Installation
pip install -e .

# Configuration
cp config/openclaw.example.yaml config/openclaw.yaml
# Edit config/openclaw.yaml with your settings

# Run tests
pytest tests/ -v

# Start the agent
python main.py

Exercise Project: Build a Custom Plugin

Create a weather plugin that enhances OpenCLaw with weather information:

  1. Create plugins/weather/__init__.py and plugins/weather/weather.py
  2. Implement PluginInterface with metadata
  3. Register a message_processor capability
  4. Add weather query handling to extract location and date from user messages
  5. Fetch weather data from an external API
  6. Format weather response according to user preferences
  7. Test the plugin integration
# plugins/weather/weather.py
from src.plugins.sdk import PluginInterface, PluginMetadata, PluginContext, Capability, CapabilityType

class WeatherPlugin(PluginInterface):
    def get_metadata(self) -> PluginMetadata:
        return PluginMetadata(
            name="weather",
            version="1.0.0",
            author="OpenCLaw",
            description="Provides weather information for user queries"
        )
    
    def initialize(self, context: PluginContext) -> bool:
        self.context = context
        self.api_key = context.config.get('weather_api_key')
        self.capability = Capability(
            name="weather_query",
            ctype=CapabilityType.MESSAGE_PROCESSOR,
            priority=50,
            handler=self.process_weather_query
        )
        self.context.api.register_capability("weather", self.capability)
        return True
    
    def shutdown(self):
        pass
    
    def process_weather_query(self, message: str, state: Dict) -> Optional[Dict]:
        location = self.extract_location(message)
        if not location:
            return None
        
        weather_data = self.fetch_weather(location)
        return {
            'enhanced': True,
            'weather': weather_data,
            'response_modifier': lambda r: f"{r}\n\nWeather in {location}: {weather_data['description']}, {weather_data['temp']}°"
        }
    
    def extract_location(self, text: str) -> Optional[str]:
        # Implementation
        pass
    
    def fetch_weather(self, location: str) -> Dict:
        # Implementation
        pass
EXERCISE

Extend the OpenCLaw implementation with a memory persistence feature that survives restarts. Implement conversation state serialization and deserialization. Add a migration system for handling schema updates across versions.