Problem: expert match commentary dies the moment the network drops β and the fans who most need an explainer carry the weakest phones.
Solution: Gaffer streams tactical commentary from a model on your own device (QVAC), and when your device is too weak it borrows a laptop peer's compute over an encrypted P2P link (Pear stack) β zero cloud, zero API keys.
Built: a working P2P inference-offload runtime with mid-sentence failover, pear:// model sharing, a broadcast-style HUD, a reproducible benchmark, an offline-proof script and 220 passing tests (incl. real-swarm integration).
The magic moment: a weak client crawls at ~6 tok/s. A laptop joins the same match topic β and the gauge surges to ~47 tok/s with a lime
β BOOSTED BY PEERbadge. Kill the laptop mid-sentence and the sentence finishes anyway, token-exact, from the local engine. All of it with the internet unplugged.
git clone https://github.com/edycutjong/gaffer.git && cd gaffer
npm install # pure-JS P2P deps, ~20s
# one terminal (judge mode)
node cli.js --standalone
# β¦ or the real thing, two terminals / two machines:
node cli.js --provider # terminal A β the laptop brain
node cli.js --client # terminal B β the weak phone
# desktop HUD (requires the Pear runtime: npm i -g pear)
pear run .
# proofs
npm test # 220 tests: unit + real-swarm integration
npm run bench # local vs offloaded tok/s, p50/p95
npm run verify:offline # the whole flow with the internet blockedThe default engine is auto: it uses the real QVAC engine when @qvac/sdk is installed
(npm run setup:qvac, then the model downloads once β or arrives over pear://), and
otherwise falls back to a clearly-labelled deterministic sim engine so the P2P offload
is demoable on any machine. Every surface (CLI banner, HUD badge, bench header) states
which engine produced the tokens.
| Layer | Status |
|---|---|
| Hyperswarm discovery, Noise-encrypted delegation, token streaming, ordering checks | Real, always β including in tests (loopback DHT testnet, no mocks) |
| Mid-stream provider death β local resume state machine | Real, always β exercised by integration tests and the recorded demo |
pear:// model sharing (Hyperdrive seed + fetch) |
Real, always β byte-exact fetch verified in tests |
| LLM inference + Piper TTS | Real with @qvac/sdk installed (--engine qvac); otherwise a disclosed deterministic grammar (--engine sim) that is never presented as a model β TTS stays off rather than faking audio |
| Match events | Deterministic simulator (seeded fixtures) β Gaffer narrates feeds; it is not a licensed data product |
Browser demo (npm run demo:web) |
Replay of a real recorded session, badged as such β the surge is never faked |
flowchart LR
subgraph Phone["Weak client (phone / second terminal)"]
UI[HUD / CLI] --> CE[CommentaryEngine]
CE --> RT{InferenceRouter\nLOCAL β OFFLOADED β FALLBACK}
RT -- local --> E1[engine: qvac or sim]
end
subgraph Laptop["Provider peer (laptop)"]
P[ProviderNode] --> E2[engine: qvac or sim]
end
RT -- "req / tok / end (framed JSON)" --> SS[Hyperswarm socket\nNoise Secretstream E2E]
SS --> P
HS([HyperDHT topic = hash of match id]) -. discovery .- RT & P
MODEL[(pear:// Hyperdrive\nGGUF weights)] -. seed / fetch .-> E1 & E2
lib/state.jsβ the failover state machine, every (state Γ event) pair defined and testedlib/router.jsβ delegation with strict token ordering; deterministic engines resume token-exact after provider death, non-deterministic engines restart the segment (flagged) β honesty over magiclib/protocol.jsβ length-prefixed framed JSON over the already-encrypted swarm socket, hostile-frame guards includedlib/engines/qvac.jsβ the real SDK adapter:loadModel({modelSrc})(local / http /pear://),completion({modelId, history, stream:true})βtokenStream,textToSpeech(Piper),startQVACProvider({topic})offered when available- full details: docs/ARCHITECTURE.md Β· docs/COMPLEXITY.md
Measured on a cold clone (fresh npm ci), sim profiles (weak client 6 tok/s vs laptop
48 tok/s, disclosed), 12 segments, loopback swarm β the transport numbers are the real
product measurement. Expect Β±2% run-to-run variance:
| Metric | β Local | β Offloaded |
|---|---|---|
| tokens/sec p50 | 5.99 | 46.7 |
| tokens/sec p95 | 5.99 | 47.8 |
| first token (p50) | 168 ms | 22 ms |
- Speedup Γ7.8 (p50) β the weak device streams at the laptop's rate
- 97%+ transport efficiency β the P2P hop (framing + Noise + swarm) costs a few percent at most
- β€ 25 ms peer connect β OFFLOADED on loopback; LAN adds network RTT
- With
--engine qvacthe same bench measures real model tok/s on your hardware
npm test runs 220 tests (node --test, zero test-framework deps): protocol framing &
hostile-frame guards, topic derivation, deterministic match simulation, sim-engine grammar &
throttling, prompt construction, metrics math, all 20 state-machine transitions, router
failover semantics, commentary pacing/controls, offline network guard, and real-swarm
integration over a loopback HyperDHT testnet β provider discovery, delegated streaming with
strict ordering, token-exact mid-stream failover, provider recovery, cross-peer cancel,
concurrent clients, and byte-exact pear:// model fetch. CI runs them on Node 20/22/24 plus
secret scanning, verify:offline, a bench artifact, and a submission-readiness gate that
re-verifies this README's test count against a live run.
npm run coverage holds lib/ at 100% lines, 100% functions, and 100% branches under
node --test's native coverage. Three files are excluded from that unit gate β honestly, and
each for a real reason: lib/engines/qvac.js (the live @qvac/sdk on-device adapter β never
stubbed to fake coverage), lib/engine.js (its QVAC-load branches need the real SDK; the
fallback path is still tested), and lib/modelshare.js (real pear:// Hyperdrive replication,
integration-proven). Nothing is mocked to inflate a number.
completion({stream:true})βtokenStreamβ the streaming commentary itself. Without it: a cloud LLM, which breaks the entire premise.loadModel({modelSrc: 'pear://β¦'})β weights arrive from a peer, not HuggingFace; match day needs zero internet. Without it: a CDN download over dead stadium wifi.startQVACProvider({topic})/ Gaffer's provider protocol β a laptop lends its compute to a phone over P2P. This is the capability unlock; no cloud SDK can do it, because the transport is the Holepunch substrate QVAC ships on.textToSpeech()(Piper) β the voice in your ear, on-device. Without it: a cloud TTS call and a privacy hole.- Hyperswarm + HyperDHT β the per-match room; peers are keypairs, holepunched directly. Without it: a signalling/TURN server you must run and trust.
- Secretstream (Noise) β every delegated token is E2E-encrypted transport-level. Without it: TLS to a middlebox.
- Hyperdrive / Corestore β the model as a P2P drive with sparse, verified replication. Without it: object storage + a CDN.
Take QVAC + Pear out and you'd need: a cloud LLM endpoint, a cloud TTS endpoint, a model CDN, a signalling/TURN service, and a rented GPU β plus a privacy policy asking fans to trust you. Gaffer replaces that stack with two devices talking directly. Full brief: docs/gaffer_dorahacks_submission.md.
- Cold start: first model load (download/mmap/warm-up) is slow; mitigated by
pear://pre-seeding (node scripts/seed_model.js), and documented instead of hidden. - Generative, not factual: Gaffer narrates a match event feed; it is not a licensed live-data product.
- Token-exact resume is engine-conditional: deterministic engines resume mid-sentence exactly;
a real LLM restarts the segment (flagged
restartedin the UI) because cross-device sampling determinism can't be honestly guaranteed. - The offline guard covers TCP/TLS/HTTP(S)/fetch; the DHT itself speaks UDP β see docs/AUDIT_REPORT.md for the full threat model and residual risks.
startQVACProviderconsumer-side pairing isn't exercised in CI (needs the SDK + model installed); Gaffer's own delegation protocol carries the demo and the SDK-native provider is offered when present.
- The streaming-
completionloop shape followstetherto/qvac-examples(qvac-coffee-conversation); the verified SDK surface comes from the official docs. - Everything else β the delegation protocol, router/failover state machine, offline guard, match/commentary engines, HUD, scripts and tests β was written for this hackathon, in-window.
gaffer/
βββ cli.js # two-terminal demo + judge mode
βββ index.js # importable library surface (offload helpers)
βββ lib/ # runtime: engines, swarm, protocol, router, state, metricsβ¦
β βββ engines/ # qvac.js (real SDK adapter) Β· sim.js (disclosed dev engine)
βββ app/ # Pear desktop HUD (live) + replay mode for browsers
βββ scripts/ # bench Β· verify_offline Β· seed Β· seed_model Β· lan_bootstrap β¦
βββ test/ # 220 tests (unit + integration over a loopback DHT)
βββ docs/ # DEMO Β· ARCHITECTURE Β· COMPLEXITY Β· AUDIT Β· pitch deck Β· friction log
βββ landing/ # one-page explainer
βββ data/fixtures/ # deterministic demo matches (3-2 thriller included)
DEMO.md β exact two-machine demo steps & expected output Β· ARCHITECTURE.md Β· COMPLEXITY.md Β· AUDIT_REPORT.md β invariants & threat model Β· AUDIT_HACKATHON_REVIEW.md β Round-of-32 judge-lens audit (code+docs) Β· AUDIT_POLISH_WIN_WOW.md β docs-only polish/wow audit Β· friction-log.md β real DX notes for the QVAC/Pear teams Β· PITCH_DECK.md Β· SEED_DATA.md Β· DoraHacks submission
Apache-2.0 Β© 2026 Edy Cu β as required by the Tether Developers Cup rules, and kept that way afterwards.
Built solo for the Tether Developers Cup 2026 (QVAC track, on the Pears stack). Thank you for taking the time to review Gaffer β we wanted every fan to have a pundit in their ear, even the one with a dead phone in the away end, and we're proudest that it never once phones home.


