@clawhub-petercheng-b937315668
Open-source Deep Research framework. Autonomously searches, extracts, and synthesizes information from the web.
---
name: researchclaw
description: Open-source Deep Research framework. Autonomously searches, extracts, and synthesizes information from the web.
homepage: https://github.com/liantian-cn/researchclaw
metadata: {"openclaw":{"emoji":"🦁","requires":{"bins":["python"],"env":["OPENCLAW_API_KEY"]},"primaryEnv":"OPENCLAW_API_KEY"}}
---
# ResearchClaw 🦁
Open-source Deep Research framework based on OpenClaw. An intelligent research assistant that autonomously searches, extracts, and synthesizes information from the web.
## Installation
```bash
# Install from GitHub
pip install git+https://github.com/liantian-cn/researchclaw.git
# Or install locally
cd researchclaw
pip install -e .
```
## OpenClaw Commands
### /research
Research a topic deeply and generate a comprehensive report.
```
/research <topic> [--depth=3] [--engine=duckduckgo] [--output=path] [--format=markdown]
```
Examples:
```
/research AI trends 2026
/research quantum computing --depth=5
/research machine learning --engine=bing --output=./report.md
```
### /search
Search for information on the web.
```
/search <query> [--limit=10] [--engine=duckduckgo]
```
Examples:
```
/search machine learning
/search neural networks --limit=20
```
### /llm
Chat with LLM providers.
```
/llm <prompt> [--provider=deepseek] [--model=...] [--temperature=0.7]
```
Examples:
```
/llm What is Python?
/llm Explain quantum computing --provider=glm
```
### /health
Check ResearchClaw skill health status.
### /help
Show available commands.
## Standalone Scripts
You can also use the standalone scripts:
```bash
python {baseDir}/scripts/research.py "artificial intelligence trends 2026"
python {baseDir}/scripts/search.py "machine learning"
python {baseDir}/scripts/llm.py "What is Python?"
```
## Options
- `--depth, -d`: Research depth level (default: 3)
- `--engine, -e`: Search engine to use (duckduckgo, bing)
- `--output, -o`: Output file path
- `--format, -f`: Output format (markdown, html, json)
- `--lang, -l`: Language (en, zh)
## Requirements
- Python 3.10+
- Optional: API keys for search engines (Bing) and LLM providers (DeepSeek, GLM, etc.)
## Environment Variables
- `DEEPSEEK_API_KEY`: DeepSeek API key
- `GLM_API_KEY`: GLM API key
- `MINIMAX_API_KEY`: MiniMax API key
- `KIMI_API_KEY`: Kimi API key
- `QWEN_API_KEY`: Qwen API key
- `BING_API_KEY`: Bing Search API key
Notes:
- Uses DuckDuckGo by default (no API key required)
- Supports multiple LLM providers for synthesis
- Generates comprehensive research reports in multiple formats
FILE:__init__.py
"""
ResearchClaw Skill for OpenClaw
"""
from .interface import (
ResearchResult,
SearchResult,
SkillState,
BaseResearchSkill,
ResearchClawSkill,
create_skill,
)
__version__ = "0.5.0"
__all__ = [
"ResearchResult",
"SearchResult",
"SkillState",
"BaseResearchSkill",
"ResearchClawSkill",
"create_skill",
]
FILE:_meta.json
{
"ownerId": "researchclaw",
"slug": "researchclaw",
"version": "0.5.0",
"publishedAt": null
}
FILE:commands.py
"""
ResearchClaw Command Handler
Implements OpenClaw slash commands for ResearchClaw
"""
import re
import json
from typing import Dict, Any, List, Optional
from skill.interface import ResearchClawSkill, ResearchResult, SearchResult
class CommandParser:
"""Parse and validate ResearchClaw commands"""
# Command patterns
RESEARCH_PATTERN = re.compile(r'^/research\s+(.+?)(?:\s+--(\w+)\s+(\S+))*$')
SEARCH_PATTERN = re.compile(r'^/search\s+(.+?)(?:\s+--(\w+)\s+(\S+))*$')
LLM_PATTERN = re.compile(r'^/llm\s+(.+?)(?:\s+--(\w+)\s+(\S+))*$')
@staticmethod
def parse_research_args(args_str: str) -> Dict[str, Any]:
"""Parse /research command arguments
Args:
args_str: Arguments string after /research
Returns:
Dict: Parsed arguments
"""
# Simple parsing: topic and key=value pairs
# Handle: topic with spaces, --key value, key=value, --flag
result = {'depth': 3}
# Find the topic (everything up to first -- or key=)
import re
match = re.match(r'^([^-][^=]*?)(?:\s+--|\s+\w+=|$)', args_str.strip())
if match:
result['topic'] = match.group(1).strip()
else:
result['topic'] = args_str.strip()
# Parse key=value pairs (with or without --)
kv_pattern = re.compile(r'(?:--)?(\w+)=(\S+)')
for match in kv_pattern.finditer(args_str):
key, value = match.groups()
if value.isdigit():
value = int(value)
elif value.lower() in ('true', 'false'):
value = value.lower() == 'true'
result[key] = value
# Parse --flag format (no =)
flag_pattern = re.compile(r'\s+--(\w+)(?:\s+|$)')
for match in flag_pattern.finditer(args_str):
result[match.group(1)] = True
return result
@staticmethod
def parse_search_args(args_str: str) -> Dict[str, Any]:
"""Parse /search command arguments"""
result = {'limit': 10}
# Find the query (everything up to first -- or key=)
import re
match = re.match(r'^([^-][^=]*?)(?:\s+--|\s+\w+=|$)', args_str.strip())
if match:
result['query'] = match.group(1).strip()
else:
result['query'] = args_str.strip()
# Parse key=value pairs (with or without --)
kv_pattern = re.compile(r'(?:--)?(\w+)=(\S+)')
for match in kv_pattern.finditer(args_str):
key, value = match.groups()
if value.isdigit():
value = int(value)
result[key] = value
return result
class ResearchCommandHandler:
"""Handle OpenClaw commands for ResearchClaw"""
def __init__(self, skill: Optional[ResearchClawSkill] = None):
"""Initialize command handler
Args:
skill: ResearchClaw skill instance
"""
self.skill = skill or ResearchClawSkill()
self.skill.on_load()
self.parser = CommandParser()
def handle_command(self, command: str) -> Dict[str, Any]:
"""Handle incoming command
Args:
command: Command string (e.g., "/research AI trends")
Returns:
Dict: Command result with status, content, and metadata
"""
command = command.strip()
# Route to appropriate handler
if command.startswith('/research '):
return self.handle_research(command)
elif command.startswith('/search '):
return self.handle_search(command)
elif command.startswith('/llm '):
return self.handle_llm(command)
elif command == '/help':
return self.handle_help()
elif command == '/health':
return self.handle_health()
else:
return {
'success': False,
'error': f'Unknown command: {command}',
'message': 'Use /help for available commands'
}
def handle_research(self, command: str) -> Dict[str, Any]:
"""Handle /research command
Args:
command: Full command string
Returns:
Dict: Research result
"""
# Extract args
args_str = command[len('/research '):]
args = self.parser.parse_research_args(args_str)
try:
result = self.skill.research(**args)
return {
'success': True,
'type': 'research',
'topic': result.topic,
'content': result.content,
'sources': result.sources,
'confidence': result.confidence,
'metadata': result.metadata
}
except Exception as e:
return {
'success': False,
'error': str(e),
'type': 'research'
}
def handle_search(self, command: str) -> Dict[str, Any]:
"""Handle /search command"""
args_str = command[len('/search '):]
args = self.parser.parse_search_args(args_str)
try:
results = self.skill.search(**args)
return {
'success': True,
'type': 'search',
'query': args.get('query'),
'results': [r.to_dict() for r in results],
'count': len(results)
}
except Exception as e:
return {
'success': False,
'error': str(e),
'type': 'search'
}
def handle_llm(self, command: str) -> Dict[str, Any]:
"""Handle /llm command"""
args_str = command[len('/llm '):]
# Simple parsing: prompt and optional --provider
parts = args_str.split(' --')
prompt = parts[0].strip()
kwargs = {}
if len(parts) > 1:
for part in parts[1:]:
if ' ' in part:
key, value = part.split(' ', 1)
kwargs[key] = value
try:
response = self.skill.chat(prompt, **kwargs)
return {
'success': True,
'type': 'llm',
'prompt': prompt,
'response': response
}
except Exception as e:
return {
'success': False,
'error': str(e),
'type': 'llm'
}
def handle_help(self) -> Dict[str, Any]:
"""Handle /help command"""
return {
'success': True,
'type': 'help',
'commands': {
'/research <topic> [--depth=3] [--engine=duckduckgo] [--output=path] [--format=markdown]':
'Research a topic deeply',
'/search <query> [--limit=10] [--engine=duckduckgo]':
'Search for information',
'/llm <prompt> [--provider=deepseek] [--model=...] [--temperature=0.7]':
'Chat with LLM',
'/health': 'Check skill health',
'/help': 'Show this help message'
}
}
def handle_health(self) -> Dict[str, Any]:
"""Handle /health command"""
health = self.skill.health_check()
info = self.skill.get_info()
return {
'success': True,
'type': 'health',
**health,
'info': info
}
def format_result_as_markdown(result: Dict[str, Any]) -> str:
"""Format command result as markdown
Args:
result: Command result dictionary
Returns:
str: Formatted markdown
"""
if not result.get('success'):
return f"❌ Error: {result.get('error', 'Unknown error')}"
cmd_type = result.get('type')
if cmd_type == 'research':
lines = [
f"# Research: {result['topic']}",
"",
f"**Confidence:** {result.get('confidence', 0):.2f}",
f"**Sources:** {len(result.get('sources', []))}",
"",
"---",
"",
result['content'][:2000],
]
return "\n".join(lines)
elif cmd_type == 'search':
lines = [f"# Search Results: {result['query']}", ""]
for i, r in enumerate(result.get('results', []), 1):
lines.append(f"{i}. **{r['title']}**")
lines.append(f" {r['url']}")
lines.append(f" {r['snippet'][:100]}...")
lines.append("")
return "\n".join(lines)
elif cmd_type == 'llm':
return f"# LLM Response\n\n{result['response']}"
elif cmd_type == 'help':
lines = ["# ResearchClaw Commands", ""]
for cmd, desc in result.get('commands', {}).items():
lines.append(f"**{cmd}**")
lines.append(f"{desc}")
lines.append("")
return "\n".join(lines)
elif cmd_type == 'health':
return f"# Health Check\n\nStatus: {result.get('state')}\nHealthy: {result.get('healthy')}"
return json.dumps(result, indent=2)
__all__ = [
"CommandParser",
"ResearchCommandHandler",
"format_result_as_markdown"
]
FILE:examples/01_basic_research.py
#!/usr/bin/env python3
"""
Example 1: Basic Research
Run: python examples/01_basic_research.py
"""
import sys
import os
# Add skill to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from skill import ResearchClawSkill
def main():
print("=" * 50)
print("Example 1: Basic Research")
print("=" * 50)
# Create and load skill
skill = ResearchClawSkill()
skill.on_load()
# Simple research
print("\nResearching: artificial intelligence")
result = skill.research("artificial intelligence", depth=2)
print(f"\nTopic: {result.topic}")
print(f"Confidence: {result.confidence:.2f}")
print(f"Sources: {len(result.sources)}")
print(f"\nContent Preview:")
print(result.content[:500])
print("...")
# Cleanup
skill.on_unload()
print("\n✓ Research complete!")
if __name__ == "__main__":
main()
FILE:examples/02_search.py
#!/usr/bin/env python3
"""
Example 2: Search with Custom Options
Run: python examples/02_search.py
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from skill import ResearchClawSkill
def main():
print("=" * 50)
print("Example 2: Search with Options")
print("=" * 50)
skill = ResearchClawSkill()
skill.on_load()
# Search with limit
print("\nSearching: quantum computing (limit=5)")
results = skill.search("quantum computing", limit=5)
print(f"\nFound {len(results)} results:")
for i, r in enumerate(results, 1):
print(f"\n{i}. {r.title}")
print(f" URL: {r.url}")
print(f" Score: {r.score}")
print(f" Snippet: {r.snippet[:100]}...")
skill.on_unload()
print("\n✓ Search complete!")
if __name__ == "__main__":
main()
FILE:examples/03_llm_chat.py
#!/usr/bin/env python3
"""
Example 3: LLM Chat
Run: python examples/03_llm_chat.py
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from skill import ResearchClawSkill
def main():
print("=" * 50)
print("Example 3: LLM Chat")
print("=" * 50)
skill = ResearchClawSkill()
skill.on_load()
# Chat with default provider (DeepSeek)
print("\nChat with DeepSeek:")
response = skill.chat("What is machine learning in one sentence?")
print(f"Response: {response[:200]}...")
# Note: Requires DEEPSEAK_API_KEY environment variable
# Set it with: export DEEPSEEK_API_KEY=your_key
skill.on_unload()
print("\n✓ Chat complete!")
if __name__ == "__main__":
main()
FILE:examples/04_config.py
#!/usr/bin/env python3
"""
Example 4: Custom Configuration
Run: python examples/04_config.py
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from skill import ResearchClawSkill
def main():
print("=" * 50)
print("Example 4: Custom Configuration")
print("=" * 50)
# Custom configuration
config = {
"default_depth": 5,
"default_limit": 20,
"default_engine": "duckduckgo",
"default_provider": "deepseek",
"default_temperature": 0.5,
"cache_dir": "/tmp/researchclaw_cache"
}
skill = ResearchClawSkill(config)
skill.on_load()
print(f"\nConfig loaded:")
print(f" default_depth: {skill.get_config('default_depth')}")
print(f" default_limit: {skill.get_config('default_limit')}")
print(f" default_engine: {skill.get_config('default_engine')}")
print(f" cache_dir: {skill._cache_dir}")
# Update config at runtime
skill.set_config("custom_setting", "my_value")
print(f"\n custom_setting: {skill.get_config('custom_setting')}")
# Health check
health = skill.health_check()
print(f"\nHealth: {health}")
skill.on_unload()
print("\n✓ Config demo complete!")
if __name__ == "__main__":
main()
FILE:examples/05_output.py
#!/usr/bin/env python3
"""
Example 5: Research with Output
Run: python examples/05_output.py
"""
import sys
import os
import tempfile
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from skill import ResearchClawSkill
def main():
print("=" * 50)
print("Example 5: Research with Output")
print("=" * 50)
skill = ResearchClawSkill()
skill.on_load()
# Create temp directory for output
with tempfile.TemporaryDirectory() as tmpdir:
# Save as markdown
md_path = os.path.join(tmpdir, "report.md")
print(f"\nResearching with markdown output...")
result = skill.research(
"climate change",
depth=2,
output=md_path,
format="markdown"
)
print(f" Saved to: {md_path}")
# Check file exists
if os.path.exists(md_path):
size = os.path.getsize(md_path)
print(f" File size: {size} bytes")
# Save as JSON
json_path = os.path.join(tmpdir, "report.json")
print(f"\nResearching with JSON output...")
result = skill.research(
"climate change",
depth=2,
output=json_path,
format="json"
)
print(f" Saved to: {json_path}")
if os.path.exists(json_path):
size = os.path.getsize(json_path)
print(f" File size: {size} bytes")
skill.on_unload()
print("\n✓ Output demo complete!")
if __name__ == "__main__":
main()
FILE:examples/06_clawmem_integration.py
#!/usr/bin/env python3
"""
Example 6: Claw-Mem Integration
Run: python examples/06_clawmem_integration.py
This example shows how to integrate ResearchClaw with claw-mem
for persistent memory of research findings.
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from skill import ResearchClawSkill
def simulate_clawmem_save(topic, content, sources):
"""
Simulate saving to claw-mem.
In real usage, this would call the claw-mem API.
"""
print(f"\n[claw-mem] Saving research about '{topic}'")
print(f" - Content length: {len(content)} chars")
print(f" - Sources: {len(sources)} items")
# Simulated memory entry
memory_entry = {
"type": "research",
"topic": topic,
"sources": sources,
"timestamp": "2026-04-27T10:00:00Z"
}
print(f" - Memory entry created: {memory_entry['timestamp']}")
return memory_entry
def main():
print("=" * 50)
print("Example 6: Claw-Mem Integration")
print("=" * 50)
skill = ResearchClawSkill()
skill.on_load()
# Research a topic
print("\n1. Performing research on 'AI ethics'...")
result = skill.research("AI ethics", depth=3)
# Save to claw-mem
print("\n2. Integrating with claw-mem...")
memory = simulate_clawmem_save(
result.topic,
result.content,
result.sources
)
# Research another topic
print("\n3. Performing research on 'quantum computing'...")
result2 = skill.research("quantum computing", depth=2)
# Save to claw-mem
print("\n4. Integrating with claw-mem...")
memory2 = simulate_clawmem_save(
result2.topic,
result2.content,
result2.sources
)
print("\n5. Research complete! Both topics saved to memory.")
skill.on_unload()
print("\n✓ Claw-Mem integration demo complete!")
if __name__ == "__main__":
main()
FILE:examples/07_error_handling.py
#!/usr/bin/env python3
"""
Example 7: Error Handling
Run: python examples/07_error_handling.py
"""
import sys
import os
# Add skill to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from skill import ResearchClawSkill
def main():
print("=" * 50)
print("Example 7: Error Handling")
print("=" * 50)
skill = ResearchClawSkill()
skill.on_load()
# Example 1: Check health before operation
print("\n1. Checking skill health...")
health = skill.health_check()
if health["healthy"]:
print(" ✓ Skill is healthy")
else:
print(f" ✗ Skill error: {health.get('error')}")
# Example 2: Handle empty topic
print("\n2. Handling empty topic...")
try:
result = skill.research("")
print(f" Result: {result.topic}")
except Exception as e:
print(f" ✗ Error (expected): {type(e).__name__}")
# Example 3: Handle invalid engine
print("\n3. Handling invalid engine...")
try:
# This will use default engine since invalid one is ignored
result = skill.search("test", engine="invalid_engine")
print(f" ✓ Search worked (used default engine)")
except Exception as e:
print(f" ✗ Error: {e}")
# Example 4: Check state
print("\n4. Checking skill state...")
print(f" Current state: {skill.state.value}")
# Example 5: Graceful disable
print("\n5. Graceful disable...")
skill.on_disable()
print(f" State after disable: {skill.state.value}")
skill.on_unload()
print("\n✓ Error handling demo complete!")
if __name__ == "__main__":
main()
FILE:examples/README.md
# ResearchClaw Examples
This directory contains runnable examples demonstrating ResearchClaw usage.
## Running Examples
```bash
cd skill
python examples/01_basic_research.py
python examples/02_search.py
python examples/03_llm_chat.py
python examples/04_config.py
python examples/05_output.py
python examples/06_clawmem_integration.py
python examples/07_error_handling.py
```
## Examples List
| # | File | Description |
|---|------|-------------|
| 1 | `01_basic_research.py` | Basic research functionality |
| 2 | `02_search.py` | Search with custom options |
| 3 | `03_llm_chat.py` | LLM chat with providers |
| 4 | `04_config.py` | Custom configuration |
| 5 | `05_output.py` | Save research to file |
| 6 | `06_clawmem_integration.py` | Claw-Mem integration |
| 7 | `07_error_handling.py` | Error handling patterns |
## Prerequisites
Some examples require API keys:
```bash
# Set environment variables
export DEEPSEEK_API_KEY=your_key
export GLM_API_KEY=your_key
export MINIMAX_API_KEY=your_key
export KIMI_API_KEY=your_key
export QWEN_API_KEY=your_key
```
FILE:interface.py
"""
ResearchClaw Skill Interface
Defines the base class for OpenClaw Skill integration
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, List, Optional
from dataclasses import dataclass, field
from enum import Enum
import logging
import warnings
logger = logging.getLogger(__name__)
class SkillState(Enum):
"""Skill lifecycle states"""
UNLOADED = "unloaded"
LOADED = "loaded"
ENABLED = "enabled"
DISABLED = "disabled"
ERROR = "error"
# Setup logging
def _setup_logging():
"""Setup skill logging"""
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
_setup_logging()
@dataclass
class ResearchResult:
"""Research result from skill execution"""
topic: str
content: str
sources: List[str]
confidence: float = 0.0
metadata: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
return {
"topic": self.topic,
"content": self.content,
"sources": self.sources,
"confidence": self.confidence,
"metadata": self.metadata
}
@dataclass
class SearchResult:
"""Search result"""
title: str
url: str
snippet: str
score: Optional[float] = None
def to_dict(self) -> Dict[str, Any]:
return {
"title": self.title,
"url": self.url,
"snippet": self.snippet,
"score": self.score
}
class BaseResearchSkill(ABC):
"""Base class for ResearchClaw OpenClaw Skill"""
def __init__(self, config: Optional[Dict[str, Any]] = None):
"""Initialize skill with configuration
Args:
config: Skill configuration dictionary
"""
self.config = config or {}
self.name = "researchclaw"
self.version = "0.5.0"
self._state = SkillState.UNLOADED
self._error_message: Optional[str] = None
@property
def state(self) -> SkillState:
"""Get current skill state"""
return self._state
@property
def error_message(self) -> Optional[str]:
"""Get error message if in error state"""
return self._error_message
@abstractmethod
def research(self, topic: str, **kwargs) -> ResearchResult:
"""Execute research on a topic
Args:
topic: Research topic
**kwargs: Additional parameters (depth, engine, etc.)
Returns:
ResearchResult: Research findings
"""
pass
@abstractmethod
def search(self, query: str, **kwargs) -> List[SearchResult]:
"""Search for information
Args:
query: Search query
**kwargs: Additional parameters (limit, engine, etc.)
Returns:
List[SearchResult]: Search results
"""
pass
@abstractmethod
def chat(self, prompt: str, **kwargs) -> str:
"""Chat with LLM
Args:
prompt: User prompt
**kwargs: Additional parameters (provider, model, etc.)
Returns:
str: LLM response
"""
pass
# Configuration methods
def load_config(self, config_path: Optional[str] = None) -> Dict[str, Any]:
"""Load skill configuration
Args:
config_path: Path to config file
Returns:
Dict: Loaded configuration
"""
if config_path:
import json
from pathlib import Path
if Path(config_path).exists():
with open(config_path) as f:
loaded = json.load(f)
self.config.update(loaded)
return self.config
def get_config(self, key: str, default: Any = None) -> Any:
"""Get configuration value
Args:
key: Configuration key
default: Default value if key not found
Returns:
Any: Configuration value
"""
return self.config.get(key, default)
def set_config(self, key: str, value: Any) -> None:
"""Set configuration value
Args:
key: Configuration key
value: Configuration value
"""
self.config[key] = value
# Lifecycle methods
def on_load(self) -> bool:
"""Called when skill is loaded
Returns:
bool: True if load successful
"""
try:
self._state = SkillState.LOADED
logger.info(f"Skill {self.name} loaded successfully")
return True
except Exception as e:
self._state = SkillState.ERROR
self._error_message = str(e)
logger.error(f"Failed to load skill {self.name}: {e}")
return False
def on_unload(self) -> None:
"""Called when skill is unloaded"""
self._state = SkillState.UNLOADED
logger.info(f"Skill {self.name} unloaded")
def on_enable(self) -> bool:
"""Called when skill is enabled
Returns:
bool: True if enable successful
"""
if self._state == SkillState.LOADED:
self._state = SkillState.ENABLED
logger.info(f"Skill {self.name} enabled")
return True
return False
def on_disable(self) -> None:
"""Called when skill is disabled"""
self._state = SkillState.DISABLED
logger.info(f"Skill {self.name} disabled")
def on_start(self) -> bool:
"""Called when skill starts processing
Returns:
bool: True if start successful
"""
return True
def on_stop(self) -> None:
"""Called when skill stops processing"""
pass
def health_check(self) -> Dict[str, Any]:
"""Perform health check
Returns:
Dict: Health status
"""
return {
"healthy": self._state != SkillState.ERROR,
"state": self._state.value,
"error": self._error_message
}
def get_info(self) -> Dict[str, Any]:
"""Get skill information
Returns:
Dict: Skill metadata
"""
return {
"name": self.name,
"version": self.version,
"description": "Open-source Deep Research framework",
"state": self._state.value,
}
class ResearchClawSkill(BaseResearchSkill):
"""ResearchClaw Skill implementation for OpenClaw"""
def __init__(self, config: Optional[Dict[str, Any]] = None):
super().__init__(config)
self._runner = None
self._search_engine = None
self._llm_engine = None
self._cache_dir = self.get_config("cache_dir", ".researchclaw_cache")
def on_load(self) -> bool:
"""Load skill and initialize components"""
try:
# Ensure cache directory exists
from pathlib import Path
Path(self._cache_dir).mkdir(parents=True, exist_ok=True)
# Call parent on_load
return super().on_load()
except Exception as e:
self._state = SkillState.ERROR
self._error_message = str(e)
return False
def research(self, topic: str, **kwargs) -> ResearchResult:
"""Execute research on a topic
Args:
topic: Research topic (required, non-empty)
**kwargs: Additional parameters (depth, engine, etc.)
Returns:
ResearchResult: Research findings
Raises:
ValueError: If topic is empty or invalid
"""
# Validate topic
if not topic or not topic.strip():
logger.warning("Empty topic provided, returning empty result")
return ResearchResult(
topic="",
content="",
sources=[],
confidence=0.0,
metadata={"error": "Empty topic"}
)
# Validate depth
depth = kwargs.get('depth', self.get_config('default_depth', 3))
if depth is not None:
try:
depth = int(depth)
depth = max(1, min(depth, 10)) # Clamp to 1-10
except (ValueError, TypeError):
depth = 3
logger.warning(f"Invalid depth {kwargs.get('depth')}, using default 3")
logger.info(f"Starting research on topic: {topic[:50]}... (depth={depth})")
from researchclaw.research.runner import ResearchRunner
output = kwargs.get('output')
output_format = kwargs.get('format', 'markdown')
try:
runner = ResearchRunner()
report = runner.run(topic, depth=depth)
except Exception as e:
logger.error(f"Research failed: {e}")
return ResearchResult(
topic=topic,
content=f"Research failed: {str(e)}",
sources=[],
confidence=0.0,
metadata={"error": str(e)}
)
# Save to file if output specified
if output:
from pathlib import Path
output_file = Path(output)
output_file.parent.mkdir(parents=True, exist_ok=True)
if output_format == 'json':
content = report.to_json()
elif output_format == 'html':
content = report.format_html()
else:
content = report.format_markdown()
output_file.write_text(content)
# Collect all sources
all_sources = []
for section in report.sections:
all_sources.extend(section.sources)
avg_confidence = sum(s.confidence for s in report.sections) / len(report.sections) if report.sections else 0
return ResearchResult(
topic=report.topic,
content=report.format_markdown(),
sources=all_sources,
confidence=avg_confidence,
metadata={
"version": report.version,
"sections": len(report.sections),
"output": output
}
)
def search(self, query: str, **kwargs) -> List[SearchResult]:
"""Search for information
Args:
query: Search query (required, non-empty)
**kwargs: Additional parameters (limit, engine, etc.)
Returns:
List[SearchResult]: Search results
Raises:
ValueError: If query is empty
"""
# Validate query
if not query or not query.strip():
logger.warning("Empty query provided, returning empty results")
return []
# Validate limit
limit = kwargs.get('limit', self.get_config('default_limit', 10))
if limit is not None:
try:
limit = int(limit)
limit = max(1, min(limit, 50)) # Clamp to 1-50
except (ValueError, TypeError):
limit = 10
logger.warning(f"Invalid limit {kwargs.get('limit')}, using default 10")
engine = kwargs.get('engine', self.get_config('default_engine', 'duckduckgo'))
logger.info(f"Searching for: {query[:50]}... (limit={limit}, engine={engine})")
from researchclaw.research.search import SearchEngine
search_engine = SearchEngine(provider=engine)
try:
results = search_engine.search(query, limit=limit)
except Exception as e:
logger.error(f"Search failed: {e}")
return []
return [
SearchResult(
title=r.title,
url=r.url,
snippet=r.snippet,
score=r.score
)
for r in results
]
def chat(self, prompt: str, **kwargs) -> str:
"""Chat with LLM
Args:
prompt: User prompt (required, non-empty)
**kwargs: Additional parameters (provider, model, temperature, etc.)
Returns:
str: LLM response
Raises:
ValueError: If prompt is empty
"""
# Validate prompt
if not prompt or not prompt.strip():
logger.warning("Empty prompt provided")
return "Error: Empty prompt. Please provide a valid prompt."
provider = kwargs.get('provider', self.get_config('default_provider', 'deepseek'))
model = kwargs.get('model')
temperature = kwargs.get('temperature', self.get_config('default_temperature', 0.7))
max_tokens = kwargs.get('max_tokens')
# Validate temperature
try:
temperature = float(temperature)
temperature = max(0.0, min(temperature, 2.0)) # Clamp to 0-2
except (ValueError, TypeError):
temperature = 0.7
logger.warning(f"Invalid temperature, using default 0.7")
# Get API key from environment
import os
api_key = os.environ.get(f"{provider.upper()}_API_KEY")
logger.info(f"Chat request to {provider}: {prompt[:30]}...")
try:
from researchclaw.llm.engine import LLMEngine
from researchclaw.llm import ChatMessage, MessageRole
engine = LLMEngine(provider=provider, model=model, api_key=api_key)
messages = [ChatMessage(role=MessageRole.USER, content=prompt)]
response = engine.chat(messages, temperature=temperature, max_tokens=max_tokens)
return response.content
except Exception as e:
logger.error(f"Chat failed: {e}")
return f"Error: Chat failed - {str(e)}"
def health_check(self) -> Dict[str, Any]:
"""Perform health check"""
base_health = super().health_check()
# Check if required modules are available
try:
from researchclaw.research.runner import ResearchRunner
from researchclaw.research.search import SearchEngine
modules_ok = True
except ImportError as e:
modules_ok = False
base_health["error"] = str(e)
return {
**base_health,
"modules_loaded": modules_ok,
"cache_dir": self._cache_dir
}
# Factory function
def create_skill(config: Optional[Dict[str, Any]] = None) -> BaseResearchSkill:
"""Factory function to create ResearchClaw skill instance
Args:
config: Skill configuration
Returns:
BaseResearchSkill: Skill instance
"""
return ResearchClawSkill(config=config)
__all__ = [
"ResearchResult",
"SearchResult",
"SkillState",
"BaseResearchSkill",
"ResearchClawSkill",
"create_skill"
]
FILE:requirements.txt
# ResearchClaw Skill Dependencies
# Python 3.10+
# Core dependencies
requests>=2.28.0
beautifulsoup4>=4.11.0
lxml>=4.9.0
# CLI
click>=8.1.0
rich>=13.0.0
# Search
ddgs>=9.0.0
# Research
html2text>=2020.1.16
weasyprint>=62.0.0
# Optional: API server
fastapi>=0.109.0
uvicorn[standard]>=0.27.0
# Optional: LLM providers
# DeepSeek: pip install openai
# GLM: pip install zhipuai
# etc.
FILE:scripts/llm.py
#!/usr/bin/env python3
"""
ResearchClaw LLM Chat Command for OpenClaw Skill
"""
import sys
import os
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
import argparse
from researchclaw.llm.engine import LLMEngine
from researchclaw.llm import ChatMessage, MessageRole
def main():
parser = argparse.ArgumentParser(description='Chat with LLM')
parser.add_argument('prompt', help='Prompt for LLM')
parser.add_argument('--provider', '-p', default='deepseek', help='LLM provider')
parser.add_argument('--model', '-m', help='Model name')
parser.add_argument('--temperature', '-t', type=float, default=0.7, help='Temperature')
args = parser.parse_args()
# Get API key from environment
api_key = os.environ.get(f"{args.provider.upper()}_API_KEY")
engine = LLMEngine(provider=args.provider, model=args.model, api_key=api_key)
messages = [ChatMessage(role=MessageRole.USER, content=args.prompt)]
response = engine.chat(messages, temperature=args.temperature)
print(f"Provider: {args.provider}")
print(f"Model: {engine.model}")
print(f"\n{response.content}")
if __name__ == '__main__':
main()
FILE:scripts/research.py
#!/usr/bin/env python3
"""
ResearchClaw Research Command for OpenClaw Skill
"""
import sys
import os
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
import argparse
from researchclaw.research.runner import ResearchRunner
def main():
parser = argparse.ArgumentParser(description='Research a topic deeply')
parser.add_argument('topic', help='Research topic')
parser.add_argument('--depth', '-d', type=int, default=3, help='Research depth')
parser.add_argument('--engine', '-e', default='duckduckgo', help='Search engine')
parser.add_argument('--output', '-o', help='Output file path')
parser.add_argument('--format', '-f', choices=['markdown', 'html', 'json'],
default='markdown', help='Output format')
args = parser.parse_args()
runner = ResearchRunner()
report = runner.run(args.topic, depth=args.depth)
# Output results
if args.output:
from pathlib import Path
output_file = Path(args.output)
output_file.parent.mkdir(parents=True, exist_ok=True)
if args.format == 'json':
content = report.to_json()
elif args.format == 'html':
content = report.format_html()
else:
content = report.format_markdown()
output_file.write_text(content)
print(f"Report saved to: {args.output}")
else:
print(report.format_markdown()[:2000])
print("\n... (truncated)")
if __name__ == '__main__':
main()
FILE:scripts/search.py
#!/usr/bin/env python3
"""
ResearchClaw Search Command for OpenClaw Skill
"""
import sys
import os
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
import argparse
from researchclaw.research.search import SearchEngine
def main():
parser = argparse.ArgumentParser(description='Search for information')
parser.add_argument('query', help='Search query')
parser.add_argument('--limit', '-n', type=int, default=10, help='Number of results')
parser.add_argument('--engine', '-e', default='duckduckgo', help='Search engine')
args = parser.parse_args()
engine = SearchEngine(provider=args.engine)
results = engine.search(args.query, limit=args.limit)
print(f"Found {len(results)} results for: {args.query}\n")
for i, result in enumerate(results, 1):
print(f"{i}. {result.title}")
print(f" {result.url}")
print(f" {result.snippet[:150]}...")
print()
if __name__ == '__main__':
main()
Self-Improvement System for AI agents. Features: feedback collection, hint extraction, rule learning. Use for: learning from user corrections.
---
name: claw-rl
description: "Self-Improvement System for AI agents. Use when: (1) collecting user feedback (thumbs up/down, corrections), (2) extracting improvement hints from user messages, (3) learning from mistakes and preferences, (4) injecting learned rules into sessions. Features Binary RL evaluation, OPD hint extraction, Multi-Armed Bandit strategy selection, and continuous background learning."
homepage: https://github.com/opensourceclaw/claw-rl
metadata: {"clawdbot":{"emoji":"🔄","requires":{"bins":["python3"],"packages":["claw-rl"]}}}
---
# claw-rl: Self-Improvement System for AI agents
Self-improvement system for AI agents with reinforcement learning and continuous learning.
## Prerequisites
```bash
pip install claw-rl
```
## Quick Start
### Collect Feedback
```bash
python3 {baseDir}/scripts/collect_feedback.py "Great job!" --action file_created
python3 {baseDir}/scripts/collect_feedback.py "Wrong, use Chinese instead" --action response --negative
```
### Get Learned Rules
```bash
python3 {baseDir}/scripts/get_rules.py --top-k 10
python3 {baseDir}/scripts/get_rules.py --context "user preference"
```
### Check Learning Status
```bash
python3 {baseDir}/scripts/status.py
```
### Start Learning Daemon
```bash
python3 {baseDir}/scripts/daemon.py start
python3 {baseDir}/scripts/daemon.py stop
python3 {baseDir}/scripts/daemon.py status
```
## Core Components
| Component | Purpose | Example |
|-----------|---------|---------|
| **Binary RL Judge** | Evaluate satisfaction from feedback | 👍 → positive, 👎 → negative |
| **OPD Extractor** | Extract improvement hints | "Use Chinese" → rule hint |
| **Learning Loop** | Continuous background learning | Process feedback queue |
| **MAB Strategy** | Strategy selection via bandits | Thompson Sampling, ε-greedy |
## Learning Modes
| Mode | Description | Use Case |
|------|-------------|----------|
| `calibration` | Calibration learning | User satisfaction calibration |
| `strategy` | Strategy learning | Action selection optimization |
| `value` | Value preference learning | User preference learning |
| `context` | Context-aware learning | Situational rules |
## Configuration
OpenClaw config (`openclaw.config.json`):
```json
{
"plugins": {
"slots": {
"context-engine": "claw-rl"
},
"claw-rl": {
"config": {
"workspaceDir": "~/.openclaw/workspace",
"autoInject": true,
"autoLearn": true,
"topK": 10
}
}
}
}
```
## Performance
| Operation | Latency |
|-----------|---------|
| Initialize | ~2ms |
| Collect feedback | ~0.03ms |
| Extract hint | ~1ms |
| Get rules | ~0.7ms |
| Process learning | ~0.5ms |
## Advanced
See references for detailed documentation:
- [Architecture](references/architecture.md) - System design
- [Learning](references/learning.md) - Learning algorithms
- [MAB](references/mab.md) - Multi-Armed Bandit strategies
FILE:references/architecture.md
# claw-rl Architecture
## Overview
claw-rl is a self-improvement system for AI agents based on reinforcement learning:
1. **Feedback Collection** - Collect user satisfaction signals
2. **Hint Extraction** - Extract improvement hints from corrections
3. **Rule Learning** - Learn behavioral rules from feedback
4. **Rule Injection** - Inject learned rules into sessions
## Core Components
### Binary RL Judge
Evaluates user satisfaction from feedback signals:
- Positive signals: 👍, "good", "thanks", etc.
- Negative signals: 👎, "wrong", "no", corrections
- Outputs: confidence score, signal type
### OPD Hint Extractor
Extracts One-Prompt Directive hints from user corrections:
- Pattern recognition from correction text
- Rule extraction from "do X instead" patterns
- Context-aware hint prioritization
### Learning Loop
Continuous background learning:
- Process feedback queue
- Update rule weights
- Decay old rules
- Merge similar rules
### Multi-Armed Bandit
Strategy selection via bandit algorithms:
- Thompson Sampling
- ε-Greedy
- UCB (Upper Confidence Bound)
- Adaptive exploration/exploitation
## Data Flow
```
User Feedback → Binary RL Judge → Signal Classification
↓
OPD Hint Extractor (if negative)
↓
Learning Loop → Rule Storage
↓
MAB Strategy Selection
↓
Rule Injection → Session Context
```
## Storage
All learning data stored in SQLite:
- `feedback.db` - Feedback history
- `rules.db` - Learned rules
- `stats.db` - Learning statistics
FILE:references/learning.md
# Learning Algorithms
## Learning Types
### Calibration Learning
Adjusts confidence thresholds based on user feedback:
- Tracks calibration accuracy over time
- Adjusts decision boundaries
- Reduces false positives/negatives
### Strategy Learning
Optimizes action selection strategies:
- Learns which actions lead to satisfaction
- Weights actions by success rate
- Context-aware strategy selection
### Value Preference Learning
Learns user preferences:
- Extracts preference patterns from feedback
- Builds preference model per user
- Adapts to changing preferences
### Context-Aware Learning
Learns situational rules:
- Identifies context patterns
- Associates rules with contexts
- Applies context-specific rules
## Learning Pipeline
```
1. Collect Feedback
↓
2. Classify Signal (positive/negative)
↓
3. Extract Hints (if negative)
↓
4. Update Rule Weights
↓
5. Decay Old Rules
↓
6. Merge Similar Rules
↓
7. Inject into Context
```
## Rule Prioritization
Rules are prioritized by:
- Recency (recent feedback weighted higher)
- Frequency (repeated patterns weighted higher)
- Confidence (high-confidence hints weighted higher)
- Context match (relevant contexts weighted higher)
## Rule Decay
Old rules decay over time:
- Exponential decay with configurable half-life
- Low-weight rules removed automatically
- Important rules protected from decay
FILE:references/mab.md
# Multi-Armed Bandit Strategies
## Overview
Multi-Armed Bandit (MAB) provides intelligent strategy selection:
- Balances exploration vs exploitation
- Adapts to changing environments
- Optimizes for long-term reward
## Available Strategies
### Thompson Sampling
Bayesian approach with beta distributions:
- Models uncertainty in strategy performance
- Samples from posterior distribution
- Naturally balances exploration/exploitation
```python
from claw_rl.mab import ThompsonSamplingStrategy
strategy = ThompsonSamplingStrategy(
alpha=1.0, # Prior success count
beta=1.0 # Prior failure count
)
```
### ε-Greedy
Simple but effective strategy:
- Explores with probability ε
- Exploits best strategy with probability 1-ε
- Configurable decay for ε
```python
from claw_rl.mab import EpsilonGreedyStrategy
strategy = EpsilonGreedyStrategy(
epsilon=0.1, # Exploration rate
decay=0.99, # Decay per round
min_epsilon=0.01 # Minimum ε
)
```
### Decay Modes
- `none` - No decay
- `linear` - Linear decay per round
- `exponential` - Exponential decay per round
- `step` - Step decay at intervals
## Strategy Performance Tracking
Each strategy tracks:
- Total selections
- Success count
- Failure count
- Average reward
- Last selection timestamp
## Selection Process
```
1. Get all available strategies
2. Calculate selection probability
3. Sample strategy based on algorithm
4. Execute selected strategy
5. Update performance metrics
6. Repeat
```
## Best Practices
- Start with Thompson Sampling for uncertain environments
- Use ε-Greedy for simpler scenarios
- Monitor strategy performance over time
- Adjust decay parameters based on feedback frequency
FILE:scripts/collect_feedback.py
#!/usr/bin/env python3
"""Collect user feedback for learning"""
import sys
import argparse
def main():
parser = argparse.ArgumentParser(description="Collect feedback for claw-rl")
parser.add_argument("feedback", help="User feedback text")
parser.add_argument("--action", "-a", default="general", help="Action that triggered feedback")
parser.add_argument("--negative", "-n", action="store_true", help="Mark as negative feedback")
parser.add_argument("--workspace", "-w", default="~/.openclaw/workspace", help="Workspace path")
args = parser.parse_args()
try:
from claw_rl import BinaryRLJudge, OPDHintExtractor
from claw_rl.core import LearningLoop
except ImportError:
print("❌ claw-rl not installed. Run: pip install claw-rl")
sys.exit(1)
# Evaluate feedback
judge = BinaryRLJudge()
signal = "negative" if args.negative else "positive"
result = judge.judge(args.feedback, signal)
# Extract hint if negative
if args.negative:
extractor = OPDHintExtractor()
hint = extractor.extract(args.feedback)
if hint:
print(f"💡 Extracted hint: {hint.content}")
print(f"📊 Feedback signal: {signal}")
print(f"📈 Confidence: {result.confidence:.2f}")
# Queue for learning
loop = LearningLoop(workspace=args.workspace)
loop.queue_feedback(args.feedback, signal, args.action)
print("✅ Feedback queued for learning")
if __name__ == "__main__":
main()
FILE:scripts/daemon.py
#!/usr/bin/env python3
"""Manage learning daemon"""
import sys
import argparse
def main():
parser = argparse.ArgumentParser(description="Manage claw-rl daemon")
parser.add_argument("action", choices=["start", "stop", "status", "restart"], help="Daemon action")
parser.add_argument("--workspace", "-w", default="~/.openclaw/workspace", help="Workspace path")
args = parser.parse_args()
try:
from claw_rl.core import LearningDaemon
except ImportError:
print("❌ claw-rl not installed. Run: pip install claw-rl")
sys.exit(1)
daemon = LearningDaemon(workspace=args.workspace)
if args.action == "start":
daemon.start()
print("✅ Learning daemon started")
elif args.action == "stop":
daemon.stop()
print("✅ Learning daemon stopped")
elif args.action == "status":
status = daemon.status()
print(f"📊 Daemon status: {status}")
elif args.action == "restart":
daemon.stop()
daemon.start()
print("✅ Learning daemon restarted")
if __name__ == "__main__":
main()
FILE:scripts/get_rules.py
#!/usr/bin/env python3
"""Get learned rules for injection"""
import sys
import argparse
def main():
parser = argparse.ArgumentParser(description="Get learned rules from claw-rl")
parser.add_argument("--top-k", "-k", type=int, default=10, help="Max rules to return")
parser.add_argument("--context", "-c", default=None, help="Context filter")
parser.add_argument("--workspace", "-w", default="~/.openclaw/workspace", help="Workspace path")
args = parser.parse_args()
try:
from claw_rl.core import LearningLoop
except ImportError:
print("❌ claw-rl not installed. Run: pip install claw-rl")
sys.exit(1)
loop = LearningLoop(workspace=args.workspace)
rules = loop.get_rules(top_k=args.top_k, context=args.context)
if not rules:
print("No learned rules found")
return
print(f"📚 Learned Rules ({len(rules)}):\n")
for i, rule in enumerate(rules, 1):
priority = rule.get('priority', 'normal')
content = rule.get('content', '')
source = rule.get('source', 'unknown')
print(f"{i}. [{priority}] {content}")
print(f" Source: {source}")
print()
if __name__ == "__main__":
main()
FILE:scripts/status.py
#!/usr/bin/env python3
"""Check learning system status"""
import argparse
def main():
parser = argparse.ArgumentParser(description="Check claw-rl status")
parser.add_argument("--workspace", "-w", default="~/.openclaw/workspace", help="Workspace path")
args = parser.parse_args()
try:
from claw_rl.core import LearningLoop, LearningDaemon
from claw_rl.auto_activate import is_active
except ImportError:
print("❌ claw-rl not installed. Run: pip install claw-rl")
return
print("🔄 claw-rl Status\n")
print(f"Active: {'✅ Yes' if is_active() else '❌ No'}")
loop = LearningLoop(workspace=args.workspace)
stats = loop.get_stats()
print(f"\n📊 Statistics:")
print(f" Total feedback: {stats.get('total_feedback', 0)}")
print(f" Positive: {stats.get('positive_count', 0)}")
print(f" Negative: {stats.get('negative_count', 0)}")
print(f" Rules learned: {stats.get('rules_count', 0)}")
print(f" Pending queue: {stats.get('pending_count', 0)}")
daemon = LearningDaemon(workspace=args.workspace)
daemon_status = daemon.status()
print(f"\n daemon: {daemon_status}")
if __name__ == "__main__":
main()
Three-Tier Memory System for OpenClaw. Store and recall memories across sessions with Episodic, Semantic, and Procedural layers. BM25 + Heuristic retrieval.
---
name: claw-mem
description: "Three-Tier Memory System for OpenClaw. Store and recall memories across sessions with Episodic, Semantic, and Procedural layers. BM25 + Heuristic retrieval."
homepage: https://github.com/opensourceclaw/claw-mem
metadata: {"clawdbot":{"emoji":"🧠","requires":{"bins":["python3"],"packages":["claw-mem"]}}}
---
# claw-mem: Three-Tier Memory System
Local-first memory system with three memory layers and intelligent retrieval.
## Prerequisites
```bash
pip install git+https://github.com/opensourceclaw/claw-mem.git
```
## Quick Start
### Search Memory
```bash
python3 {baseDir}/scripts/search.py "project deadlines" --limit 10
```
### Store Memory
```bash
python3 {baseDir}/scripts/store.py "User preference" --category preference
```
### Start Bridge (for OpenClaw integration)
```bash
python3 {baseDir}/scripts/bridge.py --workspace ~/.openclaw/workspace
```
## Memory Layers
| Layer | Purpose | Example |
|-------|---------|---------|
| **Episodic** | Event sequences | Session context |
| **Semantic** | Knowledge facts | User preferences |
| **Procedural** | Rules | Best practices |
## Retrieval Modes
| Mode | Description | Use Case |
|------|-------------|----------|
| `keyword` | Simple keyword match | Quick lookup |
| `bm25` | BM25 ranking | Relevance ranking |
| `hybrid` | BM25 + Keyword | Balanced retrieval |
| `enhanced_smart` | Full pipeline | Best quality |
Set mode via environment:
```bash
export CLAW_MEM_SEARCH_MODE=enhanced_smart
```
## Configuration
OpenClaw config (`openclaw.config.json`):
```json
{
"plugins": {
"slots": {
"memory": "claw-mem"
},
"claw-mem": {
"config": {
"workspaceDir": "~/.openclaw/workspace",
"autoRecall": true,
"autoCapture": true,
"topK": 10
}
}
}
}
```
## Performance
| Operation | Latency |
|-----------|---------|
| Initialize | ~4ms |
| Store | ~8ms |
| Search | ~5ms |
| **Average** | **~6ms** |
## Advanced
See references for detailed documentation:
- [Architecture](references/architecture.md) - Three-tier design
- [Retrieval](references/retrieval.md) - Search algorithms
FILE:references/architecture.md
# Three-Tier Memory Architecture
## Overview
claw-mem implements a three-tier memory architecture inspired by human memory:
1. **Episodic Memory** - Event sequences and experiences
2. **Semantic Memory** - Facts and knowledge
3. **Procedural Memory** - Rules and procedures
## Layer Details
### Episodic (L1)
- Stores event sequences
- Time-ordered
- Auto-expires after TTL
- Used for: session context, recent interactions
### Semantic (L2)
- Stores facts and knowledge
- Importance-scored
- Persistent across sessions
- Used for: user preferences, project facts
### Procedural (L3)
- Stores rules and procedures
- Rule extraction from patterns
- Used for: workflows, best practices
## Storage
All layers use SQLite for persistence:
- `episodic.db` - Episodic events
- `semantic.db` - Semantic knowledge
- `procedural.db` - Procedural rules
## Working Memory
In-memory cache for fast access:
- LRU eviction
- TTL-based expiration
- Max 100 items by default
FILE:references/retrieval.md
# Retrieval Algorithms
## Search Pipeline
```
Query → Keyword Filter → BM25 Ranking → Entity Enhancement → Heuristic Scoring → Results
```
## Retrievers
### KeywordRetriever
- Simple substring match
- Fast, O(n)
- Good for exact matches
### BM25Retriever
- Okapi BM25 ranking
- Term frequency + inverse document frequency
- Good for relevance ranking
### HybridBM25Retriever
- Combines keyword + BM25
- Balanced precision/recall
### EntityEnhancedRetriever
- Named entity recognition
- Boosts results with matching entities
- Requires spaCy (optional)
### HeuristicRetriever
- Recency boost
- Importance scoring
- Context matching
### SmartRetriever
- Combines all retrievers
- Weighted scoring
- Best overall quality
## Performance
| Retriever | Latency | Quality |
|-----------|---------|---------|
| keyword | ~1ms | Basic |
| bm25 | ~3ms | Good |
| hybrid | ~4ms | Better |
| entity | ~8ms | Good (with entities) |
| heuristic | ~5ms | Very Good |
| enhanced_smart | ~6ms | Best |
FILE:scripts/bridge.py
#!/usr/bin/env python3
"""Start claw-mem JSON-RPC bridge"""
import argparse
def main():
parser = argparse.ArgumentParser(description="Start claw-mem bridge")
parser.add_argument("--workspace", "-w", default="~/.openclaw/workspace", help="Workspace path")
args = parser.parse_args()
try:
from claw_mem.bridge import run_bridge
except ImportError:
print("❌ claw-mem not installed. Run: pip install claw-mem")
return
print(f"🧠 Starting claw-mem bridge for {args.workspace}")
run_bridge(workspace=args.workspace)
if __name__ == "__main__":
main()
FILE:scripts/search.py
#!/usr/bin/env python3
"""Search claw-mem memory store"""
import sys
import argparse
def main():
parser = argparse.ArgumentParser(description="Search claw-mem")
parser.add_argument("query", help="Search query")
parser.add_argument("--limit", "-n", type=int, default=10, help="Max results")
parser.add_argument("--mode", choices=["keyword", "bm25", "hybrid", "entity", "heuristic", "smart", "enhanced_smart"], default="enhanced_smart", help="Search mode")
parser.add_argument("--workspace", "-w", default="~/.openclaw/workspace", help="Workspace path")
args = parser.parse_args()
try:
from claw_mem import MemoryManager
except ImportError:
print("❌ claw-mem not installed. Run: pip install claw-mem")
sys.exit(1)
manager = MemoryManager(workspace=args.workspace)
# Set search mode
import os
os.environ['CLAW_MEM_SEARCH_MODE'] = args.mode
results = manager.search(args.query, top_k=args.limit)
if not results:
print("No results found")
return
for i, r in enumerate(results, 1):
layer = r.get('layer', 'unknown')
content = r.get('content', '')[:100]
score = r.get('score', 0)
print(f"{i}. [{layer}] {content}...")
if score:
print(f" Score: {score:.3f}")
if __name__ == "__main__":
main()
FILE:scripts/store.py
#!/usr/bin/env python3
"""Store memory in claw-mem"""
import sys
import argparse
def main():
parser = argparse.ArgumentParser(description="Store in claw-mem")
parser.add_argument("text", help="Memory text")
parser.add_argument("--category", "-c", default="general", help="Memory category")
parser.add_argument("--layer", choices=["episodic", "semantic", "procedural"], default="semantic", help="Memory layer")
parser.add_argument("--workspace", "-w", default="~/.openclaw/workspace", help="Workspace path")
args = parser.parse_args()
try:
from claw_mem import MemoryManager
except ImportError:
print("❌ claw-mem not installed. Run: pip install claw-mem")
sys.exit(1)
manager = MemoryManager(workspace=args.workspace)
manager.store(args.text, layer=args.layer, metadata={"category": args.category})
print(f"✅ Stored in {args.layer} layer: {args.text[:50]}...")
if __name__ == "__main__":
main()