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/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.12",
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.*