OpenClaw Performance Optimization Tips
OpenClaw performance optimization techniques to make your AI agent faster: DuckDB query tuning, skill loading, session management, and caching strategies.
OpenClaw performance comes down to a few things: how fast the agent can think (LLM latency), how fast it can act (tool call speed), and how much work it does unnecessarily (efficiency). You can't change LLM latency much, but the other two are very much in your control. This guide covers the practical optimization techniques that make the biggest difference in day-to-day DenchClaw use.
New to DenchClaw? See what DenchClaw is and follow the setup guide first.
Profiling Before Optimizing#
Before tuning anything, know where time is actually being spent. DenchClaw's JSONL logs include durationMs for every tool call:
# Find the slowest tool calls from recent sessions
cat ~/.openclaw-dench/workspace/.openclaw/logs/*.jsonl \
| jq 'select(.durationMs > 500) | {tool, durationMs, input}' \
| jq -s 'sort_by(-.durationMs) | .[0:20]'Most performance problems fall into one of these categories:
- Slow DuckDB queries — missing indexes, full table scans, inefficient JOINs
- Large context windows — too much history/skill content being sent to the LLM
- Slow external API calls — network latency, missing caching
- Sequential tool calls that could be parallel — doing things one by one when they could be concurrent
DuckDB Query Optimization#
DenchClaw uses DuckDB for all local data storage. DuckDB is fast by default, but there are several things that slow it down.
Add indexes for frequent filters#
The default EAV schema uses generic tables. For frequently queried columns, add indexes:
-- Check what queries are slow first
EXPLAIN ANALYZE
SELECT * FROM objects WHERE type = 'contact' AND status = 'active';
-- Add indexes for frequent filters
CREATE INDEX IF NOT EXISTS idx_objects_type ON objects(type);
CREATE INDEX IF NOT EXISTS idx_objects_status ON objects(status);
CREATE INDEX IF NOT EXISTS idx_entries_field_id ON entry_fields(field_id);
CREATE INDEX IF NOT EXISTS idx_entries_object_id ON entry_fields(object_id);Avoid SELECT *#
The EAV schema stores values in a generic entry_fields table. SELECT * on views that join many tables is slow. Be specific:
-- Slow: joins everything
SELECT * FROM v_contacts;
-- Fast: only what you need
SELECT
o.id,
o.name,
ef.value as email
FROM objects o
JOIN entry_fields ef ON ef.object_id = o.id
JOIN fields f ON f.id = ef.field_id AND f.name = 'email'
WHERE o.type = 'contact'
AND o.status = 'active'
LIMIT 100;Use PIVOT views efficiently#
DenchClaw's PIVOT views (prefixed v_) are pre-built for convenience but can be slow on large datasets. For queries over more than ~10,000 records, query the underlying tables directly.
Cache slow queries#
For reports that run the same heavy query repeatedly:
-- Create a materialized summary table
CREATE TABLE contact_summary AS
SELECT
o.id,
o.name,
o.status,
o.created_at,
MAX(CASE WHEN f.name = 'email' THEN ef.value END) as email,
MAX(CASE WHEN f.name = 'company' THEN ef.value END) as company
FROM objects o
JOIN entry_fields ef ON ef.object_id = o.id
JOIN fields f ON f.id = ef.field_id
WHERE o.type = 'contact'
GROUP BY o.id, o.name, o.status, o.created_at;
CREATE INDEX idx_contact_summary_email ON contact_summary(email);Refresh this table nightly via a heartbeat task or cron job.
DuckDB configuration tuning#
For machines with more RAM, increase DuckDB's memory allocation:
-- Set in your startup or session
SET memory_limit = '4GB';
SET threads = 4;Adjust to match your machine's resources. DuckDB defaults to conservative limits.
Reducing LLM Context Size#
Every byte you add to the LLM context costs latency and tokens. The biggest wins:
1. Trim conversation history#
Long conversations accumulate. After 20+ turns, the history adds thousands of tokens to every call. Use session summarization:
When a session gets long, ask the agent:
Summarize this conversation so far into a compact context note,
then we'll start a fresh session.
Or configure the agent to auto-summarize after N turns.
2. Load skills on demand#
Each skill file adds tokens to the context. Don't have all skills loaded simultaneously:
# Instead of loading everything upfront:
Load the CRM skill and check my contacts.
# Rather than a startup config that loads all skills
3. Limit tool output size#
When reading files or querying APIs, cap the output:
Read the first 100 lines of the log file only.
Return only the 10 most recent records.
Summarize the page content instead of returning the full HTML.
Add these patterns to your skill files:
## Output Size Guidelines
- File reads: limit to 200 lines unless the full file is specifically needed
- Database queries: add LIMIT 100 unless all records are required
- API responses: extract only the relevant fields, not the full object
- Web page content: summarize rather than return full text4. Use compact data formats#
When the agent needs to pass data between steps, use compact representations:
# Verbose (wastes tokens)
"The customer's name is John Smith, their email is john@example.com,
their status is Active, they signed up on..."
# Compact (same information)
{id: "123", name: "John Smith", email: "john@example.com", status: "active"}
Parallel Tool Execution#
OpenClaw can execute independent tool calls in parallel within a single agent session. The key is structuring your requests to enable this.
Bad: Sequential when parallel is possible#
# This forces sequential execution
1. Look up the contact for email@example.com
2. Look up their company
3. Check their Stripe subscription
4. Get their recent GitHub activity
Good: Explicit parallelism#
In parallel, look up:
- The contact record for email@example.com
- Their Stripe subscription status
- Their recent GitHub activity
Then combine the results.
The agent will spawn concurrent tool calls for the independent lookups, completing the task in the time of the single slowest call rather than the sum of all calls.
Caching External API Results#
External API calls (Stripe, GitHub, Notion, etc.) have network latency. Cache results when the data doesn't change frequently:
File-based cache#
Add this pattern to your skills:
# Generic cache function
cached_fetch() {
local cache_key=$1
local ttl_minutes=${2:-60}
local cache_file="/tmp/openclaw-cache-${cache_key}.json"
if [ -f "$cache_file" ]; then
# Check if cache is still valid
local age_minutes=$(( ($(date +%s) - $(stat -f %m "$cache_file" 2>/dev/null || stat -c %Y "$cache_file")) / 60 ))
if [ $age_minutes -lt $ttl_minutes ]; then
cat "$cache_file"
return 0
fi
fi
# Cache miss: fetch fresh data
return 1
}DuckDB-based cache#
For structured data, use DuckDB as a cache:
-- Cache table with TTL
CREATE TABLE IF NOT EXISTS api_cache (
cache_key VARCHAR PRIMARY KEY,
data JSON,
fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ttl_minutes INTEGER DEFAULT 60
);
-- Check cache before fetching
SELECT data FROM api_cache
WHERE cache_key = 'stripe_customers_page_1'
AND fetched_at > CURRENT_TIMESTAMP - INTERVAL (ttl_minutes || ' minutes');
-- Store fetched data
INSERT OR REPLACE INTO api_cache (cache_key, data, ttl_minutes)
VALUES ('stripe_customers_page_1', '{"data": [...]}', 60);Skill Loading Performance#
Skills are read from disk on each session. Keep skills lean:
Keep skill files focused#
A skill file should cover one integration or capability, not everything. A 5,000-word skill file adds latency and confusion. Keep skills under 1,000 words for common operations, with a pointer to detail files for edge cases:
# Stripe Skill (Core)
For common operations, use these patterns...
For advanced operations (bulk sync, webhook handling), see:
- [stripe-bulk-ops reference](./references/stripe-bulk-ops.md)
- [stripe-webhooks reference](./references/stripe-webhooks.md)The agent only loads the main skill file into context. References are loaded on demand.
Organize with a skill index#
If you have many skills, create a skill index so the agent knows what's available without loading everything:
# Skills Index
Available skills (load on demand):
- crm: Contact management, pipeline, DuckDB queries
- stripe: Payments, invoices, subscriptions
- notion: Workspace sync, page creation, database queries
- github: Issues, PRs, repository management
- email: Gmail/IMAP access via himalayaPut this index in a small file that gets loaded each session, so the agent knows what's available without reading every skill.
System-Level Optimization#
Run DuckDB on an SSD#
DuckDB performance degrades significantly on spinning disks or slow network storage. Keep your workspace on a local SSD. For remote deployments, a local NVMe SSD on the host is essential.
Node.js memory configuration#
The OpenClaw gateway is a Node.js process. On machines with plenty of RAM, increase the V8 heap:
# In your startup script or systemd environment
NODE_OPTIONS="--max-old-space-size=4096" openclaw gateway startKeep the gateway process warm#
If you start and stop the gateway frequently, there's startup latency each time. For day-to-day use, keep it running as a background service:
# macOS: add to Launch Agents
openclaw gateway startA warm gateway responds to the first request instantly rather than spending 2-3 seconds on startup.
Measuring the Impact#
After making changes, measure the improvement:
# Before: capture baseline performance
cat ~/.openclaw-dench/workspace/.openclaw/logs/*.jsonl \
| jq 'select(.tool == "exec") | .durationMs' \
| awk '{sum += $1; count++} END {print "avg:", sum/count, "count:", count}'
# Make your changes
# After: compare
cat ~/.openclaw-dench/workspace/.openclaw/logs/*.jsonl \
| jq 'select(.tool == "exec") | .durationMs' \
| awk '{sum += $1; count++} END {print "avg:", sum/count, "count:", count}'For LLM latency, track response times in DuckDB using the audit log data from the audit logs guide.
FAQ#
Q: What's the single biggest performance improvement I can make?
A: Add indexes to your DuckDB tables. If you're querying more than a few thousand records, a missing index can make queries 100x slower than they need to be. Check EXPLAIN ANALYZE on your slowest queries.
Q: Does conversation length really affect performance that much? A: Yes. A 100-turn conversation might add 20,000+ tokens to every subsequent LLM call. That adds latency and cost. Fresh sessions for new tasks is a good habit.
Q: Is DuckDB fast enough for large datasets? A: Very fast. DuckDB is a columnar analytical database designed for this kind of workload. Millions of rows are handled without issue. The bottleneck is usually missing indexes or SELECT * queries, not DuckDB itself.
Q: Can I run the gateway on a separate server from the frontend? A: The gateway and frontend are designed to run on the same host. Separating them adds network latency between them and complicates configuration.
Q: How much RAM does DenchClaw need for good performance? A: 4GB is workable, 8GB is comfortable, 16GB lets you run heavier workloads without hitting swap. The main memory consumer is DuckDB's working set and the Node.js process.
Ready to try DenchClaw? Install in one command: npx denchclaw. Full setup guide →
