openzoo 0.49.12 → 0.49.13
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.
- package/bin/openzoo.js +19 -0
- package/lib/namespace.js +13 -7
- package/lib/openclaw.js +196 -0
- package/lib/pay.js +38 -7
- package/lib/wrap.js +16 -2
- package/lib/xbot.js +1576 -0
- package/lib/xburner.js +80 -0
- package/package.json +1 -1
- package/vendor/modelroute/FOR_MOOSE.md +110 -0
package/lib/xburner.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A managed x402 burner per X account, DERIVED not stored.
|
|
3
|
+
*
|
|
4
|
+
* @openzoobot's paid lane needs the ASKER to pay, and an X reply is a terrible
|
|
5
|
+
* place to ask someone to install a wallet. So each X account id gets a burner
|
|
6
|
+
* the zoo drives on their behalf, funded by them and auto-topped-up, holding
|
|
7
|
+
* only a working balance — the same shape as the local burner `npx openzoo`
|
|
8
|
+
* already creates, one per account instead of one per machine.
|
|
9
|
+
*
|
|
10
|
+
* DERIVED, NOT STORED, and that is the whole security argument:
|
|
11
|
+
* seed(user) = HMAC-SHA512(master, "openzoo-xbot-v1:" + userId)
|
|
12
|
+
* There is exactly ONE secret on disk no matter how many accounts ever mention
|
|
13
|
+
* the bot. A per-user keyfile store would mean thousands of secrets, a backup
|
|
14
|
+
* problem, a deletion problem, and a breach that scales with adoption. Here the
|
|
15
|
+
* blast radius is one file that already had to be protected, and a burner can
|
|
16
|
+
* be re-derived on any machine from that file alone — nothing to lose, nothing
|
|
17
|
+
* to migrate, no keypair that exists only on whichever laptop ran the poller.
|
|
18
|
+
*
|
|
19
|
+
* The tradeoff, stated plainly: the master file CAN derive every burner, so it
|
|
20
|
+
* is as sensitive as all of them combined. That is why it is 0600, never
|
|
21
|
+
* logged, never sent anywhere, and why balances are kept at working size by
|
|
22
|
+
* auto top-up rather than being allowed to accumulate.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import fs from 'node:fs';
|
|
26
|
+
import os from 'node:os';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import crypto from 'node:crypto';
|
|
29
|
+
import { Keypair } from '@solana/web3.js';
|
|
30
|
+
|
|
31
|
+
const MASTER_FILE = process.env.OPENZOO_XBOT_MASTER
|
|
32
|
+
|| path.join(os.homedir(), '.openzoo', 'xbot-master.key');
|
|
33
|
+
|
|
34
|
+
/** Bump if the derivation ever changes — old burners must keep deriving. */
|
|
35
|
+
const DERIVATION = 'openzoo-xbot-v1';
|
|
36
|
+
|
|
37
|
+
export function loadOrCreateMaster(file = MASTER_FILE) {
|
|
38
|
+
try {
|
|
39
|
+
const hex = fs.readFileSync(file, 'utf8').trim();
|
|
40
|
+
const buf = Buffer.from(hex, 'hex');
|
|
41
|
+
if (buf.length === 32) return buf;
|
|
42
|
+
throw new Error('bad length');
|
|
43
|
+
} catch {
|
|
44
|
+
const buf = crypto.randomBytes(32);
|
|
45
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
46
|
+
// wx: never clobber an existing master. Overwriting it would orphan every
|
|
47
|
+
// burner ever handed out — funds still on-chain, key unrecoverable.
|
|
48
|
+
try {
|
|
49
|
+
fs.writeFileSync(file, buf.toString('hex') + '\n', { mode: 0o600, flag: 'wx' });
|
|
50
|
+
} catch {
|
|
51
|
+
return Buffer.from(fs.readFileSync(file, 'utf8').trim(), 'hex');
|
|
52
|
+
}
|
|
53
|
+
return buf;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Deterministic burner for an X user id.
|
|
59
|
+
* Keyed on the numeric id, never the handle: handles are reassignable, and a
|
|
60
|
+
* burner that follows a renamed handle would hand a new owner the old wallet.
|
|
61
|
+
*/
|
|
62
|
+
export function deriveBurner(xUserId, master = loadOrCreateMaster()) {
|
|
63
|
+
if (!xUserId) throw new Error('deriveBurner needs an X user id');
|
|
64
|
+
const mac = crypto.createHmac('sha512', master)
|
|
65
|
+
.update(`${DERIVATION}:${String(xUserId)}`)
|
|
66
|
+
.digest();
|
|
67
|
+
const keypair = Keypair.fromSeed(Uint8Array.from(mac.subarray(0, 32)));
|
|
68
|
+
const evmPrivateKey = `0x${mac.subarray(32, 64).toString('hex')}`;
|
|
69
|
+
return {
|
|
70
|
+
keypair,
|
|
71
|
+
evmPrivateKey,
|
|
72
|
+
address: keypair.publicKey.toBase58(),
|
|
73
|
+
xUserId: String(xUserId),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Address only — for a reply that tells someone where to send funds. */
|
|
78
|
+
export function burnerAddress(xUserId, master) {
|
|
79
|
+
return deriveBurner(xUserId, master).address;
|
|
80
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.49.
|
|
3
|
+
"version": "0.49.13",
|
|
4
4
|
"description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# The auto-router, for Moose
|
|
2
|
+
|
|
3
|
+
*How `openzoo/auto` picks a model — a VSA classifier trained closed-form, 40KB,
|
|
4
|
+
no torch, no embeddings, no pretrained anything. Your math, our objective.*
|
|
5
|
+
|
|
6
|
+
## The objective (this is the part that isn't a classifier)
|
|
7
|
+
|
|
8
|
+
Routing is not "what topic is this" and not "what's the best model":
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
pick argmin cost(m, task) subject to P_success(m | task) >= bar(task)
|
|
12
|
+
m ∈ feasible(task)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
If nothing clears the bar, say so and return the highest-P option **labelled as
|
|
16
|
+
such** — never silently pretend the cheapest one was fine. Four terms, four
|
|
17
|
+
sources:
|
|
18
|
+
|
|
19
|
+
- **feasible()** — hard metadata facts only (image input, tool calling, strict
|
|
20
|
+
JSON, context length). A model without an image modality can't read a
|
|
21
|
+
screenshot at any price. Never guessed.
|
|
22
|
+
- **cost()** — `price_in × est. prompt tokens + price_out × est. completion
|
|
23
|
+
tokens`, divided by the cheapest feasible candidate's, so ranking is
|
|
24
|
+
scale-free.
|
|
25
|
+
- **bar()** — an authored policy knob per (class, difficulty). Stated, not
|
|
26
|
+
hidden, not measured.
|
|
27
|
+
- **P_success()** — the interesting one, below.
|
|
28
|
+
|
|
29
|
+
**A trap we deliberately walked around:** price is tempting as a capability
|
|
30
|
+
proxy (expensive ≈ stronger). It is NOT used that way, because cost is the
|
|
31
|
+
thing being minimised — letting price raise P_success would make the objective
|
|
32
|
+
partly cancel itself and quietly re-rank toward expensive models for no
|
|
33
|
+
measured reason. Price appears in cost(), nowhere else.
|
|
34
|
+
|
|
35
|
+
## The classifier — random indexing, two gradient-free heads
|
|
36
|
+
|
|
37
|
+
Text → hypervector by **Kanerva random indexing**: each token deterministically
|
|
38
|
+
hashes (sha256, never Python's salted `hash()` — deterministic under any
|
|
39
|
+
`PYTHONHASHSEED`) to k sparse ±1 positions in d=4096. A request is the bundle
|
|
40
|
+
of its token atoms over **three separately-normalised channels** — words, word
|
|
41
|
+
bigrams, char 4-grams — so a misspelling degrades a vector instead of erasing a
|
|
42
|
+
word. Channel atoms are weighted by a hashed IDF table (16KB).
|
|
43
|
+
|
|
44
|
+
Two heads produce the **same artifact shape** (K×d matrix, K=10 classes:
|
|
45
|
+
code / reasoning / longctx / vision / bulk / creative / translate / agentic /
|
|
46
|
+
chat / advice):
|
|
47
|
+
|
|
48
|
+
- **ridge (shipped)** — closed-form one-vs-rest least squares in the dual:
|
|
49
|
+
`W = Xᵀ(XXᵀ + λI)⁻¹Y`. One 535×535 solve. No epochs, no learning rate, no
|
|
50
|
+
shuffling, no lucky seed.
|
|
51
|
+
- **adapthd (kept)** — perceptron prototypes; the reference the ridge head is
|
|
52
|
+
checked against.
|
|
53
|
+
|
|
54
|
+
Shipped artifact: **40KB int8 matrix + 16KB IDF**. Inference is a dot product.
|
|
55
|
+
`router.json` carries `{classes, q_b64 (int8), q_shape [10,4096], scale, idf}`.
|
|
56
|
+
|
|
57
|
+
## Measured, with ablations (train_router.py --ablate)
|
|
58
|
+
|
|
59
|
+
10 classes, 400 authored requests + anchors, held-out split written in a
|
|
60
|
+
deliberately different register:
|
|
61
|
+
|
|
62
|
+
| configuration | held-out | 5-fold |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| ridge + IDF + anchors | **0.642** | **0.772** |
|
|
65
|
+
| bag-of-words nearest centroid | 0.333 | — |
|
|
66
|
+
| majority class | 0.100 | — |
|
|
67
|
+
|
|
68
|
+
Each of the three choices (ridge, IDF, anchors) was ablated separately and each
|
|
69
|
+
earns its place.
|
|
70
|
+
|
|
71
|
+
**But class accuracy is not the headline.** `routing_regret.py` measures the
|
|
72
|
+
classifier in dollars and failures instead: **5.0% of held-out requests route
|
|
73
|
+
below the bar their true class would have demanded, at a median 1.12× the
|
|
74
|
+
oracle route's cost.** A misclassification that lands on an adequate cheaper
|
|
75
|
+
model is not an error that matters.
|
|
76
|
+
|
|
77
|
+
## P_success — a prior that becomes a measurement
|
|
78
|
+
|
|
79
|
+
No API reports "model m completes class c with probability p". What exists is
|
|
80
|
+
OpenRouter's per-category leaderboards: top-20 **by usage** — revealed
|
|
81
|
+
preference, not benchmark (popularity tracks price, marketing, defaults too,
|
|
82
|
+
and it's labelled as such).
|
|
83
|
+
|
|
84
|
+
So: prior = leaderboard rank in the categories the class maps to, shrunk toward
|
|
85
|
+
a metadata floor for the ~87% of the 414-model catalogue on no leaderboard at
|
|
86
|
+
all. Then `record_outcome(class, model, ok)` turns it into a **Beta posterior**
|
|
87
|
+
whose prior weight is the leaderboard number and whose data is our own
|
|
88
|
+
completions. Day one it answers `evidence: "prior"`; after real traffic it
|
|
89
|
+
answers `evidence: "measured(n=…)"`. As of today `outcomes.json` holds **391
|
|
90
|
+
(class, model) pairs of live measurements**.
|
|
91
|
+
|
|
92
|
+
The classifier's output is never the answer — it sets the bar and picks which
|
|
93
|
+
leaderboards to trust. The catalogue is a lookup table, not a softmax, so a
|
|
94
|
+
model added tomorrow is routable today with zero retraining.
|
|
95
|
+
|
|
96
|
+
## Serving
|
|
97
|
+
|
|
98
|
+
Gateway (`x402-tokens/src/auto.ts`): `openzoo/auto` → `route(text)` → shortlist
|
|
99
|
+
→ **first-2-of-5 race** over the shortlist (RACE_X=2, RACE_Y=5, min score 6);
|
|
100
|
+
tool-bearing bodies race a tool-capable pool, never a bare flash. If the router
|
|
101
|
+
artifacts are missing it degrades to a static cheap pool — routing is an
|
|
102
|
+
enhancement, never a dependency. Named models are never rerouted.
|
|
103
|
+
|
|
104
|
+
Files: `holographic_modelroute.py` (the whole thing, heavily commented),
|
|
105
|
+
`router.json` / `outcomes.json` / `catalog.json` (artifacts),
|
|
106
|
+
`tools/modelroute/train_router.py` (training + ablations),
|
|
107
|
+
`tools/modelroute/routing_regret.py` (the dollars-and-failures metric).
|
|
108
|
+
|
|
109
|
+
*Live result on X right now: @openzoobot answers tweet-sized questions at
|
|
110
|
+
$0.00001–0.007 via this router, receipts printed per reply.*
|