Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ dist/
*/.DS_Store
*.DS_Store

node_modules/
333 changes: 180 additions & 153 deletions contextpilot/server/http_server.py

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions openclaw-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# @contextpilot/contextpilot

OpenClaw plugin for [ContextPilot](https://github.com/EfficientContext/ContextPilot) — faster long-context inference via in-process context optimization. **Zero external dependencies** — no Python, no proxy server, just install and go.

## What It Does

ContextPilot registers as an OpenClaw **Context Engine** and optimizes every LLM request by:

1. **Extracting** documents from tool results
2. **Reordering** documents for maximum prefix cache sharing across turns
3. **Deduplicating** repeated content blocks with compact reference hints
4. **Injecting** cache control markers (Anthropic `cache_control: { type: "ephemeral" }`)

All processing happens in-process — no external services needed.

## Installation

### From npm (when published)

```bash
openclaw plugins install @contextpilot/contextpilot
```

### From local path (development)

Add to `~/.openclaw/openclaw.json`:

```json
{
"plugins": {
"load": {
"paths": [
"/path/to/ContextPilot/openclaw-plugin"
]
}
}
}
```

## Configuration

In `~/.openclaw/openclaw.json`, enable the plugin and set it as the context engine:

```json
{
"plugins": {
"slots": {
"contextEngine": "contextpilot"
},
"entries": {
"contextpilot": {
"enabled": true,
"config": {
"scope": "all"
}
}
}
},
"tools": {
"allow": ["contextpilot"]
}
}
```

### Scope Options

| Scope | Tool Results | Description |
|:------|:------------:|:------------|
| `all` (default) | Optimized | Optimize all tool results |
| `tool_results` | Optimized | Same as `all` |

> **Note:** System prompt optimization is not currently available — OpenClaw's context engine API does not expose the system prompt to plugins.

## How It Works

```
OpenClaw agent request
ContextPilot Context Engine (assemble hook)
├─ Convert OpenClaw message format (toolResult → tool_result)
├─ Extract documents from tool results
├─ Reorder for prefix cache sharing
├─ Deduplicate repeated blocks
├─ Inject cache_control markers
Optimized context → LLM Backend
```

The plugin registers as an OpenClaw Context Engine using `api.registerContextEngine()`. The `assemble()` hook intercepts context assembly before each LLM call.

## Files

```
openclaw-plugin/
├── openclaw.plugin.json # Plugin manifest (id: "contextpilot")
├── package.json # npm package (@contextpilot/contextpilot)
├── src/
│ ├── index.ts # Plugin entry point
│ └── engine/
│ ├── cache-control.ts # Cache control injection
│ ├── dedup.ts # Content deduplication
│ ├── extract.ts # Document extraction
│ └── live-index.ts # Reordering engine
└── tsconfig.json
```

## Agent Tool

| Tool | Description |
|------|-------------|
| `contextpilot_status` | Check engine status, request count, and chars saved |

> **Note:** The status tool is registered but may not be visible to agents due to OpenClaw plugin API limitations.

## Verifying It Works

Check the gateway logs:

```
[ContextPilot] Stats: 5 requests, 28,356 chars saved (~7,089 tokens, ~$0.0213)
```

## Expected Savings

Savings depend on conversation length and repeated content:

| Scenario | Chars Saved | Token Reduction |
|:---------|------------:|----------------:|
| Short session (few tool calls) | 0-5K | ~0-5% |
| Medium session (10+ file reads) | 20-50K | ~10-20% |
| Long session (repeated large files) | 100K+ | ~30-50% |

Run `./benchmark.sh` to measure with/without comparison on your workload.

## License

Apache-2.0
176 changes: 176 additions & 0 deletions openclaw-plugin/benchmark.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#!/bin/bash
#
# ContextPilot Token Usage Benchmark
# Compares prefill/input tokens with and without the plugin
#

set -e

OPENCLAW_CONFIG="$HOME/.openclaw/openclaw.json"
BACKUP_CONFIG="$HOME/.openclaw/openclaw.json.bak"
LOG_WITH="/tmp/gw-with-cp.log"
LOG_WITHOUT="/tmp/gw-without-cp.log"

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEST_FILE="${SCRIPT_DIR}/src/engine/dedup.ts"

echo "=========================================="
echo "ContextPilot Token Usage Benchmark"
echo "=========================================="

# Backup config
cp "$OPENCLAW_CONFIG" "$BACKUP_CONFIG"

cleanup() {
echo ""
echo "Restoring config..."
cp "$BACKUP_CONFIG" "$OPENCLAW_CONFIG"
rm -f "$BACKUP_CONFIG"
openclaw gateway stop 2>/dev/null || pkill -f "openclaw gateway" 2>/dev/null || true
}
trap cleanup EXIT

enable_contextpilot() {
python3 << 'PYTHON'
import json, os
path = os.path.expanduser("~/.openclaw/openclaw.json")
with open(path) as f: c = json.load(f)
c.setdefault('plugins', {}).setdefault('slots', {})['contextEngine'] = 'contextpilot'
c['plugins'].setdefault('entries', {}).setdefault('contextpilot', {})['enabled'] = True
with open(path, 'w') as f: json.dump(c, f, indent=2)
PYTHON
}

disable_contextpilot() {
python3 << 'PYTHON'
import json, os
path = os.path.expanduser("~/.openclaw/openclaw.json")
with open(path) as f: c = json.load(f)
if 'plugins' in c:
c['plugins'].get('slots', {}).pop('contextEngine', None)
if 'contextpilot' in c['plugins'].get('entries', {}):
c['plugins']['entries']['contextpilot']['enabled'] = False
with open(path, 'w') as f: json.dump(c, f, indent=2)
PYTHON
}

restart_gateway() {
local logfile=$1
echo " Stopping gateway..."
openclaw gateway stop 2>/dev/null || true
pkill -f "openclaw gateway" 2>/dev/null || true
sleep 3
echo " Starting gateway..."
openclaw gateway > "$logfile" 2>&1 &
sleep 6
if ! pgrep -f "openclaw" > /dev/null; then
echo " ERROR: Gateway failed to start"
cat "$logfile" | tail -10
exit 1
fi
echo " Gateway running."
}

run_test_sequence() {
echo " Reading file 3 times to build up context..."
timeout 60 openclaw agent --agent main --message "Read $TEST_FILE and count functions" > /dev/null 2>&1 || true
timeout 60 openclaw agent --agent main --message "Read $TEST_FILE again" > /dev/null 2>&1 || true
timeout 60 openclaw agent --agent main --message "Read $TEST_FILE one more time and summarize" > /dev/null 2>&1 || true
echo " Done."
}

extract_last_usage() {
local logfile=$1
# Find the last complete usage block and extract values
local input=$(grep '"input":' "$logfile" 2>/dev/null | tail -1 | sed 's/[^0-9]//g' || echo "0")
local cache_read=$(grep '"cacheRead":' "$logfile" 2>/dev/null | tail -1 | sed 's/[^0-9]//g' || echo "0")
local cache_write=$(grep '"cacheWrite":' "$logfile" 2>/dev/null | tail -1 | sed 's/[^0-9]//g' || echo "0")
echo "$input $cache_read $cache_write"
}

extract_chars_saved() {
local logfile=$1
# Look for ContextPilot stats line
grep "Stats:" "$logfile" 2>/dev/null | tail -1 | sed -n 's/.*\([0-9][0-9,]*\) chars saved.*/\1/p' | tr -d ',' || echo "0"
}

# ==========================================
# Test WITH ContextPilot
# ==========================================
echo ""
echo "Test 1: WITH ContextPilot enabled"
echo "----------------------------------------"
enable_contextpilot
restart_gateway "$LOG_WITH"
run_test_sequence

WITH_USAGE=$(extract_last_usage "$LOG_WITH")
WITH_INPUT=$(echo "$WITH_USAGE" | cut -d' ' -f1)
WITH_CACHE_READ=$(echo "$WITH_USAGE" | cut -d' ' -f2)
WITH_CACHE_WRITE=$(echo "$WITH_USAGE" | cut -d' ' -f3)
WITH_CHARS=$(extract_chars_saved "$LOG_WITH")

echo ""
echo " Results:"
echo " Input tokens: $WITH_INPUT"
echo " Cache read: $WITH_CACHE_READ"
echo " Cache write: $WITH_CACHE_WRITE"
echo " Chars deduped: $WITH_CHARS"

# ==========================================
# Test WITHOUT ContextPilot
# ==========================================
echo ""
echo "Test 2: WITHOUT ContextPilot"
echo "----------------------------------------"
disable_contextpilot
restart_gateway "$LOG_WITHOUT"
run_test_sequence

WITHOUT_USAGE=$(extract_last_usage "$LOG_WITHOUT")
WITHOUT_INPUT=$(echo "$WITHOUT_USAGE" | cut -d' ' -f1)
WITHOUT_CACHE_READ=$(echo "$WITHOUT_USAGE" | cut -d' ' -f2)
WITHOUT_CACHE_WRITE=$(echo "$WITHOUT_USAGE" | cut -d' ' -f3)

echo ""
echo " Results:"
echo " Input tokens: $WITHOUT_INPUT"
echo " Cache read: $WITHOUT_CACHE_READ"
echo " Cache write: $WITHOUT_CACHE_WRITE"
echo " Chars deduped: 0 (plugin disabled)"

# ==========================================
# Summary
# ==========================================
echo ""
echo "=========================================="
echo "COMPARISON"
echo "=========================================="
echo ""
printf "%-20s %12s %12s\n" "" "WITH CP" "WITHOUT CP"
printf "%-20s %12s %12s\n" "--------------------" "------------" "------------"
printf "%-20s %12s %12s\n" "Input tokens" "$WITH_INPUT" "$WITHOUT_INPUT"
printf "%-20s %12s %12s\n" "Cache read" "$WITH_CACHE_READ" "$WITHOUT_CACHE_READ"
printf "%-20s %12s %12s\n" "Cache write" "$WITH_CACHE_WRITE" "$WITHOUT_CACHE_WRITE"
printf "%-20s %12s %12s\n" "Chars deduped" "$WITH_CHARS" "0"
echo ""

# Calculate differences
if [ "$WITH_INPUT" -gt 0 ] && [ "$WITHOUT_INPUT" -gt 0 ]; then
if [ "$WITH_INPUT" -lt "$WITHOUT_INPUT" ]; then
diff=$((WITHOUT_INPUT - WITH_INPUT))
pct=$((diff * 100 / WITHOUT_INPUT))
echo ">>> ContextPilot reduced input tokens by $diff ($pct% savings)"
elif [ "$WITH_INPUT" -gt "$WITHOUT_INPUT" ]; then
diff=$((WITH_INPUT - WITHOUT_INPUT))
pct=$((diff * 100 / WITHOUT_INPUT))
echo ">>> ContextPilot added $diff tokens ($pct% overhead)"
else
echo ">>> No difference in input tokens"
fi
fi

if [ "$WITH_CHARS" -gt 0 ]; then
tokens_saved=$((WITH_CHARS / 4))
echo ">>> Deduplication removed ~$tokens_saved tokens worth of repeated content"
fi
18 changes: 18 additions & 0 deletions openclaw-plugin/openclaw.plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"id": "contextpilot",
"name": "ContextPilot",
"description": "Faster long-context inference via context reuse — reorders, deduplicates, and injects cache control for maximum prefix cache sharing.",
"version": "0.2.0",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"scope": {
"type": "string",
"enum": ["all", "tool_results"],
"description": "Which messages ContextPilot optimizes",
"default": "all"
}
}
}
}
22 changes: 22 additions & 0 deletions openclaw-plugin/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions openclaw-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@contextpilot/contextpilot",
"version": "0.2.0",
"description": "ContextPilot plugin for OpenClaw — faster long-context inference via in-process context reuse. Zero external dependencies.",
"type": "module",
"license": "Apache-2.0",
"author": "ContextPilot Contributors",
"repository": {
"type": "git",
"url": "https://github.com/EfficientContext/ContextPilot.git",
"directory": "openclaw-plugin"
},
"keywords": [
"openclaw",
"openclaw-plugin",
"contextpilot",
"kv-cache",
"context-reuse",
"prompt-cache",
"dedup",
"llm"
],
"openclaw": {
"extensions": [
"./src/index.ts"
]
},
"files": [
"src/",
"openclaw.plugin.json",
"README.md"
],
"dependencies": {
"@sinclair/typebox": "^0.34.49"
}
}
Loading
Loading