How we built an interactive 3D educational app that turns tokens, decoding, and Supermemory into a living factory — and why memory is the spine, not a side feature.
TL;DR
Token Factory is a Next.js + React Three Fiber web app that teaches how large language models generate text by animating every step as physical “token cubes” moving through industrial rooms: tokenizer, embeddings, logits, softmax, temperature, top-k, top-p, sampling, context window, KV cache, speculative decoding — bookended by a Supermemory vault that recalls crystals before generation and stores conversations after.
Repo: github.com/harishkotra/token-factory
npx supermemory local # required memory layer on :6767
npm install && cp .env.example .env.local
# set SUPERMEMORY_API_KEY=sm_...
npm run dev
1. The problem we set out to solve
Most explanations of LLM inference are either:
- Walls of math — logits, softmax, nucleus sampling as formulas, or
- Black-box chat UIs — a text box, a spinner, a paragraph.
Neither builds intuition. Temperature feels abstract until you see a distribution flatten. Top-p is hard until you watch a probability bucket fill and stop. Memory layers sound like “RAG with better marketing” until a crystal flies from a vault into the context conveyor.
We wanted something closer to:
- Factorio (systems you can watch)
- Brilliant.org (learn by interaction)
- Apple onboarding (motion does the explaining)
…with a serious technical point: modern agents aren’t just decoders; they are memory-augmented systems. So Supermemory Local (localhost:6767) is not an optional plugin. It is the product’s core infrastructure.
2. Product framing
Elevator pitch
Instead of invisible mathematics, every token becomes a physical cube moving through an AI factory. Users don’t read about AI — they watch it happen. And the factory remembers.
Core loop (always)
User prompt
→ Supermemory search + profile
→ Enriched context (memory / profile / prompt cubes)
→ Educational decoding pipeline (rooms)
→ Append tokens → answer streams in the Response panel
→ Persist episodic crystal → vault refresh
There is no “disable memory” toggle in production UX. Decoding knobs (temperature, top-k/p, KV cache, speculative) sit on top of a memory spine that never turns off.
3. High-level architecture
┌─────────────────────────────────────────────────────────────────┐
│ Browser (Next.js App Router) │
│ │
│ ConnectionGate ──► connection-store (health must be green) │
│ HUD: TopBar · StageChrome · ResponsePanel · PromptDock · Vault │
│ FactoryScene (R3F / Three.js) ◄── factory-store timeline │
│ │ │
│ │ ALWAYS: recall → decode rooms → persist │
└─────────┼───────────────────────────────────────────────────────┘
│ /api/health /api/memory /api/memory/search
│ /api/seed /api/profile
▼
┌─────────────────────────────────────────┐
│ MemoryService (src/lib/supermemory) │
│ Official supermemory SDK │
│ baseURL → http://localhost:6767 │
└──────────────────┬──────────────────────┘
▼
supermemory-server
(graph · embeddings · auth)
Why this split?
| Layer | Responsibility |
|---|---|
| Zustand stores | Orchestrate timed animations + UI state |
| Next API routes | Keep the Supermemory API key server-side |
| MemoryService | Single source of truth for health, seed, search, list, persist |
| llm-simulator + answer-planner | Educational decode + coherent demo answers |
| R3F scene | Visual rooms; never empty when idle |
Failure policy is intentional and strict:
- Missing API key → misconfigured gate
- Supermemory down → disconnected gate; Start disabled
- Recall fails mid-run → abort (no fake success)
- Tour mode exists only as camera-only, labeled non-generative
4. Tech stack
| Concern | Choice | Why |
|---|---|---|
| App framework | Next.js 16 (App Router), TypeScript | Fast API routes + React UI |
| Styling | Tailwind CSS v4 | Utility-first HUD; glass panels |
| 3D | Three.js, React Three Fiber, Drei | Declarative scenes, OrbitControls |
| Motion | Framer Motion | Drawer / gate / response transitions |
| State | Zustand | Lightweight store for long generation timelines |
| UI primitives | Radix + Lucide | Accessible sliders, switches |
| Memory |
supermemory SDK → :6767
|
Local-first Memory API for the hackathon |
Important Tailwind note for anyone cloning: do not let Tailwind v4 auto-scan binary dirs. Supermemory’s local DB under .supermemory/data can produce invalid CSS if scanned. We restrict sources in globals.css:
@import "tailwindcss";
@source not "../../../.supermemory";
@source not "../../../.next";
@source not "../../../node_modules";
@source "../../**/*.{js,ts,jsx,tsx,mdx}";
5. Factory rooms (what each mode teaches)
Each room has a short blurb shown in stage chrome. The design rule: one sentence, never a paragraph.
| Room | Teaching moment |
|---|---|
| Memory Vault | Crystals = conversations / facts; search before generate |
| Tokenizer | Text → token cubes (incl. crude subword splits) |
| Embedding | Glow = “vector of meaning” (no raw numbers) |
| Transformer | Attention atmosphere — gears & beams, not matrix dumps |
| Logits | Candidates scored; taller/brighter = higher score |
| Softmax | Scores → probabilities that sum to 100% |
| Temperature | Low T peaks; high T flattens creativity |
| Top-K | Only K survive the laser gate |
| Top-P | Fill a probability bucket until ≥ P |
| Sampling | One token chosen (lottery claw) |
| Append | Winner joins the answer string; loop |
| Context | Finite window; oldest fall off |
| KV Cache | Green stamps skip recompute |
| Speculative | Small draft vs large verify |
Idle rooms still show demo cubes/props so navigation never lands on an empty floor. During a run, only the active room ± neighbors mount heavy content to reduce flicker and cost.
6. Memory layer: Supermemory as infrastructure
Client configuration
// src/lib/supermemory/client.ts (conceptual)
import Supermemory from "supermemory";
export function getSupermemoryClient() {
return new Supermemory({
apiKey: process.env.SUPERMEMORY_API_KEY!,
baseURL: process.env.SUPERMEMORY_BASE_URL || "http://localhost:6767",
});
}
Operations we implement
// Conceptual MemoryService surface
checkHealth() // documents.list({ limit: 1 }) + latency
ensureSeeded() // idempotent customId seeds (Phaser, tokens, …)
searchAndProfile(q) // search.memories + profile
listVault() // documents.list for the Vault UI
persistGeneration(...) // client.add with episodic metadata
Seed memories
On first successful connect, we upsert semantic seeds such as:
- Phaser is a 2D HTML5 game framework
- Tokens are subword pieces of text
- Temperature controls randomness
- KV cache speeds up autoregressive inference
So demos like “Tell me about Phaser” hit real search scores, not a hardcoded offline array as the source of truth.
API routes
| Route | Role |
|---|---|
GET /api/health |
Connection badge + gate |
GET/POST /api/memory |
List vault / persist generation |
POST /api/memory/search |
Recall for the pipeline |
POST /api/seed |
Idempotent demo seeds |
GET /api/profile |
Static/dynamic profile lines |
Keys never ship to the browser; the client only talks to same-origin Next routes.
7. Generation orchestration
The heart of the experience is runGeneration() in the factory store: a timed state machine that advances room, phase, candidates, and generated[] with sleeps so the camera and 3D scene can follow.
Pseudocode:
async function runGeneration() {
await ensureSupermemoryConnected(); // else abort + gate
// 1) Recall
const { memories, profile } = await search(prompt);
// 2) Plan a coherent answer from memory (educational sim)
const plan = planAnswerTokens(prompt, memories, maxTokens);
// 3) Build context cubes: profile + memory + prompt
let context = mergeContext(profile, memories, prompt);
// 4) For each planned token: animate the full decode path
for (const forced of plan) {
showRoom("transformer");
let cubes = makeCandidateCubes(context, 8, { forcedWinner: forced });
cubes = applySoftmax(cubes, temperature);
cubes = applyTopK(cubes, topK);
cubes = applyTopP(cubes, topP).cubes;
const picked = sampleOne(cubes, { forcedWinner: forced, greedy: true });
append(picked.text);
context = trim(context + picked, contextWindow);
}
// 5) Persist episodic crystal
await persist({ prompt, completion: generated.join(" ") });
await refreshVault();
}
Why an “answer planner”?
Early prototypes sampled purely from a toy vocabulary. That produced vault entries like:
robot accept of reject token token
Cute for chaos mode; terrible for demos. The answer planner turns top Supermemory hits (or heuristics) into a token sequence, while the factory still shows logits → filters → sampling. The planned token is injected as the highest-logit candidate so UI lessons stay true and the Response panel stays readable.
// src/lib/answer-planner.ts (excerpt)
export function planAnswerTokens(
prompt: string,
memories: MemoryCrystal[],
maxTokens = 20
): string[] {
if (memories.length > 0) {
const best = /* highest score/importance */;
const words = cleanWords(best.content || best.summary);
if (words.length >= 3) return words.slice(0, maxTokens);
}
// heuristics for Phaser, temperature, tokens, …
// soft fallback
}
Softmax (educational core)
export function applySoftmax(cubes: TokenCube[], temperature: number) {
const T = Math.max(0.05, temperature);
const scaled = cubes.map((c) => c.logit / T);
const max = Math.max(...scaled);
const exps = scaled.map((z) => Math.exp(z - max));
const sum = exps.reduce((a, b) => a + b, 0);
return cubes.map((c, i) => ({
...c,
probability: exps[i] / sum,
intensity: exps[i] / sum, // drives cube height / glow
}));
}
Top-k and top-p then prune alive flags for the laser gate and bucket animations.
8. 3D and UX engineering notes
Contained stage
Early versions used full-viewport absolute HUD over a full-bleed Canvas. Result: overlapping text, unreadable “AI is responding,” and chaotic flicker as the camera teleported through 14 rooms.
Current layout:
┌ Top bar (logo, connection, Help) ─────────────┐
│ Contained 3D stage (rounded frame) │
│ + StageChrome (mode blurb, zoom, room rail) │
├ Response panel (prompt + streaming answer) ───┤
└ Prompt dock (Start / Decode / Levels) ────────┘
Camera & zoom
OrbitControls owns the camera. Room changes and stageZoom set target + distance once (effect-driven), instead of fighting scroll zoom every frame:
// Conceptual camera update
const look = new THREE.Vector3(...meta.lookAt);
const baseCam = new THREE.Vector3(...meta.camera);
const dist = baseCam.distanceTo(look) * stageZoom;
camera.position.copy(look.clone().add(dir.multiplyScalar(dist)));
controls.target.copy(look);
controls.update();
Buttons: zoom in (stageZoom /= 1.2), zoom out (*= 1.2), reset (1). Scroll still works via OrbitControls.
Connection UX
ConnectionGate blocks the experience until Supermemory is healthy, with copy-paste steps for npx supermemory local and .env.local. The connection badge shows live latency (e.g. Supermemory · live · 37ms).
9. Project layout (for navigators)
src/
app/api/{health,memory,memory/search,seed,profile}/
components/
factory/ # R3F scene, rooms, cubes, conveyor
hud/ # gate, badge, stage chrome, response, help, vault
ui/ # button, slider, switch, label
lib/
supermemory/ # client, config, service
llm-simulator.ts # tokenize, logits, softmax, top-k/p, sample
answer-planner.ts # memory-grounded token plans
rooms.ts levels.ts types.ts
store/
factory-store.ts
connection-store.ts
ARCHITECTURE.md
10. What we deliberately did not do
- No silent offline memory store as source of truth — demos must exercise Supermemory.
- No hosted LLM requirement for the factory path — keeps the hackathon demo offline-friendly (except local SM) and focused on pedagogy.
- No walls of documentation in-UI — Help drawer + one-line mode blurbs only.
A future path is clear: swap the answer planner for a real model API while keeping the same visual decode path and Supermemory spine.
11. Try it yourself
git clone https://github.com/harishkotra/token-factory.git
cd token-factory
npx supermemory local # copy sm_ key
cp .env.example .env.local # paste key
npm install && npm run dev
Suggested first run:
- Wait for Supermemory · live
- Prompt:
Tell me about Phaser - Watch vault → context colors (amber memory / violet profile / cyan prompt) → decode rooms
- Read the Answer panel
- Open Vault — new episodic crystal
12. Closing thought
Token Factory is a bet that systems literacy for AI needs the same visual language we use for games and product design. If a student can feel why temperature changes creativity in thirty seconds, and see a memory crystal join the context conveyor, we’ve done more than ship a hackathon demo — we’ve made the invisible pipeline a place you can walk through.
Build something you can point a camera at. Then make it remember.
Code & more: https://www.dailybuild.xyz/project/196-token-factory
Top comments (0)