# tokenling **tokenling**, a GPT-2 pet that runs entirely in your browser. A small Tamagotchi-like creature whose brain is a fully fine-tuned [GPT-2](https://huggingface.co/openai-community/gpt2) (the original 124M model). The brain makes the decisions: it chats, toddles around its room, reacts to pats, decides whether it wants the snack, bath, medicine or game of catch you offer, wants things, naps and dreams, remembers things about you and writes a diary page at bedtime. The page only runs its body (hunger, energy, mess) and its room. It grows from baby to kid, teen and adult in real time. There's no server or API: the egg hatches once the page has downloaded the brain (about 97 MB, 4-bit) into the browser's Cache Storage, and it runs on the visitor's device with transformers.js on WebAssembly. The pet, its chat, memories, gifts and diary live in `localStorage`. It installs as an app, works offline, and can send alerts the owner turns on (no push server: the page plans them and its service worker shows them). New brains are offered as an upgrade; compatible older brains can stay in use. After a prompt-format change the pet dozes until its new brain is downloaded, keeping its save. ## The pet's tools GPT-2 was fine-tuned to call tools inline in its own lines. The app parses them as they stream: | Tag | Effect | |---|---| | `[emote:happy\|love\|sad\|sleepy\|wave\|dance\|spin\|blush\|grumpy\|wow\|giggle\|cry]` | sprite animation | | `[want:snack\|play\|bath\|sleep\|pats\|medicine]` | thought bubble | | `[walk:left\|right]` | walks a few steps | | `[yes]`, `[no]` | answers an offer (`* Sam offered Mochi a cookie *`); only `[yes]` feeds, bathes, medicates or starts catch | | `[remember:FACT]` | saves a memory about you | | `[gift:flower\|pebble\|star\|leaf\|shell\|heart\|drawing\|feather]` | puts a gift on your shelf | | `[diary:NOTE]` | jots a note for tonight's diary | | `[clock]`, `[look]`, `[recall:KEYWORD]`, `[yesterday]` | **lookups**: generation stops, the app appends `{result}`, and the model continues the same line | | `[sleep]` | falls asleep | | `[dream:X]` | dreams of an allowed sprite while asleep | A line of only tags (`Mochi: [walk:left] [emote:happy]`) is the pet acting on its own; the app asks for one every few seconds. Every prompt starts with a state header, rendered identically by `train/world.py` and `web/world.ts`: ``` ## tokenling chat pet: Mochi · kid · day 3 · curious, foodie owner: Sam now: 21:40 night, rainy needs: tummy hungry, fun happy, energy sleepy, clean remembers: Sam likes frogs; Sam has a cat named Bo today: ate a cookie; played catch (4 stars); had a bath --- Sam: what time is it? Mochi: [clock]{21:40 night} it's so late!! [emote:sleepy] [want:snack] ``` ## Documentation | | | |---|---| | [`AGENTS.md`](AGENTS.md) | Maintainer contract for humans and coding agents: invariants, rules, commands. Start here before changing anything. | | [`docs/architecture.md`](docs/architecture.md) | Modules, the pet's turn, where the model comes from | | [`docs/prompt-format.md`](docs/prompt-format.md) | The header, event lines, tool grammar and lookups; the contract with the weights | | [`docs/pet-simulation.md`](docs/pet-simulation.md) | Life cycle, stats, care actions, minigame, diary, storage schema, LCD | | [`docs/training.md`](docs/training.md) | Teacher data, validation, fine-tuning, evals, the run receipts | | [`docs/export-and-runtime.md`](docs/export-and-runtime.md) | ONNX export, 4-bit quantization, transformers.js runtime, why not WebGPU | | [`docs/host-page.md`](docs/host-page.md) | The shipped page and the markup, config and CSS classes the app needs | | [`docs/deployment.md`](docs/deployment.md) | What to publish, server requirements, shipping and rolling back a model, sharing GPUs | | [`docs/testing.md`](docs/testing.md) | Parity test, evals and `playwright-cli` recipes | | [`docs/extending.md`](docs/extending.md) | Adding emotes, tools, foods, stages; what needs retraining | | [`docs/gotchas.md`](docs/gotchas.md) | Symptoms, causes and fixes for the sharp edges | ## Layout | Path | Contents | |---|---| | `web/main.tsx`, `web/components/` | Preact entry and typed UI components: device/setup, chat, diary, shelf, settings, brain notice/dialog and debug panel | | `web/app/` | typed state, save format/storage, body simulation, care, animations, conversation, diary, model client, game, PWA and lifecycle controllers; see the module map in `docs/architecture.md` | | `web/types.ts`, `web/runtime-types.ts` | save, scene and worker message types; the vendored runtime API boundary | | `web/brain.ts` | model worker; bundled separately as `brain.js` | | `web/scene.ts`, `web/sprites.ts`, `web/world.ts` | pixel LCD, sprite icons and the Python-compatible prompt format | | `package.json`, `package-lock.json`, `tsconfig.json` | pinned Preact, TypeScript, esbuild and Prettier dependencies; strict type checking | | `site/index.html`, `site/assets/` | the page (a template with `@PLACEHOLDERS@`), its font, web app manifest and icons | | `site/sw.js` | service worker: offline copies of the page, app and runtime; alerts | | `site/build.sh`, `site/bundle.mjs`, `site/release.json` | builds the static site from the release description (current and older models) | | `site/make-icons.mjs` | draws the app icons in `site/assets/` | | `train/world.py` | prompt format, tool results and event lines, mirrored by `web/world.ts` | | `train/gen_data.py`, `train/teachers.sh` | synthetic chat/diary data from a teacher model, validated tag by tag | | `train/migrate_v6.py` | migrates old chat headers and sleep events, rebuilds loss spans and replaces old teen rows | | `train/mix_data.py` | reuses an older dataset's rows that the current format still accepts | | `train/train.py` | full fine-tune, with loss on the pet's own words and tool calls only | | `train/eval_pet.py`, `train/quant_eval.py` | behavioral eval through the app's lookup loop; quantization quality | | `train/export.sh`, `train/quantize.py` | ONNX export and 4-bit quantization for WebAssembly | | `train/vendor.sh`, `train/publish.sh` | pinned browser runtime and static publishing | | `train/check_onnx.py` | greedy-decode check of an export against torch | | `test/parity.mjs`, `test/parity_states.py` | JS/Python prompt parity | | `test/data_rules.py` | training-contract and migration regression cases, including a stand-in sleep teacher | | `test/site_tools.py` | stdlib regressions for safe build/publish failures, immutable artifacts and preview runtime refresh | | `test/contract.mjs` | checks the Preact component ids, host mount/config contract and docs | | `test/body.test.mjs`, `test/storage.test.mjs`, `test/fixtures/save-v1.json` | body rules and save compatibility, including a save captured from the previous app | | `test/browser-flow.js` | repeatable playwright-cli browser regression flow against a local debug preview | | `test/dev-site.sh`, `test/serve.py` | local preview: builds the site, stages the runtime and models and serves them with COOP/COEP headers | | `test/bench.html` | brain load time, speed and memory in a real browser (included as unlinked `bench.html` in every site build) | The frontend uses Preact and strict TypeScript. `site/build.sh` type-checks, bundles the app and worker with esbuild, then fills the static host template. No server is needed at runtime. See [`docs/deployment.md`](docs/deployment.md). ## Quick start ```sh npm ci # Node.js 24 or newer npm test # types, parity, UI contract, body/save and data rules npm run format:check # consistent TS/TSX formatting node test/parity.mjs # JS and Python prompts agree node test/contract.mjs # app, reference page and docs agree on element ids site/build.sh /tmp/site # the static site (add models/ and runtime/ with train/publish.sh) # preview in a browser (needs an exported model and the vendored runtime): MODEL=$DATA/release/tokenling-v6 RUNTIME=$DATA/release/runtime test/dev-site.sh /tmp/dev-site # -> http://127.0.0.1:8765/pet/ ``` Nothing in the scripts is tied to a particular machine: inputs are arguments or environment variables, and each script's header lists them. Datasets, checkpoints and exports (`$DATA`) stay out of Git. Training uses one Python environment with torch and CUDA (`$VENV`), and ONNX export uses a second one (`$EXP`, see [`docs/export-and-runtime.md`](docs/export-and-runtime.md)). The full rebuild, from data to a published model, is in [`docs/training.md`](docs/training.md), [`docs/export-and-runtime.md`](docs/export-and-runtime.md) and [`docs/deployment.md`](docs/deployment.md). Published model and runtime directories are immutable: ship a new model id, update `site/release.json`, then build and deploy. ## License [MIT](LICENSE). Copyright (c) 2026 Ava Chaney. The bundled Nunito font retains its [SIL Open Font License](site/assets/OFL-nunito.txt).