| name | openclawn-agent-framework |
| description | Use OpenCLAWN, a self-improving multi-agent framework with routing audit, skill decay, confidence-gated learning, and 26 sandboxed tools for code, data, docs, git, and web. |
| triggers | ["set up openclawn agent framework","create self-improving ai agent with openclawn","configure openclawn multi-agent conversation","use openclawn skill crystallization","implement openclawn autopilot with approval gate","debug openclawn routing decisions","export import openclawn skill packs","add custom tool to openclawn agent"] |
OpenCLAWN Agent Framework
Skill by ara.so — Hermes Skills collection.
OpenCLAWN is a lightweight, self-improving multi-agent framework built around 4 core innovations: routing audit + self-calibration, skill decay, confidence-gated crystallization, and role output contracts. It features hybrid local (Ollama) + cloud (Gemini/Claude) LLM routing, 26 sandboxed tools, and a compounding skill library that tidies and improves itself as it's used.
Key capabilities:
- Multi-agent conversations (pipeline, debate, orchestrator strategies)
- Self-calibrating smart router with multilingual complexity detection
- Skill compounding: promote, refine, merge, decay — all versioned & revertible
- Autopilots with approval-gated proposals (no silent execution)
- Activity timeline tracking every agent action
- Skill pack export/import with SSRF + injection guards
Installation
Prerequisites
- Python 3.12+
- Docker (for sandboxed tool execution)
- Ollama installed and running (for local models)
- API keys for Gemini and/or Claude (optional, for heavy tiers)
Quick Setup
git clone https://github.com/MuhammadHasbiAshshiddieqy/OpenClawn.git
cd OpenClawn
uv sync --frozen --extra dev
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
mkdir -p data
sqlite3 data/openclawn.db < migrations/001_initial.sql
ollama pull gemma4:e2b
ollama pull gemma4:e4b
ollama pull gemma4:12b
docker build -t openclawn-sandbox:latest -f Dockerfile.sandbox .
uvicorn web.main:app --reload --port 8000
Access the UI at http://localhost:8000.
Architecture Overview
Core Components
-
SmartRouter — Innovation #1: Routing audit + self-calibration
- Scores queries on 10 dimensions (code, math, reasoning, etc.)
- Labels complexity: TRIVIAL → SIMPLE → MODERATE → COMPLEX → CRITICAL
- Logs every decision for later calibration
- Multilingual support with optional script-aware tier bumps
-
SkillDecay — Innovation #2: Skill lifecycle management
- Skills scored 0–1 based on age, usage, success rate
- Decay passes run hourly (throttled)
- Skills below 0.3 score excluded from context
-
Crystallizer — Innovation #3: Confidence-gated skill storage
- Captures multi-tool solutions as reusable skills
- Self-evaluates with confidence score (1–5)
- Only stores skills with confidence ≥4
- Uses evaluator tier ≥ generator tier
-
RoleNegotiator — Innovation #4: Typed multi-agent contracts
- Validates handoffs between roles (PM → Dev → QA)
- Ensures output contracts are met before handoff
- Prevents fragile multi-agent communication
-
SkillCurator — Compounding layer (I1)
- Merges duplicate skills
- Deduplicates based on semantic similarity
- Requires judge tier ≥4, revertible
-
SkillFeedback — Compounding layer (I2/I3)
- Promotes draft skills on success
- Refines skills on correction
- Revives decayed skills on proven re-use
Basic Usage
Single Agent Chat
from agent.agent import Agent
from core.llm_client import LLMClient
from core.memory_manager import MemoryManager
from core.smart_router import SmartRouter
from core.routing_auditor import RoutingAuditor
llm_client = LLMClient()
memory_manager = MemoryManager(agent_id="agent_1")
router = SmartRouter()
auditor = RoutingAuditor()
agent = Agent(
llm_client=llm_client,
memory_manager=memory_manager,
router=router,
auditor=auditor,
agent_id="agent_1"
)
async for chunk in agent.process_query(
query="Write a Python script to parse CSV and export to JSON",
stream=True
):
print(chunk, end="", flush=True)
Multi-Agent Conversation (Pipeline)
from conversation.orchestrator import ConversationOrchestrator
from conversation.strategies import PipelineStrategy
roles = [
{
"name": "product_manager",
"system_prompt": "You are a product manager. Define requirements.",
"output_contract": {
"required_fields": ["requirements", "acceptance_criteria"],
"format": "markdown"
}
},
{
"name": "developer",
"system_prompt": "You are a developer. Implement the solution.",
"output_contract": {
"required_fields": ["implementation", "tests"],
"format": "code"
}
},
{
"name": "qa_engineer",
"system_prompt": "You are a QA engineer. Test the solution.",
"output_contract": {
"required_fields": ["test_results", "bugs_found"],
"format": "markdown"
}
}
]
orchestrator = ConversationOrchestrator(
strategy=PipelineStrategy(),
roles=roles
)
result = await orchestrator.run_conversation(
initial_prompt="Build a user authentication system"
)
Autopilot with Approval Gates
from autopilots.scheduler import AutopilotScheduler
from autopilots.autopilot import Autopilot
autopilot = Autopilot(
name="daily_security_scan",
schedule="0 9 * * *",
prompt="Scan the codebase for security vulnerabilities and report findings",
agent_id="security_agent",
require_approval=True
)
scheduler = AutopilotScheduler()
await scheduler.add_autopilot(autopilot)
await scheduler.start()
Configuration
Router Tier Mapping (/router endpoint or config file)
tiers:
trivial:
local: "gemma4:e2b"
fallback: ["gemini-2.5-flash"]
simple:
local: "gemma4:e4b"
fallback: ["gemini-2.5-flash"]
moderate:
local: "gemma4:12b"
fallback: ["gemini-2.5-pro"]
complex:
cloud: "gemini-2.5-pro"
fallback: ["claude-3-7-sonnet"]
critical:
cloud: "claude-3-7-sonnet"
fallback: ["gemini-2.5-pro-exp-03"]
Soul Configuration (soul.toml)
[identity]
name = "DevBot"
role = "Senior Full-Stack Developer"
tone = "professional, helpful"
[routing_hints]
upgrade_keywords = [
"production",
"critical bug",
"security",
"performance optimization",
"database migration"
]
prefer_local = true
[memory]
archive_threshold = 50
Environment Variables
GEMINI_API_KEY=your_gemini_api_key
ANTHROPIC_API_KEY=your_anthropic_api_key
OPENCLAWN_WORKSPACE_PATH=./workspace
OPENCLAWN_DB_PATH=./data/openclawn.db
OLLAMA_BASE_URL=http://localhost:11434
OPENCLAWN_PREFER_LOCAL=true
OPENCLAWN_LANGUAGE_AWARE_ROUTING=true
OPENCLAWN_SKILL_DECAY_ENABLED=true
OPENCLAWN_CRYSTALLIZATION_MIN_CONFIDENCE=4
OPENCLAWN_AUTOPILOT_APPROVAL_TIMEOUT=3600
OPENCLAWN_AUTO_CALIBRATION=false
Tools
OpenCLAWN includes 26 sandboxed tools, all workspace-bounded:
Filesystem Tools
await agent.process_query("Read the contents of src/main.py")
await agent.process_query("Write 'Hello, World!' to output.txt")
await agent.process_query(
"In config.yaml, replace line 5 with 'debug: true'"
)
await agent.process_query("""
Apply this patch to api.py:
--- a/api.py
+++ b/api.py
@@ -10,7 +10,7 @@
-DEBUG = False
+DEBUG = True
""")
await agent.process_query("Find all Python files in the tests directory")
await agent.process_query("Search for 'TODO' in all JavaScript files")
await agent.process_query("Read all config files: .env, config.yaml, settings.json")
Execution Tools (Sandboxed)
await agent.process_query("""
Run this Python code:
import json
data = {"name": "test", "value": 42}
print(json.dumps(data, indent=2))
""")
await agent.process_query("Run: ls -lah /workspace")
Sandbox specs:
- Docker container with
network=none
- Read-only workspace mount
- Non-root user
- 30-second timeout
- Stdout/stderr captured
Network Tools (SSRF-guarded)
await agent.process_query("Fetch the content of https://example.com")
await agent.process_query("Search the web for 'Python async best practices'")
await agent.process_query("""
Make a POST request to https://api.example.com/data
Headers: {"Authorization": "Bearer ${API_TOKEN}"}
Body: {"query": "test"}
""")
SSRF protections:
- Blocks private IPs (127.0.0.0/8, 192.168.0.0/16, etc.)
- Blocks cloud metadata endpoints
- DNS rebinding protection
Data & Document Tools
await agent.process_query("Query the users table: SELECT * FROM users LIMIT 10")
await agent.process_query("""
Query data.json with: users[?age > `25`].{name: name, email: email}
""")
await agent.process_query("Extract text from report.pdf")
await agent.process_query("Write a report to docs/summary.md with this content: ...")
await agent.process_query("Convert docs/summary.md to PDF at reports/summary.pdf")
Development Tools
await agent.process_query("Show git status")
await agent.process_query("Show diff for src/main.py")
await agent.process_query("Show last 5 commits")
await agent.process_query("Add TODO: Refactor auth module")
await agent.process_query("""
Report blocker: Database migration failed
Context: PostgreSQL version mismatch
Needs: Manual intervention
""")
Skill Management
Viewing Skills
import httpx
async with httpx.AsyncClient() as client:
resp = await client.get("http://localhost:8000/api/skills")
skills = resp.json()
for skill in skills:
print(f"{skill['name']} (score: {skill['decay_score']:.2f})")
Exporting Skill Pack
{
"min_score": 0.5,
"include_drafts": false
}
Importing Skill Pack
Security checks on import:
- NFKD normalization scan for homograph attacks
- Injection pattern detection
- SSRF guard for any embedded URLs
- Checksum verification
Routing Calibration
View Calibration Dashboard
Navigate to http://localhost:8000/metrics to see:
- Decision distribution (pie chart)
- Accuracy trends (over time)
- Per-tier precision/recall
- Suggested offsets for each complexity label
Manual Calibration
{
"label": "moderate",
"offset": -0.5
}
{
"label": "moderate"
}
Auto-Calibration (Opt-In)
OPENCLAWN_AUTO_CALIBRATION=true
How it works:
- Auditor logs every routing decision with 10 dimension scores
- User feedback (implicit: correction, explicit: rating) marks decisions
- Calibration service analyzes misrouted queries
- Suggests offset adjustments to improve accuracy
- Auto-apply (if enabled) or manual review
User Modeling (Opt-In, Innovation #5)
OPENCLAWN_USER_MODELING=true
Tracks dialectic patterns:
- Preferred reasoning style (Socratic, direct, exploratory)
- Common correction patterns
- Domain expertise signals
- Interaction rhythm
Privacy:
- Opt-in only
- Versioned (revertible)
- Stored locally in SQLite
- Never sent to LLM providers
- Used only for context shaping
{
"reasoning_preference": "socratic",
"expertise_domains": ["python", "async", "databases"],
"interaction_rhythm": "detailed",
"correction_patterns": [
"prefers_explicit_types",
"values_error_handling"
]
}
Common Patterns
Pattern: Multi-Step Workflow with Skill Crystallization
query = """
1. Read requirements.txt
2. Check for outdated dependencies using pip list --outdated
3. Update requirements.txt with new versions
4. Run tests to verify compatibility
5. Commit changes if tests pass
"""
async for chunk in agent.process_query(query, stream=True):
print(chunk, end="", flush=True)
Pattern: Approval-Gated Autopilot
autopilot = Autopilot(
name="weekly_cleanup",
schedule="0 0 * * 0",
prompt="""
1. Find all .log files older than 30 days
2. Create archive of old logs
3. Delete archived logs (REQUIRES APPROVAL)
4. Report disk space saved
""",
agent_id="maintenance_agent",
require_approval=True
)
Pattern: Multi-Agent Debate
from conversation.strategies import DebateStrategy
roles = [
{
"name": "architect",
"system_prompt": "You advocate for clean architecture and SOLID principles."
},
{
"name": "pragmatist",
"system_prompt": "You advocate for shipping fast and iterating."
},
{
"name": "security_expert",
"system_prompt": "You advocate for security-first design."
}
]
orchestrator = ConversationOrchestrator(
strategy=DebateStrategy(rounds=3),
roles=roles
)
result = await orchestrator.run_conversation(
initial_prompt="Design a user authentication system for a new SaaS product"
)
Pattern: Context-Aware Skill Refinement
Troubleshooting
Routing Always Uses Same Model
Problem: Router ignores complexity and always routes to one model.
Solution:
- Check
/settings for active override — disable it
- Review
soul.toml — if prefer_local=true and all queries score low, will stay local
- Check calibration offsets in
/metrics — large negative offsets raise thresholds
- Verify Ollama is running:
curl http://localhost:11434/api/tags
Skills Not Being Crystallized
Problem: Agent completes multi-tool tasks but no skills are created.
Checklist:
- Minimum 3 tool calls required
- Confidence must be ≥4 (self-evaluation)
- Evaluator tier must be ≥ generator tier
- Check logs for "critical gaps" in solution
- Verify
OPENCLAWN_CRYSTALLIZATION_MIN_CONFIDENCE in .env
Sandbox Tools Failing
Problem: code_run or shell_run returns errors.
Solutions:
docker ps
docker build -t openclawn-sandbox:latest -f Dockerfile.sandbox .
docker logs <container_id>
docker run --rm \
--network none \
-v $(pwd)/workspace:/workspace:ro \
openclawn-sandbox:latest \
python3 -c "print('Hello')"
Memory Context Too Large
Problem: Queries fail with token limit errors.
Solutions:
- Reduce
archive_threshold in soul.toml (archives older messages to FTS5)
- Manually archive old messages:
POST /api/memory/archive
- Increase model's context window in router config
- Enable
ContextCompactor budget limits in agent config:
agent = Agent(
...,
context_budget=8000
)
Autopilot Proposals Not Appearing
Problem: Autopilot runs but no proposals in UI.
Check:
- Verify
require_approval=True on autopilot
- Check autopilot logs:
GET /api/autopilots/{id}/logs
- Ensure action tools are marked
requires_approval=True:
TOOL_REGISTRY = {
"file_write": {
"requires_approval": True,
...
}
}
Skill Decay Too Aggressive
Problem: Useful skills dropping below 0.3 threshold too quickly.
Solutions:
- Adjust decay rate in config:
decay:
age_weight: 0.2
usage_weight: 0.4
success_weight: 0.4
base_rate: 0.05
- Manually boost skill score:
POST /api/skills/{id}/boost
- Mark skill as "pinned" (never decays):
POST /api/skills/{id}/pin
Web UI Not Loading
Problem: Blank page or 404 errors.
Solutions:
curl http://localhost:8000/health
ls web/static/
cd web && npm run build
uvicorn web.main:app --reload --log-level debug
API Reference (Key Endpoints)
### Chat
POST /api/chat/stream
Content-Type: application/json
{
"message": "Write a function to parse JSON",
"agent_id": "default",
"stream": true
}
### Multi-Agent Conversation
POST /api/converse/stream
{
"prompt": "Build user auth system",
"strategy": "pipeline", # or "debate" or "orchestrator"
"roles": [...]
}
### Skills
GET /api/skills?min_score=0.5&status=active
POST /api/skills/export
POST /api/skills/import
POST /api/skills/{id}/promote
POST /api/skills/{id}/pin
DELETE /api/skills/{id} # Soft delete (revertible)
### Autopilots
GET /api/autopilots
POST /api/autopilots
GET /api/autopilots/{id}/proposals
POST /api/autopilots/proposals/{id}/approve
POST /api/autopilots/proposals/{id}/reject
### Routing
GET /api/router/tiers
POST /api/router/calibrate
POST /api/router/calibration/revert
GET /api/router/decisions?limit=100
### Memory
POST /api/memory/archive
GET /api/memory/search?q=authentication
DELETE /api/memory/clear # Requires confirmation
### Activity
GET /api/activity?limit=50&type=tool_call
GET /api/activity/blockers
Testing
pytest
pytest tests/test_router.py
pytest tests/test_crystallizer.py
pytest --cov=agent --cov=core --cov-report=html
pytest tests/integration/
pytest tests/test_sandbox.py -v
Advanced: Custom Tool Development
from pydantic import BaseModel, Field
from typing import Optional
class MyCustomToolInput(BaseModel):
"""Input schema for my custom tool."""
target: str = Field(..., description="Target identifier")
options: Optional[dict] = Field(default=None, description="Optional parameters")
async def my_custom_tool(
target: str,
options: Optional[dict] = None,
workspace_path: str = "./workspace"
) -> dict:
"""
Custom tool that does something useful.
Args:
target: Target identifier
options: Optional parameters
workspace_path: Workspace root (auto-injected)
Returns:
Result dict with 'success', 'output', 'error'
"""
try:
result = f"Processed {target}"
return {
"success": True,
"output": result,
"error": None
}
except Exception as e:
return {
"success": False,
"output": None,
"error": str(e)
}
TOOL_REGISTRY["my_custom_tool"] = {
"function": my_custom_tool,
"input_schema": MyCustomToolInput,
"description": "Does something useful with a target",
"requires_approval": False,
"allowed_roles": ["developer", "admin"]
}
Resources
Quick command reference:
uvicorn web.main:app --reload --port 8000
ollama pull gemma4:e4b
pytest
curl -X POST http://localhost:8000/api/skills/export
open http://localhost:8000/metrics
open http://localhost:8000/autopilots