openzoo 0.48.33 → 0.48.35

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/hrr.js CHANGED
@@ -26,7 +26,7 @@ export const contextCacheDisabled = () => process.env.OPENZOO_NO_CONTEXT_CACHE =
26
26
  * Ensure `corpus` is bound on the zoo. Returns
27
27
  * { contextId, hash, reused, bytes } — reused=true means zero bytes shipped.
28
28
  */
29
- export async function bindCorpus(corpus, { onStage, force = false } = {}) {
29
+ export async function bindCorpus(corpus, { onStage, force = false, appendTo = null } = {}) {
30
30
  const hash = corpusHash(corpus);
31
31
  const bytes = Buffer.byteLength(corpus);
32
32
  if (!force) {
@@ -37,7 +37,11 @@ export async function bindCorpus(corpus, { onStage, force = false } = {}) {
37
37
  }
38
38
  }
39
39
  onStage?.('binding', { bytes });
40
- const r = await postWithUploadSignal(`${config.apiBase}/v1/hrr/bind`, JSON.stringify({ corpus }), {
40
+ // APPEND, don't re-upload. The gateway takes context_id on /v1/hrr/bind and
41
+ // adds to that context — which is the difference between paying for the
42
+ // delta and paying for the whole transcript again on every single turn.
43
+ const r = await postWithUploadSignal(`${config.apiBase}/v1/hrr/bind`,
44
+ JSON.stringify(appendTo ? { corpus, context_id: appendTo } : { corpus }), {
41
45
  headers: withNamespace(),
42
46
  onUploaded: () => onStage?.('bound-uploading-done', { bytes }),
43
47
  });
package/lib/proxy.js CHANGED
@@ -90,6 +90,9 @@ function jsonErr(res, status, message, extraFields = {}) {
90
90
 
91
91
  const mb = (n) => (n / 1048576).toFixed(1);
92
92
 
93
+ // anchor -> { corpus, contextId, hash } for append-only transcript spills
94
+ const spillMemo = new Map();
95
+
93
96
  /**
94
97
  * Every fundable balance across all three chains, for the startup line and
95
98
  * the live refresh. Each read is independent and advisory — one lagging RPC
@@ -339,15 +342,65 @@ async function spillTranscript(body, log) {
339
342
  }
340
343
  if (cut <= firstSpillable) return null; // nothing safely severable
341
344
 
345
+ // TRIM THE TAIL BY BYTES, NOT MESSAGE COUNT.
346
+ //
347
+ // MEASURED on the live session: 9 kept turns of Claude Code tool output made
348
+ // promptTokens swamp the counterfactual and the call scored 1.00x, while the
349
+ // SAME bound context with a one-line ask scored 8.53x. Nine messages is a
350
+ // trivial number and an enormous payload — a single Read or grep result is
351
+ // tens of KB — so counting messages measures the wrong thing entirely.
352
+ //
353
+ // Walk backwards from the newest and stop at a byte budget. The newest turns
354
+ // are the ones the model actually needs verbatim; everything older is already
355
+ // in the bound corpus and comes back through recall.
356
+ let tailStart = cut;
357
+ {
358
+ const budget = Number(process.env.OPENZOO_TAIL_MAX_CHARS || 24000);
359
+ let used = 0;
360
+ for (let i = msgs.length - 1; i >= cut; i--) {
361
+ used += msgText(msgs[i]).length;
362
+ if (used > budget && severable(i)) { tailStart = i; break; }
363
+ }
364
+ }
365
+ if (tailStart > cut) cut = tailStart;
366
+
342
367
  const head = msgs.slice(0, firstSpillable); // system block, always kept
343
368
  const corpus = msgs.slice(firstSpillable, cut).map(msgText).filter(Boolean).join('\n\n');
344
369
  if (corpus.length <= BIND_MIN_CHARS) return null;
345
370
 
346
- const bind = await bindCorpus(corpus, {
347
- onStage: (stage, info) => {
348
- if (stage === 'binding') log(`binding ${mb(info.bytes)}MB of transcript to holographic memory...`);
349
- },
350
- });
371
+ // CONTINUE THE CONTEXT, BIND ONLY THE DELTA.
372
+ //
373
+ // A transcript grows by one message per turn, so the whole-corpus hash misses
374
+ // every time and the old code re-uploaded the ENTIRE prefix on every single
375
+ // turn — OBSERVED live: 0.4MB bound three turns running, a fresh context id
376
+ // each time, while only a few KB was actually new. Bind cost grew with
377
+ // conversation length and was re-paid per message.
378
+ //
379
+ // The corpus is append-only: each turn's corpus starts with the previous
380
+ // one. So when it does, send just the tail and keep the same context_id.
381
+ // Anchored on the FIRST 2KB, which is stable for the life of a conversation
382
+ // and distinguishes concurrent ones.
383
+ const anchor = corpus.slice(0, 2048);
384
+ const prior = spillMemo.get(anchor);
385
+ let bind;
386
+ if (prior && corpus.startsWith(prior.corpus) && corpus.length > prior.corpus.length) {
387
+ const delta = corpus.slice(prior.corpus.length);
388
+ await bindCorpus(delta, {
389
+ appendTo: prior.contextId,
390
+ onStage: (stage, info) => {
391
+ if (stage === 'binding') log(`appending ${mb(info.bytes)}MB to ${prior.contextId} (delta only)`);
392
+ },
393
+ });
394
+ bind = { contextId: prior.contextId, hash: prior.hash, reused: true, bytes: delta.length };
395
+ } else {
396
+ bind = await bindCorpus(corpus, {
397
+ onStage: (stage, info) => {
398
+ if (stage === 'binding') log(`binding ${mb(info.bytes)}MB of transcript to holographic memory...`);
399
+ },
400
+ });
401
+ }
402
+ spillMemo.set(anchor, { corpus, contextId: bind.contextId, hash: bind.hash });
403
+ if (spillMemo.size > 32) spillMemo.delete(spillMemo.keys().next().value);
351
404
  const sent = msgs.length - cut;
352
405
  log(bind.reused
353
406
  ? `transcript prefix already bound (${bind.contextId}) — sending ${sent}/${msgs.length} turns`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.48.33",
3
+ "version": "0.48.35",
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",