Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Thor Tigress Cub

Thor Tigress Cub

thor-tigress-cub-junior · Haloom!

I am typing this from a studio apartment in Seattle. The Thor sits in the corner, humming like a low-grade heater that barely warms the place. What it really does is run a 30-billion-parameter model on a single board and spit out about 53 tokens a second. Five of these boards can be stacked into a cluster, so the capacity grows one board at a time.

The page is at voltforge.tech/thor-tigress-cub. You open it, type a question, and watch the answer crawl out word by word. You can unfold the folded reasoning line, see colour-coded code blocks, and a little speed counter sits underneath. It costs nothing, it asks for no account, and your conversation lives only in your browser.

Two models answer today, and both live behind the same thor-tigress-serve process. Nemotron 3 Nano 30B A3B, the default, runs on llama.cpp at about 53 tokens a second. Qwen3.6 35B A3B runs on TensorRT Edge-LLM at about 78 tokens a second when it is loaded. The picker can spin up two of them at once, across the two engine back-ends.

Where it is headed: the agentic canvas

The launch is just around the corner, and the next step is an agentic canvas sandbox: a place where a model can reach for tools and work on a visual canvas.

6 planned uses of the agentic canvas: roleplay with characters who remember the scene; a therapist with a calm listener and nothing written down on the server; immigration with forms and covering letters checked against the rules; creative story writing with story bibles, drafts and revisions side by side; music generation with a timeline the model can add notes to and play back; and vector graphics with drawing commands it can run and then look at.
Figure 24.1 The canvas, and the 6 uses it is being built for. All 6 are planned.
Planned useWhat it means
Role-playcharacters that remember the scene, with the scene kept on the canvas
A therapista calm listener, with nothing written down on the server
Immigrationforms and covering letters drafted with you and checked against the rules
Creative writingstory bibles, drafts and revisions standing side by side
Music generationa timeline the model can add notes to and play back
Vector graphicsdrawing commands it can run and then look at

The book marks what is live in green and what is planned in amber. The canvas is amber, and it is the reason the rest of this chapter exists: the chat is the surface that runs today, and these six are the ones I am building toward.

What the chat does right now

  • Streams the answer. Ask in English, Rust, or whatever language you paste at it, and the reply streams out as it is written.
  • Searches the web when you turn Web on: the built-in SearXNG engine fetches results and lists the sources it used.
  • Reasons before it answers when you turn Think on: the reasoning appears folded above the answer, ready to be expanded.
  • Picks a model from the ones loaded, two at a time, across the two engine back-ends.
  • Works from a coding agent. The API speaks the OpenAI and Anthropic shapes, so Claude Code, OpenCode and openbatrangs point at it with a base URL and a key.
  • Wears 11 themes, from Paper and Night to Solarized, Nord, Dracula, One Dark, Gruvbox, Monokai and Material Deep Ocean, and it follows your device until you pick a favourite.
  • Fits a phone, composer, threads and all, down to a 390-pixel-wide screen.
The chat page in its light theme: the cub in the middle, a greeting, and 4 suggested prompts
Figure 24.2 The opening screen, in Paper.
A conversation in the dark theme: a question, a folded thought line, an answer with a highlighted Rust code block, a stats line, and a reply in progress
Figure 24.3 A reply in Night, with the reasoning folded away, code highlighted, and the speed underneath.
The chat page on a phone in the dark theme
Figure 24.4 The same page on a 390-pixel-wide phone.
A Rust answer in the page: a highlighted code block for the example that does not compile, the reasons it fails, and the start of the corrected example.
Figure 24.5 A Rust answer, with the code block highlighted.
A question answered with Web on: the search line and the sources as titled links, then the answer below.
Figure 24.6 A question with Web on: the sources it used, then the answer.
Video 24.1 The page in use: a Rust answer streaming in, then a question with Web on.
The chat page in Material Deep Ocean: the question typed in, the answer streaming with a highlighted code block, and the stats line when it finishes.
Figure 24.7 The same demo as a GIF, in Material Deep Ocean. 400 frames, the most a LinkedIn post animates.
The same conversation in 11 themes in turn: Material Deep Ocean, One Dark, Dracula, Nord, Monokai, Solarized Dark, Gruvbox Dark, Night, Paper, GitHub Light and Solarized Light.
Figure 24.8 One conversation, 11 themes.
Web search in Material Deep Ocean: Web switched on, the search line and the sources listed as titled links, then the answer streaming in below with its stats line.
Figure 24.9 Web search as a GIF: the sources, then the answer.
The chat page at phone width: the home screen naming Nemotron 3 Nano 30B A3B, then Dijkstra's algorithm in Rust at 51.5 tokens a second; then the home screen naming Qwen3.6 35B A3B NVFP4 and its answer at 65.8.
Figure 24.10 Two models, one question, at phone width: Dijkstra's algorithm on an adjacency map, from Nemotron 3 Nano at 51.5 tokens a second and from Qwen3.6 35B A3B on TensorRT Edge-LLM at 65.8.

Getting a key

Each person gets a personal key of their own. Leave a name and an email on the invite screen, then send me a message: LinkedIn or X. I approve the request by hand and send the key back, a single 48-hex string. There is no sign-up, no password, and no company in the middle.

The invite screen: the cub, the headline that the cub is invite-only for now, a box to paste an access key, and a link to ask for a key
Figure 24.11 The door: what someone without a key meets. The picture predates the name-and-email form and the DM links, which chapter "Web chat: Thor Tigress Cub" describes.

Until recently a single shared key opened the door for everybody. Now each key can be granted or revoked one person at a time.

What the board actually stores

The conversation belongs to your browser, and it stays there. The board holds three things:

WhereWhat is storedUntil
Your browserthe full conversation, the UI settings, your keyyou clear site data or press +
The board’s memorythe reply being written, and a small prompt cachethe model process restarts or unloads
The board’s diskone encrypted file: name, email, status and the keythe record is deleted
3 columns. Your browser holds the conversations, the settings and your key, cleared with site data. The board's memory holds the reply being written and a small prompt cache, gone on restart. The board's disk holds the sealed keyring: 1 record per person with a name, an email, a status and a key, and the service key the servers use between themselves. No message text is written down.
Figure 24.12 The 3 places anything is held.

The chat server writes one file, the keyring, and a keyring record has no field for a message (crates/thor-tigress-keyring/src/store.rs). No raw chat text is ever written down. Once an answer is sent, the request is gone; the operating system keeps its usual short journal, as it does for every service on the machine. The page sets no cookie of its own, carries no analytics and no advertising, and asks for no login.

How a key is checked

A browser or an agent sends a personal key in a header over HTTPS through Tailscale Funnel. On the board, thor-tigress-agent compares it with the active keys in the sealed keyring, in constant time, and answers 401 when nothing matches. A match is forwarded to the model server with the service key from api-key, and the model server checks that key again.
Figure 24.13 The 2 keys, and where each key stops.
  1. The browser, or an agent, sends the personal key in an Authorization: Bearer <key> header over HTTPS, through Tailscale Funnel.
  2. The invite screen posts a name and an email to POST /request, and the record waits with no key.
  3. thor-tigress-serve keyring approve EMAIL gives that person 24 random bytes and prints them once as 48 hex characters.
  4. The page keeps the key in local storage and sends it as Authorization: Bearer <key>. Coding agents send the same header, or x-api-key.
  5. thor-tigress-agent compares the key against every active key in the keyring, in constant time. A key that matches nothing leaves with 401 before a model is reached. A request that matches travels to the model you picked under the service key from api-key, and the model server checks that key again. Personal keys never go that far.

The keyring itself is sealed with Argon2id and XChaCha20-Poly1305. Chapter “Keys, and the cryptography under them” opens the file byte by byte and explains the two algorithms, along with the four things they cannot protect.

Scaling: how many people can play?

A loaded model keeps its weights and a pool of windows (context slots). The default Nano takes 33.6 GB of weights and about 24 GiB for a 4-million-token window. With two models loaded, the chat measured 57.8 GB of RAM while it was serving, against 128 GB on the board. How the pool is cut is a setting, and on one board it is cut like this:

Windows open at onceTokens per windowPool
41,048,57624 GiB
8524,28824 GiB
16262,14424 GiB
32131,07224 GiB

The arithmetic is windows × tokens × 6 KiB, and memory is the ceiling. Want longer conversations? Fewer windows fit. Want more windows? Each one gets shorter. One conversation can stretch to 1 million tokens, roughly three novels.

There is no per-person quota. Someone with a key can keep a board busy for as long as they keep asking, and the only way to stop them is to revoke the key. The boards stay warm while they work, which in Seattle is a small consolation.

Inside the machine

thor-tigress-serve list prints the models on the boards:

$ thor-tigress-serve list

Thor: 59.3 GB free of 122.8 GB · keeps 8 GB free · at most 2 models loaded

  key  model                                          state                 size
  1    Nemotron 3 Nano 30B A3B · Q8_0                 loaded             33.6 GB  default (nemotron)
  2    Nemotron 3.5 Lightning 30B A3B · Q8_0          on disk            35.0 GB
  3    Qwen3.6 27B · Q4_K_M                           on disk            16.8 GB
  4    Qwen3.6 35B A3B NVFP4                          loaded             23.4 GB  TensorRT Edge-LLM · :8081
A browser or agent opens voltforge.tech, whose forwarding page sends it to the .ts.net address. Every request then goes through Tailscale Funnel to thor-tigress-agent on the Thor at :8080, which holds the key, the model picker and the APIs, and calls SearXNG at :8888 for web search. The picker's model id decides which engine runs the reply: the llama.cpp router at :8079 for the GGUF models, or TensorRT Edge-LLM at :8081 for Qwen3.6 35B A3B in NVFP4.
Figure 24.14 How a message reaches a model, on either engine.
PieceWhat it doesListens on
llama-server, router mode1 process per loaded GGUF model: the Nano, Lightning and Qwen 27B127.0.0.1:8079
TensorRT Edge-LLMthe Edge-LLM engine, for Qwen3.6 35B A3B in NVFP4 and its successors127.0.0.1:8081
thor-tigress-agentthe page, the model picker, the OpenAI and Anthropic APIs, the keys, the invite form127.0.0.1:8080
SearXNGweb search, when Web is on127.0.0.1:8888
Tailscale FunnelHTTPS from the internet to port 8080, and to nothing elsepublic

Everything lives under ~/.config/thor-chat/:

FileContents
models.inithe router’s list of models, written by thor-tigress-serve
envMODEL, USERS, CONTEXT, MODELS_MAX, EDGE_MODEL, ports
api-keythe internal key the agent and the model servers share
keyringwho may chat, sealed
keyring-passphrasewhat opens the keyring

One command, thor-tigress-serve, manages all of it:

TaskCommand
find, load, unload, download a modellist, list-latest, load KEY, unload KEY, download KEY
install the services and start them at bootinstall
create the keyringkeyring-init
see who asked, approve, revokekeyring requests, keyring approve EMAIL, keyring revoke EMAIL, keyring revoke-all
follow the logslogs

The crates sit in the repository under Apache-2.0: thor-tigress-agent for the server, thor-tigress-keyring for the registry, thor-spark-safety-eval for the prose and code checker, thor-hammer-trainer for the training set, thor-tigress-reinforcer-frontend for the review page, and thor-lasso-distiller for conversations drawn out of books. Chapters “Model serving”, “TensorRT Edge-LLM” and “Operations: recovery and hardening” walk through running the whole thing yourself, on this board or another.

Where the promise hits its limits

  • The operator can open the keyring. Whoever holds a board’s account and the passphrase file can read every name, email and key inside it. Encryption protects a copy of the file that leaves the machine; on the machine itself, the passphrase file is the weak point.
  • The invite form is open, and it can be filled with junk. A person approves each request by hand, so junk collects in the waiting list and goes no further. Every request also costs a board an Argon2id run.
  • A key is a password. Anyone holding one chats as the person it was sent to, until the key is revoked.
  • The windows serve the page and every coding agent together.
  • The chat is a beta. It can be busy, offline, or out of memory, and when it is down the forwarding page still loads.

The rest of the book