@blockrun/clawrouter 0.12.63 → 0.12.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +55 -55
  2. package/dist/cli.js +50 -14
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +57 -16
  5. package/dist/index.js.map +1 -1
  6. package/docs/anthropic-cost-savings.md +90 -85
  7. package/docs/architecture.md +12 -12
  8. package/docs/{blog-openclaw-cost-overruns.md → clawrouter-cuts-llm-api-costs-500x.md} +27 -27
  9. package/docs/clawrouter-vs-openrouter-llm-routing-comparison.md +280 -0
  10. package/docs/configuration.md +2 -2
  11. package/docs/image-generation.md +39 -39
  12. package/docs/{blog-benchmark-2026-03.md → llm-router-benchmark-46-models-sub-1ms-routing.md} +61 -64
  13. package/docs/routing-profiles.md +6 -6
  14. package/docs/{technical-routing-2026-03.md → smart-llm-router-14-dimension-classifier.md} +29 -28
  15. package/docs/worker-network.md +438 -347
  16. package/package.json +3 -2
  17. package/scripts/reinstall.sh +31 -6
  18. package/scripts/update.sh +6 -1
  19. package/docs/assets/blockrun-248-day-cost-overrun-problem.png +0 -0
  20. package/docs/assets/blockrun-clawrouter-7-layer-token-compression-openclaw.png +0 -0
  21. package/docs/assets/blockrun-clawrouter-observation-compression-97-percent-token-savings.png +0 -0
  22. package/docs/assets/blockrun-clawrouter-openclaw-agentic-proxy-architecture.png +0 -0
  23. package/docs/assets/blockrun-clawrouter-openclaw-automatic-tier-routing-model-selection.png +0 -0
  24. package/docs/assets/blockrun-clawrouter-openclaw-error-classification-retry-storm-prevention.png +0 -0
  25. package/docs/assets/blockrun-clawrouter-openclaw-session-memory-journaling-vs-context-compounding.png +0 -0
  26. package/docs/assets/blockrun-clawrouter-vs-openclaw-standalone-comparison-production-safety.png +0 -0
  27. package/docs/assets/blockrun-clawrouter-x402-usdc-micropayment-wallet-budget-control.png +0 -0
  28. package/docs/assets/blockrun-openclaw-inference-layer-blind-spots.png +0 -0
  29. package/docs/plans/2026-02-03-smart-routing-design.md +0 -267
  30. package/docs/plans/2026-02-13-e2e-docker-deployment.md +0 -1260
  31. package/docs/plans/2026-02-28-worker-network.md +0 -947
  32. package/docs/plans/2026-03-18-error-classification.md +0 -574
  33. package/docs/plans/2026-03-19-exclude-models.md +0 -538
  34. package/docs/vs-openrouter.md +0 -157
@@ -1,538 +0,0 @@
1
- # Exclude Models Feature — Implementation Plan
2
-
3
- > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
-
5
- **Goal:** Let users exclude specific models from routing via `/exclude` Telegram command, persisted to disk.
6
-
7
- **Architecture:** New `exclude-models.json` file at `~/.openclaw/blockrun/` stores the exclusion list. A `filterByExcludeList()` function in `selector.ts` filters the fallback chain (same safety pattern as existing filters). The `/exclude` command manages the list via add/remove/clear subcommands. The proxy loads the list at startup and re-reads on each request (hot-reload).
8
-
9
- **Tech Stack:** TypeScript, Node.js fs, existing ClawRouter command pattern
10
-
11
- ---
12
-
13
- ### Task 1: Exclude List Persistence Module
14
-
15
- **Files:**
16
- - Create: `src/exclude-models.ts`
17
- - Test: `src/exclude-models.test.ts`
18
-
19
- **Step 1: Write the failing test**
20
-
21
- ```typescript
22
- // src/exclude-models.test.ts
23
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
24
- import { join } from "node:path";
25
- import { mkdirSync, rmSync, existsSync } from "node:fs";
26
- import { tmpdir } from "node:os";
27
- import {
28
- loadExcludeList,
29
- addExclusion,
30
- removeExclusion,
31
- clearExclusions,
32
- } from "./exclude-models.js";
33
-
34
- const TEST_DIR = join(tmpdir(), "clawrouter-test-exclude-" + Date.now());
35
- const TEST_FILE = join(TEST_DIR, "exclude-models.json");
36
-
37
- describe("exclude-models", () => {
38
- beforeEach(() => {
39
- mkdirSync(TEST_DIR, { recursive: true });
40
- });
41
-
42
- afterEach(() => {
43
- rmSync(TEST_DIR, { recursive: true, force: true });
44
- });
45
-
46
- it("returns empty set when file does not exist", () => {
47
- const list = loadExcludeList(TEST_FILE);
48
- expect(list.size).toBe(0);
49
- });
50
-
51
- it("adds a model and persists to disk", () => {
52
- addExclusion("nvidia/gpt-oss-120b", TEST_FILE);
53
- const list = loadExcludeList(TEST_FILE);
54
- expect(list.has("nvidia/gpt-oss-120b")).toBe(true);
55
- });
56
-
57
- it("removes a model", () => {
58
- addExclusion("nvidia/gpt-oss-120b", TEST_FILE);
59
- addExclusion("xai/grok-4-0709", TEST_FILE);
60
- removeExclusion("nvidia/gpt-oss-120b", TEST_FILE);
61
- const list = loadExcludeList(TEST_FILE);
62
- expect(list.has("nvidia/gpt-oss-120b")).toBe(false);
63
- expect(list.has("xai/grok-4-0709")).toBe(true);
64
- });
65
-
66
- it("clears all exclusions", () => {
67
- addExclusion("nvidia/gpt-oss-120b", TEST_FILE);
68
- addExclusion("xai/grok-4-0709", TEST_FILE);
69
- clearExclusions(TEST_FILE);
70
- const list = loadExcludeList(TEST_FILE);
71
- expect(list.size).toBe(0);
72
- });
73
-
74
- it("deduplicates entries", () => {
75
- addExclusion("nvidia/gpt-oss-120b", TEST_FILE);
76
- addExclusion("nvidia/gpt-oss-120b", TEST_FILE);
77
- const list = loadExcludeList(TEST_FILE);
78
- expect(list.size).toBe(1);
79
- });
80
-
81
- it("resolves aliases before storing", () => {
82
- // "free" alias → "nvidia/gpt-oss-120b"
83
- addExclusion("free", TEST_FILE);
84
- const list = loadExcludeList(TEST_FILE);
85
- expect(list.has("nvidia/gpt-oss-120b")).toBe(true);
86
- });
87
- });
88
- ```
89
-
90
- **Step 2: Run test to verify it fails**
91
-
92
- Run: `npx vitest run src/exclude-models.test.ts`
93
- Expected: FAIL — module `./exclude-models.js` does not exist
94
-
95
- **Step 3: Write minimal implementation**
96
-
97
- ```typescript
98
- // src/exclude-models.ts
99
- /**
100
- * Exclude Models — persistent user-configurable model exclusion list.
101
- *
102
- * Stores excluded model IDs in ~/.openclaw/blockrun/exclude-models.json.
103
- * Models in this list are filtered out of routing fallback chains.
104
- */
105
-
106
- import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
107
- import { dirname, join } from "node:path";
108
- import { homedir } from "node:os";
109
- import { resolveModelAlias } from "./models.js";
110
-
111
- const DEFAULT_EXCLUDE_FILE = join(homedir(), ".openclaw", "blockrun", "exclude-models.json");
112
-
113
- /**
114
- * Load the exclude list from disk. Returns empty Set if file missing.
115
- */
116
- export function loadExcludeList(filePath: string = DEFAULT_EXCLUDE_FILE): Set<string> {
117
- try {
118
- const raw = readFileSync(filePath, "utf-8");
119
- const arr = JSON.parse(raw);
120
- if (Array.isArray(arr)) return new Set(arr);
121
- return new Set();
122
- } catch {
123
- return new Set();
124
- }
125
- }
126
-
127
- function save(models: Set<string>, filePath: string): void {
128
- mkdirSync(dirname(filePath), { recursive: true });
129
- writeFileSync(filePath, JSON.stringify([...models].sort(), null, 2) + "\n");
130
- }
131
-
132
- /**
133
- * Add a model to the exclude list. Resolves aliases (e.g. "free" → "nvidia/gpt-oss-120b").
134
- * Returns the resolved model ID.
135
- */
136
- export function addExclusion(model: string, filePath: string = DEFAULT_EXCLUDE_FILE): string {
137
- const resolved = resolveModelAlias(model);
138
- const list = loadExcludeList(filePath);
139
- list.add(resolved);
140
- save(list, filePath);
141
- return resolved;
142
- }
143
-
144
- /**
145
- * Remove a model from the exclude list. Returns true if it was present.
146
- */
147
- export function removeExclusion(model: string, filePath: string = DEFAULT_EXCLUDE_FILE): boolean {
148
- const resolved = resolveModelAlias(model);
149
- const list = loadExcludeList(filePath);
150
- const had = list.delete(resolved);
151
- if (had) save(list, filePath);
152
- return had;
153
- }
154
-
155
- /**
156
- * Clear all exclusions.
157
- */
158
- export function clearExclusions(filePath: string = DEFAULT_EXCLUDE_FILE): void {
159
- save(new Set(), filePath);
160
- }
161
- ```
162
-
163
- **Step 4: Run test to verify it passes**
164
-
165
- Run: `npx vitest run src/exclude-models.test.ts`
166
- Expected: PASS
167
-
168
- **Step 5: Commit**
169
-
170
- ```bash
171
- git add src/exclude-models.ts src/exclude-models.test.ts
172
- git commit -m "feat: add exclude-models persistence module"
173
- ```
174
-
175
- ---
176
-
177
- ### Task 2: Filter Function in Selector
178
-
179
- **Files:**
180
- - Modify: `src/router/selector.ts` — add `filterByExcludeList()`
181
- - Test: `src/router/selector.test.ts` — add tests
182
-
183
- **Step 1: Write the failing test**
184
-
185
- Add to `src/router/selector.test.ts`:
186
-
187
- ```typescript
188
- import { filterByExcludeList } from "./selector.js";
189
-
190
- describe("filterByExcludeList", () => {
191
- it("removes excluded models from chain", () => {
192
- const chain = ["a/model-1", "b/model-2", "c/model-3"];
193
- const excluded = new Set(["b/model-2"]);
194
- expect(filterByExcludeList(chain, excluded)).toEqual(["a/model-1", "c/model-3"]);
195
- });
196
-
197
- it("returns original chain if all models excluded (safety net)", () => {
198
- const chain = ["a/model-1", "b/model-2"];
199
- const excluded = new Set(["a/model-1", "b/model-2"]);
200
- expect(filterByExcludeList(chain, excluded)).toEqual(["a/model-1", "b/model-2"]);
201
- });
202
-
203
- it("returns original chain for empty exclude set", () => {
204
- const chain = ["a/model-1", "b/model-2"];
205
- expect(filterByExcludeList(chain, new Set())).toEqual(["a/model-1", "b/model-2"]);
206
- });
207
- });
208
- ```
209
-
210
- **Step 2: Run test to verify it fails**
211
-
212
- Run: `npx vitest run src/router/selector.test.ts`
213
- Expected: FAIL — `filterByExcludeList` not exported
214
-
215
- **Step 3: Write minimal implementation**
216
-
217
- Add to `src/router/selector.ts`:
218
-
219
- ```typescript
220
- /**
221
- * Filter a model list to remove user-excluded models.
222
- * When all models are excluded, returns the full list as a fallback
223
- * (same safety pattern as filterByToolCalling/filterByVision).
224
- */
225
- export function filterByExcludeList(models: string[], excludeList: Set<string>): string[] {
226
- if (excludeList.size === 0) return models;
227
- const filtered = models.filter((m) => !excludeList.has(m));
228
- return filtered.length > 0 ? filtered : models;
229
- }
230
- ```
231
-
232
- **Step 4: Run test to verify it passes**
233
-
234
- Run: `npx vitest run src/router/selector.test.ts`
235
- Expected: PASS
236
-
237
- **Step 5: Commit**
238
-
239
- ```bash
240
- git add src/router/selector.ts src/router/selector.test.ts
241
- git commit -m "feat: add filterByExcludeList to router selector"
242
- ```
243
-
244
- ---
245
-
246
- ### Task 3: Wire Exclude Filter into Proxy Fallback Chain
247
-
248
- **Files:**
249
- - Modify: `src/proxy.ts` — add exclude filter step, accept excludeList in ProxyOptions, load at startup
250
-
251
- **Step 1: Add `excludeModels` to ProxyOptions**
252
-
253
- In `src/proxy.ts` at line ~1174, add to `ProxyOptions`:
254
-
255
- ```typescript
256
- /**
257
- * Set of model IDs to exclude from routing.
258
- * Excluded models are filtered out of fallback chains.
259
- * Loaded from ~/.openclaw/blockrun/exclude-models.json
260
- */
261
- excludeModels?: Set<string>;
262
- ```
263
-
264
- **Step 2: Wire filter into fallback chain building**
265
-
266
- In `src/proxy.ts` around line 3606 (inside the `if (routingDecision)` block), after the context filter and before the tool-calling filter, add:
267
-
268
- ```typescript
269
- // Filter out user-excluded models
270
- const excludeFiltered = filterByExcludeList(contextFiltered, options.excludeModels ?? new Set());
271
- const excludeExcluded = contextFiltered.filter((m) => !excludeFiltered.includes(m));
272
- if (excludeExcluded.length > 0) {
273
- console.log(
274
- `[ClawRouter] Exclude filter: excluded ${excludeExcluded.join(", ")} (user preference)`,
275
- );
276
- }
277
- ```
278
-
279
- Then update the next filter to chain from `excludeFiltered` instead of `contextFiltered`:
280
-
281
- ```typescript
282
- // Change: filterByToolCalling now takes excludeFiltered instead of contextFiltered
283
- let toolFiltered = filterByToolCalling(excludeFiltered, hasTools, supportsToolCalling);
284
- const toolExcluded = excludeFiltered.filter((m) => !toolFiltered.includes(m));
285
- ```
286
-
287
- **Step 3: Also filter the FREE_MODEL fallback at line 3674**
288
-
289
- Change the free model fallback to respect exclusions:
290
-
291
- ```typescript
292
- // Ensure free model is the last-resort fallback for non-tool requests — unless user excluded it.
293
- if (!hasTools && !modelsToTry.includes(FREE_MODEL) && !(options.excludeModels?.has(FREE_MODEL))) {
294
- modelsToTry.push(FREE_MODEL);
295
- }
296
- ```
297
-
298
- **Step 4: Add import for filterByExcludeList**
299
-
300
- At the top of `proxy.ts`, add `filterByExcludeList` to the selector import:
301
-
302
- ```typescript
303
- import { selectModel, getFallbackChain, getFallbackChainFiltered, calculateModelCost, filterByToolCalling, filterByVision, filterByExcludeList } from "./router/selector.js";
304
- ```
305
-
306
- **Step 5: Load exclude list at proxy startup**
307
-
308
- In `startProxy()` (around line 1426), load the exclude list and pass it through:
309
-
310
- ```typescript
311
- import { loadExcludeList } from "./exclude-models.js";
312
-
313
- // Inside startProxy(), before creating the server:
314
- const excludeModels = options.excludeModels ?? loadExcludeList();
315
- // Pass excludeModels into the options object used by request handlers
316
- ```
317
-
318
- Note: Re-read from disk on each request for hot-reload (the file is tiny, cost is negligible):
319
-
320
- ```typescript
321
- // In the request handler, before building fallback chain:
322
- const currentExcludeList = loadExcludeList();
323
- ```
324
-
325
- **Step 6: Run existing tests**
326
-
327
- Run: `npx vitest run src/proxy.*.test.ts`
328
- Expected: PASS (existing tests should still pass)
329
-
330
- **Step 7: Commit**
331
-
332
- ```bash
333
- git add src/proxy.ts
334
- git commit -m "feat: wire excludeModels filter into proxy fallback chain"
335
- ```
336
-
337
- ---
338
-
339
- ### Task 4: `/exclude` Telegram Command
340
-
341
- **Files:**
342
- - Modify: `src/index.ts` — add `createExcludeCommand()` + register it
343
-
344
- **Step 1: Create the command function**
345
-
346
- Add to `src/index.ts` (after `createStatsCommand`):
347
-
348
- ```typescript
349
- import { loadExcludeList, addExclusion, removeExclusion, clearExclusions } from "./exclude-models.js";
350
-
351
- async function createExcludeCommand(): Promise<OpenClawPluginCommandDefinition> {
352
- return {
353
- name: "exclude",
354
- description: "Manage excluded models — /exclude add|remove|clear <model>",
355
- acceptsArgs: true,
356
- requireAuth: true,
357
- handler: async (ctx: PluginCommandContext) => {
358
- const args = ctx.args?.trim() || "";
359
- const parts = args.split(/\s+/);
360
- const subcommand = parts[0]?.toLowerCase() || "";
361
- const modelArg = parts.slice(1).join(" ").trim();
362
-
363
- // /exclude (no args) — show current list
364
- if (!subcommand) {
365
- const list = loadExcludeList();
366
- if (list.size === 0) {
367
- return {
368
- text: "No models excluded.\n\nUsage:\n /exclude add <model> — block a model\n /exclude remove <model> — unblock\n /exclude clear — remove all",
369
- };
370
- }
371
- const models = [...list].sort().map((m) => ` • ${m}`).join("\n");
372
- return {
373
- text: `Excluded models (${list.size}):\n${models}\n\nUse /exclude remove <model> to unblock.`,
374
- };
375
- }
376
-
377
- // /exclude add <model>
378
- if (subcommand === "add") {
379
- if (!modelArg) {
380
- return { text: "Usage: /exclude add <model>\nExample: /exclude add nvidia/gpt-oss-120b", isError: true };
381
- }
382
- const resolved = addExclusion(modelArg);
383
- const list = loadExcludeList();
384
- return {
385
- text: `Excluded: ${resolved}\n\nActive exclusions (${list.size}):\n${[...list].sort().map((m) => ` • ${m}`).join("\n")}`,
386
- };
387
- }
388
-
389
- // /exclude remove <model>
390
- if (subcommand === "remove") {
391
- if (!modelArg) {
392
- return { text: "Usage: /exclude remove <model>", isError: true };
393
- }
394
- const removed = removeExclusion(modelArg);
395
- if (!removed) {
396
- return { text: `Model "${modelArg}" was not in the exclude list.` };
397
- }
398
- const list = loadExcludeList();
399
- return {
400
- text: `Unblocked: ${modelArg}\n\nActive exclusions (${list.size}):\n${list.size > 0 ? [...list].sort().map((m) => ` • ${m}`).join("\n") : " (none)"}`,
401
- };
402
- }
403
-
404
- // /exclude clear
405
- if (subcommand === "clear") {
406
- clearExclusions();
407
- return { text: "All model exclusions cleared." };
408
- }
409
-
410
- return {
411
- text: `Unknown subcommand: ${subcommand}\n\nUsage:\n /exclude — show list\n /exclude add <model>\n /exclude remove <model>\n /exclude clear`,
412
- isError: true,
413
- };
414
- },
415
- };
416
- }
417
- ```
418
-
419
- **Step 2: Register the command**
420
-
421
- Add after the `/stats` command registration block (~line 971):
422
-
423
- ```typescript
424
- // Register /exclude command for model exclusion management
425
- createExcludeCommand()
426
- .then((excludeCommand) => {
427
- api.registerCommand(excludeCommand);
428
- })
429
- .catch((err) => {
430
- api.logger.warn(
431
- `Failed to register /exclude command: ${err instanceof Error ? err.message : String(err)}`,
432
- );
433
- });
434
- ```
435
-
436
- **Step 3: Log active exclusions at startup**
437
-
438
- In the startup section (after wallet info logging), add:
439
-
440
- ```typescript
441
- const startupExclusions = loadExcludeList();
442
- if (startupExclusions.size > 0) {
443
- api.logger.info(`Model exclusions active (${startupExclusions.size}): ${[...startupExclusions].join(", ")}`);
444
- }
445
- ```
446
-
447
- **Step 4: Run all tests**
448
-
449
- Run: `npx vitest run`
450
- Expected: PASS
451
-
452
- **Step 5: Commit**
453
-
454
- ```bash
455
- git add src/index.ts
456
- git commit -m "feat: add /exclude command for model exclusion management"
457
- ```
458
-
459
- ---
460
-
461
- ### Task 5: Integration Test
462
-
463
- **Files:**
464
- - Create: `src/exclude-models.integration.test.ts`
465
-
466
- **Step 1: Write integration test**
467
-
468
- ```typescript
469
- // src/exclude-models.integration.test.ts
470
- import { describe, it, expect } from "vitest";
471
- import { filterByExcludeList } from "./router/selector.js";
472
- import { DEFAULT_ROUTING_CONFIG } from "./router/config.js";
473
- import { getFallbackChain } from "./router/selector.js";
474
-
475
- describe("excludeModels integration", () => {
476
- it("filters nvidia/gpt-oss-120b from eco SIMPLE chain", () => {
477
- const chain = getFallbackChain("SIMPLE", DEFAULT_ROUTING_CONFIG.ecoTiers!);
478
- const excluded = new Set(["nvidia/gpt-oss-120b"]);
479
- const filtered = filterByExcludeList(chain, excluded);
480
-
481
- expect(filtered).not.toContain("nvidia/gpt-oss-120b");
482
- expect(filtered.length).toBeGreaterThan(0); // safety: still has models
483
- });
484
-
485
- it("excludes multiple models across eco tiers", () => {
486
- const exclude = new Set(["nvidia/gpt-oss-120b", "xai/grok-4-0709"]);
487
-
488
- for (const tier of ["SIMPLE", "MEDIUM", "COMPLEX", "REASONING"] as const) {
489
- const chain = getFallbackChain(tier, DEFAULT_ROUTING_CONFIG.ecoTiers!);
490
- const filtered = filterByExcludeList(chain, exclude);
491
- for (const model of exclude) {
492
- if (chain.includes(model)) {
493
- // Only check if the model was in the chain to begin with
494
- expect(filtered).not.toContain(model);
495
- }
496
- }
497
- expect(filtered.length).toBeGreaterThan(0);
498
- }
499
- });
500
-
501
- it("gracefully handles excluding ALL models in a tier", () => {
502
- const chain = getFallbackChain("SIMPLE", DEFAULT_ROUTING_CONFIG.ecoTiers!);
503
- const excludeAll = new Set(chain);
504
- const filtered = filterByExcludeList(chain, excludeAll);
505
- // Safety net: returns original chain when all excluded
506
- expect(filtered).toEqual(chain);
507
- });
508
- });
509
- ```
510
-
511
- **Step 2: Run integration test**
512
-
513
- Run: `npx vitest run src/exclude-models.integration.test.ts`
514
- Expected: PASS
515
-
516
- **Step 3: Run full test suite**
517
-
518
- Run: `npx vitest run`
519
- Expected: ALL PASS
520
-
521
- **Step 4: Commit**
522
-
523
- ```bash
524
- git add src/exclude-models.integration.test.ts
525
- git commit -m "test: add exclude-models integration tests"
526
- ```
527
-
528
- ---
529
-
530
- ### Summary
531
-
532
- | Task | What | Files |
533
- |------|------|-------|
534
- | 1 | Persistence module (load/add/remove/clear) | `src/exclude-models.ts`, test |
535
- | 2 | `filterByExcludeList()` in selector | `src/router/selector.ts`, test |
536
- | 3 | Wire into proxy fallback chain | `src/proxy.ts` |
537
- | 4 | `/exclude` Telegram command | `src/index.ts` |
538
- | 5 | Integration tests | `src/exclude-models.integration.test.ts` |
@@ -1,157 +0,0 @@
1
- # ClawRouter vs OpenRouter
2
-
3
- OpenRouter is a popular LLM routing service. Here's why ClawRouter is built differently — and why it matters for agents.
4
-
5
- ## TL;DR
6
-
7
- **OpenRouter is built for developers. ClawRouter is built for agents.**
8
-
9
- | Aspect | OpenRouter | ClawRouter |
10
- | ------------------ | ------------------------------------- | -------------------------------------- |
11
- | **Setup** | Human creates account, pastes API key | Agent generates wallet, receives funds |
12
- | **Authentication** | API key (shared secret) | Wallet signature (cryptographic) |
13
- | **Payment** | Prepaid balance (custodial) | Per-request USDC (non-custodial) |
14
- | **Routing** | Server-side, proprietary | Client-side, open source, <1ms |
15
- | **Rate limits** | Per-key quotas | None (your wallet, your limits) |
16
- | **Empty balance** | Request fails | Auto-fallback to free tier |
17
-
18
- ---
19
-
20
- ## The Problem with API Keys
21
-
22
- OpenRouter (and every traditional LLM gateway) uses API keys for authentication. This breaks agent autonomy:
23
-
24
- ### 1. Key Leakage in LLM Context
25
-
26
- **OpenClaw Issue [#11202](https://github.com/openclaw/openclaw/issues/11202)**: API keys configured in `openclaw.json` are resolved and serialized into every LLM request payload. Every provider sees every other provider's keys.
27
-
28
- > "OpenRouter sees your NVIDIA key, Anthropic sees your Google key... keys are sent on every turn."
29
-
30
- **ClawRouter**: No API keys. Authentication happens via cryptographic wallet signatures. There's nothing to leak because there are no shared secrets.
31
-
32
- ### 2. Rate Limit Hell
33
-
34
- **OpenClaw Issue [#8615](https://github.com/openclaw/openclaw/issues/8615)**: Single API key support means heavy users hit rate limits (429 errors) quickly. Users request multi-key load balancing, but that's just patching a broken model.
35
-
36
- **ClawRouter**: Non-custodial wallets. You control your own keys. No shared rate limits. Scale by funding more wallets if needed.
37
-
38
- ### 3. Setup Friction
39
-
40
- **OpenClaw Issues [#16257](https://github.com/openclaw/openclaw/issues/16257), [#16226](https://github.com/openclaw/openclaw/issues/16226)**: Latest installer skips model selection, shows "No auth configured for provider anthropic". Users can't even get started without debugging config.
41
-
42
- **ClawRouter**: One-line install. 30+ models auto-configured. No API keys to paste.
43
-
44
- ### 4. Model Path Collision
45
-
46
- **OpenClaw Issue [#2373](https://github.com/openclaw/openclaw/issues/2373)**: `openrouter/auto` is broken because OpenClaw prefixes all OpenRouter models with `openrouter/`, so the actual model becomes `openrouter/openrouter/auto`.
47
-
48
- **ClawRouter**: Clean namespace. `blockrun/auto` just works. No prefix collision.
49
-
50
- ### 5. False Billing Errors
51
-
52
- **OpenClaw Issue [#16237](https://github.com/openclaw/openclaw/issues/16237)**: The regex `/\b402\b/` falsely matches normal content (e.g., "402 calories") as a billing error, replacing valid AI responses with error messages.
53
-
54
- **ClawRouter**: Native x402 protocol support. Precise error handling. No regex hacks.
55
-
56
- ### 6. Unknown Model Failures
57
-
58
- **OpenClaw Issues [#16277](https://github.com/openclaw/openclaw/issues/16277), [#10687](https://github.com/openclaw/openclaw/issues/10687)**: Static model catalog causes "Unknown model" errors when providers add new models or during sub-agent spawns.
59
-
60
- **ClawRouter**: 30+ models pre-configured, auto-updated catalog.
61
-
62
- ---
63
-
64
- ## Agent-Native: Why It Matters
65
-
66
- Traditional LLM gateways require a human in the loop:
67
-
68
- ```
69
- Traditional Flow (Human-in-the-loop):
70
- Human → creates account → gets API key → pastes into config → agent runs
71
-
72
- Agent-Native Flow (Fully autonomous):
73
- Agent → generates wallet → receives USDC → pays per request → runs
74
- ```
75
-
76
- | Capability | OpenRouter | ClawRouter |
77
- | -------------------- | ----------------------- | -------------------------- |
78
- | **Account creation** | Requires human | Agent generates wallet |
79
- | **Authentication** | Shared secret (API key) | Cryptographic signature |
80
- | **Payment** | Human prepays balance | Agent pays per request |
81
- | **Funds custody** | They hold your money | You hold your keys |
82
- | **Empty balance** | Request fails | Auto-fallback to free tier |
83
-
84
- ### The x402 Difference
85
-
86
- ```
87
- Request → 402 Response (price: $0.003)
88
- → Agent's wallet signs payment
89
- → Response delivered
90
-
91
- No accounts. No API keys. No human intervention.
92
- ```
93
-
94
- **Agents can:**
95
-
96
- - Spawn with a fresh wallet
97
- - Receive funds programmatically
98
- - Pay for exactly what they use
99
- - Never trust a third party with their funds
100
-
101
- ---
102
-
103
- ## Routing: Cloud vs Local
104
-
105
- ### OpenRouter
106
-
107
- - Routing decisions happen on OpenRouter's servers
108
- - You trust their proprietary algorithm
109
- - No visibility into why a model was chosen
110
- - Adds latency for every request
111
-
112
- ### ClawRouter
113
-
114
- - **100% local routing** — 15-dimension weighted scoring runs on YOUR machine
115
- - **<1ms decisions** — no API calls for routing
116
- - **Open source** — inspect the exact scoring logic in [`src/router.ts`](../src/router.ts)
117
- - **Transparent** — see why each model is chosen
118
-
119
- ---
120
-
121
- ## Quick Start
122
-
123
- Already using OpenRouter? Switch in 60 seconds:
124
-
125
- ```bash
126
- # 1. Install ClawRouter
127
- curl -fsSL https://blockrun.ai/ClawRouter-update | bash
128
-
129
- # 2. Restart gateway
130
- openclaw gateway restart
131
-
132
- # 3. Fund wallet (address shown during install)
133
- # $5 USDC on Base = thousands of requests
134
-
135
- # 4. Switch model
136
- /model blockrun/auto
137
- ```
138
-
139
- Your OpenRouter config stays intact — ClawRouter is additive, not replacement.
140
-
141
- ---
142
-
143
- ## Summary
144
-
145
- > **OpenRouter**: Built for developers who paste API keys
146
- >
147
- > **ClawRouter**: Built for agents that manage their own wallets
148
-
149
- The future of AI isn't humans configuring API keys. It's agents autonomously acquiring and paying for resources.
150
-
151
- ---
152
-
153
- <div align="center">
154
-
155
- **Questions?** [Telegram](https://t.me/blockrunAI) · [X](https://x.com/BlockRunAI) · [GitHub](https://github.com/BlockRunAI/ClawRouter)
156
-
157
- </div>