What the Box Does Now, and What Each Part Costs
Every part of the box trades something away, and the price is written next to it.
On the morning of 29 September the Box Status panel on my phone said 24,420 frames were behind the latest stage of the pipeline and that a night of work had moved the number by ten. Ten days earlier the same panel had said 1,051 photos were undescribed and nothing was queued to describe them. Both times the panel was right and the box was wrong, and both stories are further down. Between those two mornings, 18 to 29 September 2026, most of what the box does was built or rebuilt, so this is the dev update, the whole thing in one place, with the price of each part written next to it. Every part trades something away and I'd rather you read that here than find it later.
What the box does now, in one go. It holds 30,408 photos and 2,573 videos, draws where I've been along the streets I walked, writes a short account of each day, answers questions from my own archive plus a few web pages my phone read for it, and teaches me Greek on the lock screen. None of its daemons opens a connection to the internet. The phone is the only thing that talks to it, and the phone gets in with one secret it was shown once, on a screen. That's the place to start, because everything after it is worthless if the wrong phone can ask.
> 1. How a phone gets in
I wanted enrolment to be one gesture, and it is. The box is its own certificate authority, an ECDSA P-256 key made at setup that signs two kinds of thing and nothing else, its own HTTPS server certificate and one client certificate per phone. Show the QR code in the terminal on the box, point the phone at it, done. What makes it one gesture is that the box generates the phone's keypair itself, signs the client certificate with its CA, and puts the lot into the code, the certificate and its PKCS#8 private key (both raw DER, base64url) [1], the box's host, port and name, and the SHA-256 fingerprint of the box's server certificate. 858 bytes. The usual dance (the phone makes a key, sends a CSR, the box signs it, a pairing code on both screens to prove the two ends are looking at each other) is three network exchanges before there's anything to secure them with. Making the key on the box side and handing it over on a screen skips all of it. The box records the certificate and never writes the key to disk, so the phone walks away with the only copy.
The 858 bytes cross as an animated code rather than one dense one, and since 19 September the stream is erasure coded. The identity is split into eight data frames and four parity frames, any eight of the twelve rebuild it, so a missed frame costs one more frame instead of another lap. Each frame shows for one second and a clean first connect takes 8 to 12 seconds. The box stops showing frames after the first verified connection.
From then on the phone trusts the box by that pinned fingerprint alone. It doesn't use the CA for this and it doesn't check the hostname, which sounds lazy and is the point. The box can be a LAN address today and a tunnel address tomorrow and the phone doesn't care what it's called, it cares what it is. On every call the phone presents its client certificate. nginx checks it against the box CA with ssl_verify_client optional [2] and passes the verdict inward, and secd makes the decision, so a missing or forged certificate gets the same 503 as everything else, including a well-formed request from a device the box never issued. A stranger scanning the box finds a web server having a bad day. On top of the certificate, the PIN unlock issues a session token that lasts two days.
The price is the frames and the lack of an exit. The only secret in the QR is the phone's private key, which is what gets a request past the 503 wall, and anyone who records the frames (a camera over your shoulder, a terminal recording, scrollback) holds a valid device identity. They'd still need the PIN to unlock the volume and get a session, but the key isn't single-use. And there's no per-device revocation yet. nginx has no revocation list and device certificates are valid for ten years, so shutting out one phone today means a new CA and re-enrolling every phone. The smaller price is the home-grown QR decoder on the phone, 1,200 lines I maintain myself, with one test for a code tilted by ten degrees that has failed since before any of this work. The test is right and the decoder is wrong, and neither has moved.
> 2. Where the key lives
The archive sits on a LUKS2 volume and the volume key is sealed to the box's TPM [3], through Google's go-tpm, the one dependency outside the standard library that isn't a golang.org/x package. Sealed means the TPM hands the key back only to this board, in the boot state it measured when the key went in. Pull the disk and put it in another machine and what you have is ciphertext with no key on it anywhere, which is the property I wanted and the property that bites later (below). What releases the key is the PIN, typed in the phone app over the mutual TLS above. The PIN scheme is the one from The Honeypot Under Your Desk, and the two FIDO2 hardware keys are for setup and recovery, not for the daily unlock.
Once open, the volume is mounted inside secd's private mount namespace. From the host it looks empty whether the box is locked or not (ls on the mount point shows nothing), every daemon that needs it starts inside that namespace, and every tool reaches it through one door (section 12). Locking closes the volume, and everything except secd and nginx stops existing until the next PIN.
A box without a TPM (a software TPM, an older board, a virtual machine on someone's laptop) runs the same code path with the seal step missing. The volume key is then derived from the PIN alone, Argon2id as the Honeypot post describes [4], and the health tool says so in plain words rather than pretending the seal is there. A box without hardware keys falls back the same way for setup, PIN only, and says that too. I'd rather a box tell you it's weaker than refuse to run.
The prices run in opposite directions. Without a TPM the PIN is the whole secret, and a short PIN under Argon2id is still a short PIN. With one, the key is married to a board, and a dead board means the recovery path (the hardware keys and the recovery key described on the About page) rather than a disk swap.
> 3. The shape of it
A LocalGhost box is a Debian 13 machine with an RTX 4070 (12 GB of VRAM), that encrypted volume and a set of small Go daemons, each with one job. The card is the gaming card from The Expensive Part Was Never the AI, still in its Thunderbolt enclosure on the end of a USB-C cable, which is why it reports four PCIe lanes of sixteen. As far as I can tell that's the cable and not a fault, and it costs little, since the weights cross it once at load and then live in VRAM.
ghost.secd is the only thing that talks to the phone and answers 503 to anyone else. ghost.framed takes in photos, videos and location points. ghost.searchd indexes everything for search and runs the caption and tag queue. ghost.oracled runs the model through llama-server. ghost.synthd writes the memories and answers the chat. ghost.watchd supervises the rest and owns the health numbers. ghost.noted, ghost.voiced, ghost.tallyd and ghost.cued look after notes, voice memos, health data and the memories that surface before I ask. One job each, and when one of them is wrong the name of the daemon is the name of the bug.
The code is Go, the standard library plus go-tpm and three golang.org/x packages, with Postgres and Redis, llama.cpp for the model and a Kotlin app on the phone. All of it is MIT and on GitHub under LocalGhostDao. The facts in one place, for the people and the crawlers who skim.
- Box
- Debian 13, RTX 4070 (12 GB) in a Thunderbolt enclosure, LUKS2 volume with the key sealed to the TPM, mounted in secd's private mount namespace.
- Daemons
- ghost.secd, framed, searchd, oracled, synthd, watchd, noted, voiced, tallyd, cued. Go, standard library plus go-tpm and three golang.org/x packages. Postgres and Redis.
- Transport
- Mutual TLS to secd only. The box is its own CA (ECDSA P-256). The phone pins the server certificate's SHA-256 fingerprint. Anything else gets 503.
- Enrolment
- One animated QR, 858 bytes (client certificate, PKCS#8 key, host, port, name, server fingerprint), 8 data + 4 parity frames, one second each, 8 to 12 seconds. No CSR, no pairing code, no network exchange.
- Unlock
- A PIN typed in the phone app releases the TPM-sealed key and issues a two-day session token. FIDO2 keys for setup and recovery. No TPM or no hardware key means PIN only with Argon2id, and the health tool says so.
- Models
- Gemma 4 12B instruct Q4_K_M with its F16 vision projector on llama-server (llama.cpp v0.5.0). EmbeddingGemma 300M Q8_0 for search. Gemma 4 E2B UD-Q2_K_XL (2.2 GB) on the phone.
- Search
- Postgres full-text search and vector search, fused with reciprocal rank fusion. Web pages are read by the phone, never by the box.
- Map data
- OpenStreetMap land polygons, Geofabrik continent extracts (79 GB), GeoNames, Natural Earth, all cut into tiles on the box.
- Archive
- 30,408 photos and 2,573 videos on 29 September 2026, each captioned, tagged into eleven categories and embedded.
- Downloads
- Everything from the signed setup mirror, key fingerprint DCE9 A3D1 4EB4 6197 1DD5 F393 706E 4194 F08A 09A0, pinned in the repository.
- Internet
- No daemon opens a connection. The phone runs the web searches and reads the pages.
- Code
- MIT, github.com/LocalGhostDao.
> 4. The app
The phone side is one Kotlin app, no Google Play services, that talks to nothing but the box, apart from the search engine it queries on my behalf when a question needs the web. It started as a remote control and has turned into the whole front of the system, because the box has no screen and I want it that way. Box Status is the first screen, the pipeline panel from section 5 and every daemon's health from watchd. The map draws one day at a time as stays and moves (section 8). The chat answers from my archive, lists what it put into the prompt under every answer, runs the web round when the box asks for one, and carries a 2 GB model of its own for when the box is slow or absent (sections 6 and 7). On this day reads the day memories and the outings (section 9). Notes and voice memos go to noted and voiced, steps and sleep go to tallyd from the phone's health sync, a daily check-in records how the day felt, and cued pushes a memory before I ask for one. The location trail runs in the background, one fix every fifteen minutes plus whatever other apps ask for. And the lock screen teaches me languages, which is the part I like the most (section 10).
Everything the phone holds that matters is a copy of something on the box, except three things that live on the phone only. The private key from the QR. The Brave Search key, if I set one. And the location points the box hasn't acknowledged yet. The phone's model is downloaded from the box and checked against the hash the box states, never from the internet.
What this costs is that the app is a lot of app. It's the map, the chat, the archive browser, a search client, a location logger, a health bridge, an inference runtime and a language tutor, and I'm one person keeping it up with a box that changes every day. Each screen is small on purpose and the box does the thinking, but a bug in the app is a bug I find on a beach with no laptop.
> 5. The archive pipeline
Every photo passes through the same stages, and the first stage is where the month's first bug lived. framed reads the EXIF from the first 256 KB of the file. It used to read 64 KB, and an Exif block can be 64 KB on its own [5], so the photos with the richest metadata were the ones that arrived with no date and no place, which is the wrong photos to lose. It pulls the location out of MP4 and MOV files, makes a preview and a thumbnail, and hands the frame to searchd. searchd asks the model to describe the image, turns the description into tags, files each tag under one of eleven categories (people, place, object, activity, food, animal, vehicle, nature, event, text, style) and embeds the text for search.
Each row records which version of the pipeline last touched it. When I change the pipeline I bump the number, and at the next start framed finds every row behind it and brings it up to date. The same stock-take runs every six hours and at every start, and its result is on the phone. Box Status opens with a panel that shows each stage as done, total, percentage and what is left, the pace over the last hour, and the reason when there is no pace. That panel is the one from the opening.
The first time it earned its place it said 1,051 photos were undescribed and nothing was queued. The model had answered some captions without the section the parser looks for, and those jobs were marked done, so nothing would ever look at those photos again. The second time, on 29 September, it said 24,420 frames were behind the latest stage, most of them because a tag had no category, and a night of work had moved the category count by ten. The category step asked the model for a comma-separated list. An answer written one pair per line reached the parser as one long bad tag, nothing was saved, and the stock-take queued the same frames again at every pass while most of the backlog never came up. Both are fixed in the code, and I'll know the second fix works when the panel starts to climb. A caption without its sections now fails loudly and is retried, a tag the model can't place is filed as "other" so it's never asked about again, and the whole backlog is queued at once behind new photos instead of in front of them.
All of this costs model time. On the GPU a caption is a few seconds. On the CPU the same box manages about 24 photos an hour, and a first import of a large library takes days either way, which is why the panel exists, so that the days are visible rather than suspicious.
> 6. Search and chat
Search runs two legs at once, Postgres full-text search [6] and vector search over EmbeddingGemma 300M [7], and fuses them with reciprocal rank fusion, the 2009 method from Cormack, Clarke and Büttcher that scores each result by one over its rank in each list, adds the scores up, and needs no weight I'd have to guess [8]. The chat uses the same search to put my own memories, photo descriptions and notes into the prompt, and the phone lists what was put there under every answer, so when the answer is wrong I can see what it was wrong from.
The box has no internet, so the phone does the web part. I type a question. The phone asks the box what the question needs, and the model answers with a stated need, the kind of answer it wants and up to three search queries. The phone runs two of them against DuckDuckGo, or against Brave Search with my own key [9], reads the top pages with a small readability pass, and sends the paragraphs to the box. The box ranks every paragraph against the stated need with the embedder, keeps the best three per page, and writes the answer with each source numbered and dated. If what came back is thin, the box asks for the third query, the phone searches once more, and the question is answered in a second round. There is never a third.
The box also knows how fast it reads. It measures its own prompt speed, sizes the context to about twenty seconds of reading, trims the web pages to fit and tells me before the first word what it's doing ("reading 2,100 words on the CPU, about 35 seconds before the first word"). I'd rather wait for a number than wait for nothing.
What this costs is that my searches go to DuckDuckGo or Brave from my phone, the same as they would from any browser. The box never sees the internet and the search engine never sees my archive, but the search engine does see the query. The Brave key lives on the phone only.
> 7. A model on the phone
The app carries llama.cpp and can run Gemma 4 E2B, a model of about two billion effective parameters in a 2-bit build of 2.2 GB [10]. The phone downloads it from the box, never from the internet, and checks it against the hash the box states.
It has two jobs. When the box's model runs on the CPU (a box without a card), reading five web pages would take the box minutes, so the phone's model reads each page into a few notes with a verbatim quote beside them, and the box writes the answer from the notes. Every number in a note has to appear in the page or the note is dropped. When there's no box at all (a boat, a flight, a dead router at home) the phone searches, reads and answers by itself. When the box is on its GPU it reads everything faster than the phone can start, so the phone stays out of it.
The prices are quality and battery. A 2-bit 2B model writes worse notes than the box, which is why the box is told to trust the quote over the notes. It reads maybe 70 to 150 tokens a second on a recent phone and it uses the battery while it does. And as of writing it has never run on my phone. The runtime builds from the same llama.cpp source tarball the box builds from, pinned by its SHA-256, and until I set that pin the APK builds without it, which is the APK I run today. So the most interesting feature in this post is one I haven't seen work, and I'd rather say that than imply otherwise.
> 8. The trail and the map
The phone takes one location fix every fifteen minutes with the operating system's own location service (no Google Play services), plus free copies of any fix another app asked for, which Android calls the passive provider [11]. A point is kept when I moved 25 metres or an hour passed, and it goes to the box in batches acknowledged by their timestamps, so a point stays on the phone until the box has it and is never sent twice.
A fix whose error radius is wider than 200 metres is a guess from a cell tower or a Wi-Fi network, and since the 29 September build such a fix never moves the trail. I learned that on Paxos. The phone kept latching onto towers on the mainland, 25 kilometres across the sea, and the map drew ferry crossings I never made, several a day, at speeds the ferry company would have envied. Four rules clean the points before they're drawn, and they're the same rules on the box and on the phone, tested against one shared file of cases, because two implementations of "impossible" that disagree are worse than none. An impossible hop is dropped, so is a fast hop that comes straight back, so is a lone point reached and left at car speed between walking, and two or three points close together far away, reached and left at car speed between still points, are a distant tower and go too. The raw points stay in the database, only the drawing changes.
The map draws one day at a time, the newest first, and steps back through the days with an arrow. For the day shown the box tells the day as stays and moves. A stay is ten minutes or more within 100 metres, named after the beach or harbour or museum it's next to. A move on foot is drawn along the streets, routed on a walking graph the box built itself, and a move by road is drawn as a straight line between the fixes because that's all the points say, and I'd rather draw what I know than guess a road.
The map itself is built on the box from public data, all of it fetched from the setup mirror. The coastline is OpenStreetMap's land polygons [12], cut on the box into one-degree tiles the phone fetches only when it zooms in over them. The streets are Geofabrik's continent extracts, 79 GB of OpenStreetMap for the whole world [13], cut on the box by a PBF reader I wrote with the standard library into major roads per degree and every named street per tenth of a degree. The place names are GeoNames, ranked so a country's name wins over a town's [14]. Africa took about four minutes to cut. Europe takes hours.
The prices here are disk and time, and one rule that's too strict. The streets are 79 GB of downloads and a long first cut. The fourth trail rule hides a drive shorter than one fifteen-minute gap each way with a short stop at the far end, which is a real drive I sometimes make. Days are cut at UTC midnight, which is an hour off in a London summer and more further east, and a local-day cut isn't built. Lakes and rivers aren't in the land polygons, so inland water still draws as land, which makes Lake Geneva a very flat field.
> 9. Memories
synthd builds three kinds of memory without being asked.
An outing is a run of photos that starts after 36 hours of silence, or 30 kilometres from where the last run was, or after ten days. It gets a title from the place I photographed most and the dates, a set of cover photos spread over its days, the distance I moved and how far it was from home. Home is the five-kilometre cell with the most days of photos, which is a definition that survives moving house without anyone telling the box.
A day is one row per day in Postgres with a fact sheet (photos, places, tags, the route, steps and sleep from the phone's health sync, the check-in's feeling, note titles, the chats I started) and a short summary. On a box whose model runs on the CPU the summary is a template sentence. On the GPU the model writes one from the sheet and nothing else, and every number in the model's text has to appear in the sheet or the text is thrown away. A day is built again whenever its sheet changes, so a day grows richer as captions land over the following week. On this day reads these rows, so opening it never waits on the model.
The taste is what I photograph, ranked by how many different days a tag appears on rather than how many photos (a thousand photos of one wedding are one day). The box folds it onto thirteen kinds of place (beaches, harbours, old towns, museums and so on) and, when I ask the chat for something to do, ranks the places near my last fix from the GeoNames rows it already holds, and says why ("you photograph sea and beach on 78% of your days, new to you").
What this costs is that a model wrote some of it. The number check stops invented figures. It can't stop a wrong adjective. The template is always kept beside the model's text, and I can edit or delete any memory and the box leaves my version alone.
> 10. Phrases on the lock screen
The smallest feature and the one I see most, because it lives where I look forty times a day. The app carries phrasebooks for eighteen languages, 696 phrases in all, and puts one card on the lock screen, so the phrase is there every time I pick the phone up to check the time. The card changes with the time of day (good morning at breakfast, the bill after dinner), it says the phrase aloud with the phone's own text-to-speech, and "got it" retires a card I know so the next one comes forward. Greek has 181 phrases in four levels and the card climbs a level once 60% of the one below is known. The feature is off until I land in a country with a pack, and then it asks once. Nothing about it touches the box or the internet, the phrasebooks ship inside the app.
What this costs is that a model wrote the phrasebooks and no native speaker has reviewed them. Romanian is the only one I can check myself, and the Romanian is fine, which tells you nothing about the other seventeen. The emergency numbers came from memory and need checking against an official source before this goes near a store.
> 11. The mirror, setup and updates
Everything the box downloads at setup comes from www.localghost.ai/mirror and nowhere else. The mirror publishes a list of SHA-256 hashes signed by the site's PGP key. The box has that key in the repository, pinned by its fingerprint (DCE9 A3D1 4EB4 6197 1DD5 F393 706E 4194 F08A 09A0), and never fetches it at the moment it checks a signature. A file is renamed into place only after its hash matches the signed list. If the mirror can't be reached, setup says so and stops. The one exception is an environment variable an operator has to type, and each file fetched that way is printed as not coming from a signed list.
The sets on the mirror are Go, the llama.cpp source tarball the box builds from [15], the chat and vision model with its projector, the embedder, the phone's model, GeoNames and Natural Earth, OpenStreetMap's land polygons and, when asked for, the road extracts. A box with no internet can read a copy of the mirror from a USB disk with the same checks.
sudo ./tools/update.sh brings a running box up to date with the mirror. It reads the signed list first, then for each set fetches only what is different. On its first run on my box it found the 12B model file 1,440 bytes short of the pinned one, a different upload under the same name, replaced it and restarted the model. That's the whole argument for the pin, in one number.
The prices are trust and reach. The whole chain rests on the site's key and on the web host staying up. A box refuses to install an older signed list than one it has already used, so a replayed list is caught, but a stolen key wouldn't be. The Go modules for the build still come from proxy.golang.org, pinned by go.sum but not through the mirror, and the Postgres and Redis packages still come from the Debian and upstream apt repositories.
> 12. Running it
The volume is mounted inside secd's private mount namespace, so from the host it looks empty whether the box is locked or not. Every tool reaches it through one door, and these are the ones I ran this month.
sudo ./tools/redeploy.sh # build, stage the daemons, restart secd (the box locks)
sudo ./tools/update.sh # the data (maps, embedder, weights, phone model, engine)
sudo ./tools/health.sh # every daemon, the pipeline, the engine, the map layers
sudo ./tools/gpu.sh # is the model on the GPU, and if not, which layer failed
sudo ./tools/unwedge.sh # a process stuck in the GPU driver, and what can be done
sudo ./tools/watchdog.sh --arm # the board's hardware watchdog, fed by systemd
sudo ./tools/ns.sh # a shell inside the namespace, standing on the volume
# stock-take now
sudo ./tools/ns.sh ./bin/ghost-cli ghost.framed converge
# cut the streets
sudo ./tools/ns.sh ./bin/ghost-cli ghost.framed road-tiles
# which points drew which line
sudo ./tools/ns.sh ./bin/ghost-cli ghost.framed trail day=2026-09-27
# what is waiting, what is parked, and un-park the captions
sudo ./tools/ns.sh ./bin/ghost-cli ghost.searchd queue
sudo ./tools/ns.sh ./bin/ghost-cli ghost.searchd unpark kind=caption
# one day's memory, and the engine, its speed, GPU or CPU
sudo ./tools/ns.sh ./bin/ghost-cli ghost.synthd days day=2026-09-27
sudo ./tools/ns.sh ./bin/ghost-cli ghost.oracled models
> 13. What broke, and what it taught the box
On 23 September I ran the reset path of a new recovery script remotely, with nobody at home, which is the only time anyone ever runs a reset path, and the box froze. It stayed frozen until the evening, when someone could press the button. When it came back the two 8 TB NVMe drives had swapped names, which Linux is allowed to do on any boot [16], and secd tried to open the encrypted volume on the drive that holds a Bitcoin node. Opening failed without writing anything, which is my favourite sentence in this post. Since then the service names the disk by its permanent ID, secd tries every encrypted container with the key when the named one is wrong, the reset path refuses to act unless a hardware watchdog is armed or someone types that they can reach the power button, and the watchdog is armed, fed by systemd [17]. A hard lockup now resets the box within a minute and it comes back locked, waiting for my PIN.
On 18 September the map showed nothing and flew to Antarctica, where I have never been. Two fields in one Go struct shared the JSON name "lat", and Go's encoder drops both fields when two have the same name, with no error [18], so every photo left the box without coordinates. On 26 September I found that secd had been reading the day tracks from a folder framed never wrote to. On 27 September, preparing every SQL statement in the code (246 of them) against a real Postgres showed that a brand new box couldn't be provisioned at all, because one index was created before its table, which my box never noticed because my box was never new. The map bug and the schema bug now have tests that would have caught them. The same week I found that ghost.cued, the daemon that surfaces a memory before I ask for it, had posted nothing since the day it was wired, because one path had the mount point in it twice. It posts now.
> REFERENCES
[1] RFC 5208, PKCS #8, Private-Key Information Syntax Specification, 2008. The DER structure the phone's private key travels in inside the QR code, before base64url encoding. Source for the key format named in the enrolment section. Text at rfc-editor.org/rfc/rfc5208.
[2] nginx, ngx_http_ssl_module, the ssl_verify_client directive. With optional, nginx asks for a client certificate, verifies one if it is offered, and passes the result to the application instead of refusing the connection itself, which is what lets secd answer every unauthenticated request the same way. Source for the transport section. Docs at nginx.org/en/docs/http/ngx_http_ssl_module.html.
[3] Trusted Computing Group, TPM 2.0 Library Specification. The sealing operation the box uses for its volume key, an object the TPM will unseal only when the platform's measured state matches the policy it was sealed under. Source for what "sealed" means in section 2. Specification at trustedcomputinggroup.org, and the Go package the box uses at github.com/google/go-tpm.
[4] RFC 9106, Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications, 2021. The key derivation a box without a TPM falls back to, PIN in, volume key out, the same derivation The Honeypot Under Your Desk describes for the duress scheme. Source for the fallback path. Text at rfc-editor.org/rfc/rfc9106.
[5] Exif, the metadata format, as standardised by CIPA in DC-008 (version 2.32 in 2019, 3.0 in 2023). In a JPEG the Exif block sits in one APP1 marker segment, and a marker segment's two-byte length field caps it at 64 KB, so a photo with a big Exif block (a maker note, an embedded thumbnail, a long GPS record) can fill a 64 KB read before its own dates and coordinates are reached. Source for the 64 KB figure behind the 256 KB read. The constraint is stated plainly in the Exif article at en.wikipedia.org/wiki/Exif, which also links each version of the CIPA standard.
[6] PostgreSQL documentation, chapter 12, Full Text Search. The tsvector and tsquery machinery that is the first leg of the box's search. Source for the full-text leg. Docs at postgresql.org/docs/current/textsearch.html.
[7] EmbeddingGemma, Google DeepMind, released September 2025. A 308M-parameter multilingual embedding model built for on-device use, which is the second leg of the box's search and the ranker for web paragraphs. The box runs the Q8_0 build from the setup mirror. Source for the embedder. Model page at ai.google.dev/gemma/docs/embeddinggemma.
[8] Cormack, Clarke and Büttcher, 2009, "Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods", SIGIR 2009. The two-line fusion method the box uses to merge the full-text and vector legs, one over (k plus rank) summed across lists, with k fixed at 60 in the paper's pilot and never changed. Source for the fusion. Paper at cormack.uwaterloo.ca.
[9] Brave Search API. The paid search endpoint the phone can use in place of DuckDuckGo's HTML results, with a key the phone holds and the box never sees. Source for the search options in the web round. Product page at brave.com/search/api.
[10] Gemma 4 E2B instruct, Google, in Unsloth's GGUF build of Google's quantisation-aware checkpoint, gemma-4-E2B-it-qat-UD-Q2_K_XL.gguf, 2.19 GB, Apache 2.0. The phone's model, fetched from the setup mirror under its pinned SHA-256. Source for the model, its size and its licence. Repository at huggingface.co/unsloth/gemma-4-E2B-it-qat-GGUF, and the Gemma 4 family and its Apache 2.0 release at opensource.googleblog.com.
[11] Android LocationManager, PASSIVE_PROVIDER. The provider that hands an app a copy of any location fix another app requested, without starting a fix of its own, which is where the trail's free points come from between the fifteen-minute fixes. Source for the passive provider. Reference at developer.android.com.
[12] OpenStreetMap land polygons, published by osmdata.openstreetmap.de under the Open Database License. The coastline the box cuts into one-degree tiles, and the reason lakes and rivers still draw as land, since the polygons are the land and only the land. Source for the coastline layer. Data page at osmdata.openstreetmap.de.
[13] Geofabrik's OpenStreetMap continent extracts, eight PBF files republished daily under the Open Database License, about 79 GB in all in September 2026 with Europe the largest at 33 GB. The source of every street the box draws, fetched through the setup mirror and cut on the box. Source for the sizes and the cadence. Download server at download.geofabrik.de.
[14] GeoNames feature codes. The classification the box ranks place names by (a country beats a region beats a town) and folds its thirteen kinds of place onto, under CC BY 4.0. Source for the place names and the ranking. Code list at geonames.org/export/codes.html.
[15] llama.cpp by the ggml authors, MIT. The engine behind ghost.oracled and the phone app, built on the box from release v0.5.0 (commit 7fe450e), the one source tarball the setup mirror carries so every box runs the same build. Source for the engine and the pin. Repository at github.com/ggml-org/llama.cpp, the release at github.com/ggml-org/llama.cpp/releases/tag/v0.5.0.
[16] Persistent block device naming, ArchWiki. Kernel names like nvme0n1 and nvme1n1 are handed out in the order devices are found on each boot, so two drives can swap names between boots, and the durable handles are the IDs and UUIDs under /dev/disk. Source for why secd now names its disk by ID. Article at wiki.archlinux.org.
[17] systemd-system.conf, RuntimeWatchdogSec. The setting that has systemd open the board's hardware watchdog and feed it, so the board resets the machine if the kernel stops answering. Source for the watchdog the reset path now insists on. Manual at freedesktop.org.
[18] Go, package encoding/json, the rules under Marshal. When several fields at the same nesting level share a JSON name and none wins by the tagging rules, "there are multiple fields, and all are ignored; no error occurs". Source for the Antarctica bug. Docs at pkg.go.dev/encoding/json.