OpenClaw Developer Guide
Everything you need to understand OpenClaw's internals, contribute code, use the Python API, and stay informed about releases.
Architecture at a Glance
OpenClaw follows a layered architecture that separates concerns cleanly, making every layer independently testable and replaceable. The Provider Abstraction normalises OpenAI, Anthropic, Groq, and Ollama behind a common BaseLLM interface. The Agent Core runs the think/act loop, managing iterations, tool dispatch, and memory hydration. The Tool Registry holds all built-in and custom tools discovered at startup. The Event Bus fires lifecycle hooks consumed by plugins and monitoring middleware.
Core Components
| Component | Module | Responsibility | Extension Point |
|---|---|---|---|
| Agent | openclaw.agent | Think/act loop, iteration cap, timeout | Subclass BaseAgent |
| Provider | openclaw.providers | LLM API abstraction, retry, streaming | Implement BaseLLM |
| Tool Registry | openclaw.tools | Tool discovery, schema generation, dispatch | @tool decorator |
| Memory | openclaw.memory | Short/long-term store, retrieval, eviction | Implement BaseMemory |
| Event Bus | openclaw.events | Lifecycle hooks, plugin communication | @on_event decorator |
| Config | openclaw.config | YAML/env merge, schema validation, defaults | Custom config sections |
Sections
Architecture Overview
Core components, agent execution lifecycle, provider abstraction, and extension points.
Read βContributing Guide
Fork, dev setup, code style, testing, PR process, and documentation contributions.
Read βBuilding from Source
Clone, install dev dependencies, build wheel, run in development mode.
Read βPython API Reference
Agent class, @tool decorator, Config, Memory, Event system, and exception types.
Read βRoadmap
Planned features, release timeline, community requests, and how to vote.
Read βChangelog
All releases from v0.1.0 to latest β new features, fixes, and breaking changes.
Read βCode of Conduct
Community standards, positive behaviors, enforcement, and reporting violations.
Read βQuick Start: Contributing
# 1. Fork and clone
git clone https://github.com/YOUR_USERNAME/openclaw
cd openclaw
# 2. Create virtual environment
python -m venv .venv && source .venv/bin/activate
# 3. Install in dev mode
pip install -e ".[dev]"
# 4. Install pre-commit hooks
pre-commit install
# 5. Run tests to verify setup
pytest tests/ -v --tb=shortPython API Quick Example
import openclaw
agent = openclaw.Agent(
provider="openai",
model="gpt-4o-mini",
tools=["web_search", "read_file"]
)
result = agent.run("Summarize the latest news on AI regulation")
print(result.output)Writing a Custom Tool
Tools are the atomic actions available to an agent. Use the @tool decorator to register any Python function as a tool. The decorator introspects the type hints and docstring to generate the JSON schema automatically β no manual schema writing required.
from openclaw.tools import tool
from typing import Optional
@tool
def fetch_stock_price(ticker: str, currency: str = "USD") -> dict:
"""Fetch the latest stock price for a given ticker symbol.
Args:
ticker: Stock ticker symbol, e.g. 'AAPL' or 'TSLA'.
currency: Currency code for the price. Defaults to 'USD'.
Returns:
dict with keys: ticker, price, currency, timestamp.
"""
# implementation here
import yfinance as yf
data = yf.Ticker(ticker).fast_info
return {
"ticker": ticker,
"price": data.last_price,
"currency": currency,
"timestamp": data.last_trade_date.isoformat(),
}Then pass it to the agent:
from your_tools import fetch_stock_price
agent = openclaw.Agent(
provider="openai",
model="gpt-4o",
tools=[fetch_stock_price, "web_search"] # mix custom and built-in
)
print(agent.run("What is today's Apple stock price?").output)Implementing a Custom LLM Provider
To add a provider not natively supported (e.g., a private LLM endpoint), subclass BaseLLM and implement the two required methods:
from openclaw.providers import BaseLLM, LLMResponse
from typing import Iterator
class MyPrivateLLM(BaseLLM):
"""Provider for an internal fine-tuned model behind a private API."""
def complete(self, prompt: str, **kwargs) -> LLMResponse:
response = self._call_internal_api(prompt, stream=False)
return LLMResponse(
content=response["text"],
tokens_used=response["usage"]["total_tokens"],
model=self.model_name,
)
def stream(self, prompt: str, **kwargs) -> Iterator[str]:
for chunk in self._call_internal_api(prompt, stream=True):
yield chunk["delta"]
def _call_internal_api(self, prompt: str, stream: bool) -> dict:
import requests
return requests.post(
self.config.endpoint,
json={"prompt": prompt, "stream": stream},
headers={"X-API-Key": self.config.api_key},
timeout=60,
).json()Plugin & Event System
OpenClaw's event bus fires hooks at every stage of an agent run. You can subscribe to events to add custom logging, cost tracking, human-in-the-loop checkpoints, or other middleware without touching core agent code.
| Event | Fired When | Payload Fields |
|---|---|---|
agent.started | Agent begins a task | agent_id, task, config |
agent.iteration | Each think/act loop iteration | iteration, thought, tool_call |
tool.called | Before a tool executes | tool_name, args, iteration |
tool.result | After a tool returns | tool_name, result, duration_ms |
llm.request | Before each LLM API call | prompt_tokens, model |
llm.response | After each LLM API response | completion_tokens, total_tokens, duration_ms |
agent.completed | Task finishes successfully | output, total_tokens, elapsed_s |
agent.failed | Task fails or times out | error, iteration, elapsed_s |
from openclaw.events import on_event
@on_event("llm.response")
def track_token_cost(event):
cost = event.total_tokens * 0.000002 # $0.002 / 1K tokens
print(f"Task {event.task_id}: ${cost:.4f} ({event.total_tokens} tokens)")Code Style & Convention
OpenClaw contributions must pass the CI linting gate. The toolchain is:
- Ruff β linting and import sorting (
ruff check . --fix) - Black β opinionated code formatting (
black src/ tests/) - Mypy β static type checking in strict mode (
mypy src/openclaw) - pre-commit β runs all checks automatically on each commit
All public functions and classes must have type annotations. Docstrings follow the Google style format. Commit messages follow Conventional Commits: feat:, fix:, docs:, chore:.
Pull Request Checklist
- β Tests added or updated for the changed code; coverage does not decrease
- β
ruff,black, andmypypass with zero errors - β
CHANGELOG.mdentry added under Unreleased - β
Documentation updated in
docs/for any public API change - β Breaking changes noted in PR description with migration path
- β PR description answers: What? / Why? / How tested?